commit ed9536f6ab6a5655904ecdf33f877ea3680c5e37 Author: JenniferW <1627055433@qq.com> Date: Thu Jan 15 15:50:49 2026 +0800 首次提交 diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000..dc3bc09 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,4 @@ +> 1% +last 2 versions +not dead +not ie 11 diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..5654e89 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,5 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..737e5d8 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", + "changelog": [ + "@changesets/changelog-github", + { "repo": "aiflowy/aiflowy" } + ], + "commit": false, + "fixed": [["@aiflowy-core/*", "@aiflowy/*"]], + "snapshot": { + "prereleaseTemplate": "{tag}-{datetime}" + }, + "privatePackages": { "version": true, "tag": true }, + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.commitlintrc.js b/.commitlintrc.js new file mode 100644 index 0000000..e45361c --- /dev/null +++ b/.commitlintrc.js @@ -0,0 +1 @@ +export { default } from '@aiflowy/commitlint-config'; diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..52b833a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +node_modules +.git +.gitignore +*.md +dist +.turbo +dist.zip diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..179aec6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset=utf-8 +end_of_line=lf +insert_final_newline=true +indent_style=space +indent_size=2 +max_line_length = 100 +trim_trailing_whitespace = true +quote_type = single + +[*.{yml,yaml,json}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d4e5bd3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings + +# Automatically normalize line endings (to LF) for all text-based files. +* text=auto eol=lf + +# Declare files that will always have CRLF line endings on checkout. +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf + +# Denote all files that are truly binary and should not be modified. +*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary \ No newline at end of file diff --git a/.gitconfig b/.gitconfig new file mode 100644 index 0000000..4b28a69 --- /dev/null +++ b/.gitconfig @@ -0,0 +1,2 @@ +[core] + ignorecase = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3399f39 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +node_modules +.DS_Store +dist +dist-ssr +dist.zip +dist.tar +dist.war +.nitro +.output +*-dist.zip +*-dist.tar +*-dist.war +coverage +*.local +**/.vitepress/cache +.cache +.turbo +.temp +dev-dist +.stylelintcache +yarn.lock +package-lock.json +.VSCodeCounter +**/backend-mock/data + +# local env files +.env.local +.env.*.local +.eslintcache + +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* +vite.config.mts.* +vite.config.mjs.* +vite.config.js.* +vite.config.ts.* + +# Editor directories and files +.idea +# .vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +.history +.cursor diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000..5fda2cf --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,6 @@ +ports: + - port: 5555 + onOpen: open-preview +tasks: + - init: npm i -g corepack && pnpm install + command: pnpm run dev:play diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..ee5c244 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +22.1.0 diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..aeac1ae --- /dev/null +++ b/.npmrc @@ -0,0 +1,13 @@ +registry=https://registry.npmmirror.com +public-hoist-pattern[]=lefthook +public-hoist-pattern[]=eslint +public-hoist-pattern[]=prettier +public-hoist-pattern[]=prettier-plugin-tailwindcss +public-hoist-pattern[]=stylelint +public-hoist-pattern[]=*postcss* +public-hoist-pattern[]=@commitlint/* +public-hoist-pattern[]=czg + +strict-peer-dependencies=false +auto-install-peers=true +dedupe-peer-dependents=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..d0b0ca1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,18 @@ +dist +dev-dist +.local +.output.js +node_modules +.nvmrc +coverage +CODEOWNERS +.nitro +.output + + +**/*.svg +**/*.sh + +public +.npmrc +*-lock.yaml diff --git a/.prettierrc.mjs b/.prettierrc.mjs new file mode 100644 index 0000000..c7740a9 --- /dev/null +++ b/.prettierrc.mjs @@ -0,0 +1 @@ +export { default } from '@aiflowy/prettier-config'; diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 0000000..f4b2db2 --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,4 @@ +dist +public +__tests__ +coverage diff --git a/aiflowy.code-workspace b/aiflowy.code-workspace new file mode 100644 index 0000000..53fc07b --- /dev/null +++ b/aiflowy.code-workspace @@ -0,0 +1,152 @@ +{ + "folders": [ + { + "name": "@aiflowy/app", + "path": "app", + }, + { + "name": "@aiflowy/commitlint-config", + "path": "internal/lint-configs/commitlint-config", + }, + { + "name": "@aiflowy/eslint-config", + "path": "internal/lint-configs/eslint-config", + }, + { + "name": "@aiflowy/prettier-config", + "path": "internal/lint-configs/prettier-config", + }, + { + "name": "@aiflowy/stylelint-config", + "path": "internal/lint-configs/stylelint-config", + }, + { + "name": "@aiflowy/node-utils", + "path": "internal/node-utils", + }, + { + "name": "@aiflowy/tailwind-config", + "path": "internal/tailwind-config", + }, + { + "name": "@aiflowy/tsconfig", + "path": "internal/tsconfig", + }, + { + "name": "@aiflowy/vite-config", + "path": "internal/vite-config", + }, + { + "name": "@aiflowy-core/design", + "path": "packages/@core/base/design", + }, + { + "name": "@aiflowy-core/icons", + "path": "packages/@core/base/icons", + }, + { + "name": "@aiflowy-core/shared", + "path": "packages/@core/base/shared", + }, + { + "name": "@aiflowy-core/typings", + "path": "packages/@core/base/typings", + }, + { + "name": "@aiflowy-core/composables", + "path": "packages/@core/composables", + }, + { + "name": "@aiflowy-core/preferences", + "path": "packages/@core/preferences", + }, + { + "name": "@aiflowy-core/form-ui", + "path": "packages/@core/ui-kit/form-ui", + }, + { + "name": "@aiflowy-core/layout-ui", + "path": "packages/@core/ui-kit/layout-ui", + }, + { + "name": "@aiflowy-core/menu-ui", + "path": "packages/@core/ui-kit/menu-ui", + }, + { + "name": "@aiflowy-core/popup-ui", + "path": "packages/@core/ui-kit/popup-ui", + }, + { + "name": "@aiflowy-core/shadcn-ui", + "path": "packages/@core/ui-kit/shadcn-ui", + }, + { + "name": "@aiflowy-core/tabs-ui", + "path": "packages/@core/ui-kit/tabs-ui", + }, + { + "name": "@aiflowy/constants", + "path": "packages/constants", + }, + { + "name": "@aiflowy/access", + "path": "packages/effects/access", + }, + { + "name": "@aiflowy/common-ui", + "path": "packages/effects/common-ui", + }, + { + "name": "@aiflowy/hooks", + "path": "packages/effects/hooks", + }, + { + "name": "@aiflowy/layouts", + "path": "packages/effects/layouts", + }, + { + "name": "@aiflowy/plugins", + "path": "packages/effects/plugins", + }, + { + "name": "@aiflowy/request", + "path": "packages/effects/request", + }, + { + "name": "@aiflowy/icons", + "path": "packages/icons", + }, + { + "name": "@aiflowy/locales", + "path": "packages/locales", + }, + { + "name": "@aiflowy/preferences", + "path": "packages/preferences", + }, + { + "name": "@aiflowy/stores", + "path": "packages/stores", + }, + { + "name": "@aiflowy/styles", + "path": "packages/styles", + }, + { + "name": "@aiflowy/types", + "path": "packages/types", + }, + { + "name": "@aiflowy/utils", + "path": "packages/utils", + }, + { + "name": "@aiflowy/turbo-run", + "path": "scripts/turbo-run", + }, + { + "name": "@aiflowy/vsh", + "path": "scripts/vsh", + }, + ], +} diff --git a/app/.env b/app/.env new file mode 100644 index 0000000..cf6fcbd --- /dev/null +++ b/app/.env @@ -0,0 +1,8 @@ +# 应用标题 +VITE_APP_TITLE=AIFlowy + +# 应用命名空间,用于缓存、store等功能的前缀,确保隔离 +VITE_APP_NAMESPACE=aiflowy-web + +# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密 +VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key diff --git a/app/.env.analyze b/app/.env.analyze new file mode 100644 index 0000000..ffafa8d --- /dev/null +++ b/app/.env.analyze @@ -0,0 +1,7 @@ +# public path +VITE_BASE=/ + +# Basic interface address SPA +VITE_GLOB_API_URL=/api + +VITE_VISUALIZER=true diff --git a/app/.env.development b/app/.env.development new file mode 100644 index 0000000..92e0426 --- /dev/null +++ b/app/.env.development @@ -0,0 +1,13 @@ +# 端口号 +VITE_PORT=5090 + +VITE_BASE=/ + +# 接口地址 +VITE_GLOB_API_URL=http://127.0.0.1:8080 + +# 是否打开 devtools,true 为打开,false 为关闭 +VITE_DEVTOOLS=false + +# 是否注入全局loading +VITE_INJECT_APP_LOADING=true diff --git a/app/.env.production b/app/.env.production new file mode 100644 index 0000000..2fc1855 --- /dev/null +++ b/app/.env.production @@ -0,0 +1,26 @@ +VITE_BASE=/ + +# 接口地址 +VITE_GLOB_API_URL=/ + +# 是否开启压缩,可以设置为 none, brotli, gzip +VITE_COMPRESS=none + +# 是否开启 PWA +VITE_PWA=false + +# vue-router 的模式 +VITE_ROUTER_HISTORY=hash + +# 是否注入全局loading +VITE_INJECT_APP_LOADING=true + +# 打包后是否生成dist.zip +VITE_ARCHIVER=true + + + + + + + diff --git a/app/index.html b/app/index.html new file mode 100644 index 0000000..239dace --- /dev/null +++ b/app/index.html @@ -0,0 +1,32 @@ + + + + + + + + + + + <%= VITE_APP_TITLE %> + + + + +
+ + + + + diff --git a/app/package.json b/app/package.json new file mode 100644 index 0000000..eba0a8d --- /dev/null +++ b/app/package.json @@ -0,0 +1,59 @@ +{ + "name": "@aiflowy/app", + "version": "1.0.0", + "homepage": "https://aiflowy.tech", + "bugs": "https://github.com/aiflowy/aiflowy/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/aiflowy/aiflowy.git", + "directory": "app" + }, + "type": "module", + "scripts": { + "build": "pnpm vite build --mode production", + "build:analyze": "pnpm vite build --mode analyze", + "dev": "pnpm vite --mode development", + "preview": "vite preview", + "typecheck": "vue-tsc --noEmit --skipLibCheck" + }, + "imports": { + "#/*": "./src/*" + }, + "dependencies": { + "@aiflowy-core/shadcn-ui": "workspace:*", + "@aiflowy/access": "workspace:*", + "@aiflowy/common-ui": "workspace:*", + "@aiflowy/constants": "workspace:*", + "@aiflowy/hooks": "workspace:*", + "@aiflowy/icons": "workspace:*", + "@aiflowy/layouts": "workspace:*", + "@aiflowy/locales": "workspace:*", + "@aiflowy/plugins": "workspace:*", + "@aiflowy/preferences": "workspace:*", + "@aiflowy/request": "workspace:*", + "@aiflowy/stores": "workspace:*", + "@aiflowy/styles": "workspace:*", + "@aiflowy/types": "workspace:*", + "@aiflowy/utils": "workspace:*", + "@element-plus/icons-vue": "^2.3.2", + "@tinyflow-ai/vue": "^1.1.10", + "@vueuse/core": "catalog:", + "dayjs": "catalog:", + "dompurify": "^3.3.1", + "element-plus": "catalog:", + "fetch-event-stream": "^0.1.6", + "highlight.js": "^11.11.1", + "markdown-it": "^14.1.0", + "pinia": "catalog:", + "radash": "^12.1.1", + "vue": "catalog:", + "vue-cropper": "^1.1.4", + "vue-element-plus-x": "^1.3.7", + "vue-router": "catalog:", + "vue3-json-viewer": "^2.4.1" + }, + "devDependencies": { + "cssnano": "catalog:", + "unplugin-element-plus": "catalog:" + } +} diff --git a/app/postcss.config.mjs b/app/postcss.config.mjs new file mode 100644 index 0000000..b5ceb95 --- /dev/null +++ b/app/postcss.config.mjs @@ -0,0 +1 @@ +export { default } from '@aiflowy/tailwind-config/postcss'; diff --git a/app/public/empty-dark.png b/app/public/empty-dark.png new file mode 100644 index 0000000..d1537a4 Binary files /dev/null and b/app/public/empty-dark.png differ diff --git a/app/public/empty.png b/app/public/empty.png new file mode 100644 index 0000000..41429c1 Binary files /dev/null and b/app/public/empty.png differ diff --git a/app/public/favicon.png b/app/public/favicon.png new file mode 100644 index 0000000..c03e093 Binary files /dev/null and b/app/public/favicon.png differ diff --git a/app/public/favicon.svg b/app/public/favicon.svg new file mode 100644 index 0000000..e4e6ccd --- /dev/null +++ b/app/public/favicon.svg @@ -0,0 +1,11 @@ + + + 编组备份 + + + + + + + + diff --git a/app/public/load.min.js b/app/public/load.min.js new file mode 100644 index 0000000..f482e16 --- /dev/null +++ b/app/public/load.min.js @@ -0,0 +1 @@ +const Math=window.Math,head=document.getElementsByTagName("head")[0],TIMEOUT=1e4,TAC_LOADING_DIV='
请稍等...
';function showLoading(e){var t=document.querySelector(e);t&&(t.innerHTML=TAC_LOADING_DIV)}function hideLoading(e){let t=document.querySelector(e);t&&(t.innerHTML="")}function loadCaptchaScript(e,t,n,r,o){const i=e.scriptUrls,c=e.cssUrls,l=e.timeout||TIMEOUT;let s=i.length+c.length;function d(e,i){if(s--,e&&0===s){if(hideLoading(t.bindEl),!window.TAC)throw new Error("TAC未加载,请检查地址是否正确");r(new TAC(t,n))}else e||(hideLoading(t.bindEl),o(i))}setTimeout(()=>{0!==s&&showLoading(t.bindEl)},10),i.forEach(function(e){loadResource("string"==typeof e?{url:e}:e,d,"script",l)}),c.forEach(function(e){loadResource("string"==typeof e?{url:e}:e,d,"link",l)})}function loadResource(e,t,n="script",r){if(document.querySelector(`${n}[${"script"===n?"src":"href"}="${e.url}"]`))return void t(!0,e);let o=!1;const i=document.createElement(n);"link"===n?i.rel="stylesheet":i.async=!0,i["script"===n?"src":"href"]=e.url;let c;i.onload=i.onreadystatechange=(()=>{o||i.readyState&&"loaded"!==i.readyState&&"complete"!==i.readyState||function t(n){e.checkOnReady?c=setTimeout(()=>{e.checkOnReady()?n():t(n)},10):n()}(()=>{o=!0,setTimeout(()=>t(o,e),0)})}),i.onerror=(()=>{t(o=!1,e)}),head.appendChild(i),setTimeout(()=>{o||(c&&clearTimeout(c),i.onload=i.onerror=null,i.remove&&i.remove(),t(o,e))},r||TIMEOUT)}function loadTAC(e,t,n){return new Promise((r,o)=>{let i={..."string"==typeof e?{url:e}:e};i.url&&(i.url.endsWith("/")||(i.url+="/"),i.scriptUrls||(i.scriptUrls=[i.url+"js/tac.min.js"]),i.cssUrls||(i.cssUrls=[i.url+"css/tac.css"])),i.scriptUrls&&i.cssUrls?loadCaptchaScript(i,t,n,r,o):o("请按照文档配置tac")})}setTimeout(()=>{let e=document.scripts,t=null;for(let n=0;n1||e[n].src.indexOf("load.min.js")>1){t=e[n].src.substring(e[n].src.indexOf("/"),e[n].src.lastIndexOf("/"));break}},100),window.loadCaptchaScript=loadCaptchaScript,window.loadTAC=loadTAC,window.initTAC=loadTAC; diff --git a/app/public/logo.svg b/app/public/logo.svg new file mode 100644 index 0000000..d65df8b --- /dev/null +++ b/app/public/logo.svg @@ -0,0 +1,22 @@ + + + 编组备份 2 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/public/logoDark.svg b/app/public/logoDark.svg new file mode 100644 index 0000000..1a97672 --- /dev/null +++ b/app/public/logoDark.svg @@ -0,0 +1,22 @@ + + + 编组备份 2 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/public/logoMini.svg b/app/public/logoMini.svg new file mode 100644 index 0000000..dbc45c8 --- /dev/null +++ b/app/public/logoMini.svg @@ -0,0 +1,11 @@ + + + 编组 13备份 + + + + + + + + \ No newline at end of file diff --git a/app/public/slogan.png b/app/public/slogan.png new file mode 100644 index 0000000..029391e Binary files /dev/null and b/app/public/slogan.png differ diff --git a/app/public/tac/css/tac.css b/app/public/tac/css/tac.css new file mode 100644 index 0000000..9a842f7 --- /dev/null +++ b/app/public/tac/css/tac.css @@ -0,0 +1,7 @@ +#tianai-captcha-parent{box-shadow:0 0 11px 0 #999;width:318px;height:318px;overflow:hidden;position:relative;z-index:997;box-sizing:border-box;border-radius:5px;padding:8px}#tianai-captcha-parent #tianai-captcha-box{height:260px;width:100%;position:relative;overflow:hidden}#tianai-captcha-parent #tianai-captcha-box .loading{width:120px;height:20px;-webkit-mask:linear-gradient(90deg, #000 70%, rgba(0, 0, 0, 0) 0) 0/20%;background:linear-gradient(#f7b645 0 0) 0/0% no-repeat rgba(221,221,221,.4196078431);animation:cartoon 1s infinite steps(6);margin:120px auto}@keyframes cartoon{100%{background-size:120%}}#tianai-captcha-parent #tianai-captcha-box #tianai-captcha{transform-style:preserve-3d;will-change:transform;transition-duration:.45s;transform:translateX(-300px)}#tianai-captcha-parent #tianai-captcha-bg-img{background-color:#fff;background-position:top;background-size:cover;z-index:-1;width:100%;height:100%;top:0;left:0;position:absolute;border-radius:6px}#tianai-captcha-parent .slider-bottom{height:19px;width:100%}#tianai-captcha-parent .slider-bottom .close-btn{width:20px;height:20px;background-image:url(../images/icon.png);background-repeat:no-repeat;background-position:0 -14px;float:right;margin-right:2px;cursor:pointer}#tianai-captcha-parent .slider-bottom .refresh-btn{width:20px;height:20px;background-image:url(../images/icon.png);background-position:0 -167px;background-repeat:no-repeat;float:right;margin-right:10px;cursor:pointer}#tianai-captcha-parent .slider-bottom .logo{height:30px;float:left}#tianai-captcha-parent .slider-move-shadow{animation:myanimation 2s infinite;height:100%;width:5px;background-color:#fff;position:absolute;top:0;left:0;filter:opacity(0.5);box-shadow:1px 1px 1px #fff;border-radius:50%}#tianai-captcha-parent #tianai-captcha-slider-move-track-mask{border-width:1px;border-style:solid;border-color:#00f4ab;width:0;height:32px;background-color:#a9ffe5;opacity:.5;position:absolute;top:-1px;left:-1px;border-radius:5px} +#tianai-captcha{text-align:left;box-sizing:content-box;width:300px;height:260px;z-index:999}#tianai-captcha .slider-bottom .logo{height:30px}#tianai-captcha .slider-bottom{height:19px;width:100%}#tianai-captcha .content .tianai-captcha-tips{height:25px;width:100%;position:absolute;bottom:-25px;left:0;z-index:999;font-size:15px;line-height:25px;color:#fff;text-align:center;transition:bottom .3s ease-in-out}#tianai-captcha .content .tianai-captcha-tips.tianai-captcha-tips-error{background-color:#ff5d39}#tianai-captcha .content .tianai-captcha-tips.tianai-captcha-tips-success{background-color:#39c522}#tianai-captcha .content .tianai-captcha-tips.tianai-captcha-tips-on{bottom:0}#tianai-captcha .content #tianai-captcha-loading{z-index:9999;background-color:#f5f5f5;text-align:center;height:100%;overflow:hidden;position:relative;display:flex;justify-content:center;align-items:center}#tianai-captcha .content #tianai-captcha-loading img{display:block;width:45px;height:45px}#tianai-captcha #tianai-captcha-slider-bg-canvas{position:absolute;left:0;top:0;width:100%;height:100%;border-radius:5px}#tianai-captcha #tianai-captcha-slider-bg-div{position:absolute;left:0;top:0;width:100%;height:100%;border-radius:5px}#tianai-captcha #tianai-captcha-slider-bg-div .tianai-captcha-slider-bg-div-slice{position:absolute}@keyframes myanimation{from{left:0}to{left:289px}} +#tianai-captcha.tianai-captcha-slider{z-index:999;position:absolute;left:0;top:0;user-select:none}#tianai-captcha.tianai-captcha-slider .content{width:100%;height:180px;position:relative;overflow:hidden}#tianai-captcha.tianai-captcha-slider .bg-img-div{width:100%;height:100%;position:absolute;transform:translate(0px, 0px)}#tianai-captcha.tianai-captcha-slider .bg-img-div img{height:100%;width:100%;border-radius:5px}#tianai-captcha.tianai-captcha-slider .slider-img-div{height:100%;position:absolute;left:0;transform:translate(0px, 0px)}#tianai-captcha.tianai-captcha-slider .slider-img-div #tianai-captcha-slider-move-img{height:100%}#tianai-captcha.tianai-captcha-slider .slider-move{height:34px;width:100%;margin:11px 0;position:relative}#tianai-captcha.tianai-captcha-slider .slider-move-track{position:relative;height:32px;line-height:32px;text-align:center;background:#f5f5f5;color:#999;transition:0s;font-size:14px;box-sizing:content-box;border:1px solid #f5f5f5;border-radius:4px}#tianai-captcha.tianai-captcha-slider .refresh-btn,#tianai-captcha.tianai-captcha-slider .close-btn{display:inline-block}#tianai-captcha.tianai-captcha-slider .slider-move{line-height:38px;font-size:14px;text-align:center;white-space:nowrap;color:#88949d;-moz-user-select:none;-webkit-user-select:none;user-select:none;filter:opacity(0.8)}#tianai-captcha.tianai-captcha-slider .slider-move .slider-move-btn{transform:translate(0px, 0px);position:absolute;top:-6px;left:0;width:63px;height:45px;background-color:#fff;background-repeat:no-repeat;background-size:contain;border-radius:5px}#tianai-captcha.tianai-captcha-slider .slider-tip{margin-bottom:5px;font-weight:bold;font-size:15px;line-height:normal;color:#000}#tianai-captcha.tianai-captcha-slider .slider-move-btn:hover{cursor:pointer} +#tianai-captcha.tianai-captcha-rotate .rotate-img-div{height:100%;text-align:center}#tianai-captcha.tianai-captcha-rotate .rotate-img-div img{height:100%;transform:rotate(0deg);display:inline-block} +#tianai-captcha.tianai-captcha-concat .tianai-captcha-slider-concat-img-div{background-size:100% 180px;position:absolute;transform:translate(0px, 0px);z-index:1;width:100%}#tianai-captcha.tianai-captcha-concat .tianai-captcha-slider-concat-bg-img{width:100%;height:100%;position:absolute;transform:translate(0px, 0px);background-size:100% 180px} +#tianai-captcha.tianai-captcha-disable{z-index:999;position:absolute;left:0;top:0}#tianai-captcha.tianai-captcha-disable .content{width:100%;height:180px;position:relative;overflow:hidden}#tianai-captcha.tianai-captcha-disable .content .bg-img-div{background-image:url(../images/dun.jpeg);width:100%;height:100%;overflow:hidden}#tianai-captcha.tianai-captcha-disable .content .bg-img-div #content-span{color:#fff;overflow:hidden;margin-top:132px;display:block;text-align:center} +#tianai-captcha.tianai-captcha-word-click{box-sizing:border-box}#tianai-captcha.tianai-captcha-word-click .click-tip{position:relative;height:40px;width:100%}#tianai-captcha.tianai-captcha-word-click .click-tip .tip-img{height:35px;position:absolute;right:15px}#tianai-captcha.tianai-captcha-word-click .click-tip #tianai-captcha-click-track-font{font-size:18px;display:inline-block;height:40px;line-height:40px;position:absolute}#tianai-captcha.tianai-captcha-word-click .slider-bottom{position:relative;top:6px}#tianai-captcha.tianai-captcha-word-click .content #bg-img-click-mask{width:100%;height:100%;position:absolute;left:0;top:0}#tianai-captcha.tianai-captcha-word-click .content #bg-img-click-mask .click-span{position:absolute;left:0;top:0;border-radius:50px;background-color:#409eff;width:20px;height:20px;text-align:center;line-height:20px;color:#fff;border:2px solid #fff;box-sizing:content-box}#tianai-captcha.tianai-captcha-word-click .click-confirm-btn{width:100%;height:35px;border-radius:4px;background-image:linear-gradient(173deg, hsl(38.09, 91%, 57.89%) 0%, hsl(38.09, 89.38%, 71.74%) 100%);font-size:15px;text-align:center;box-sizing:border-box;line-height:35px;color:#fff;margin-top:3px}#tianai-captcha.tianai-captcha-word-click .click-confirm-btn:hover{cursor:pointer} diff --git a/app/public/tac/images/dun.jpeg b/app/public/tac/images/dun.jpeg new file mode 100644 index 0000000..347a371 Binary files /dev/null and b/app/public/tac/images/dun.jpeg differ diff --git a/app/public/tac/images/icon.png b/app/public/tac/images/icon.png new file mode 100644 index 0000000..586a123 Binary files /dev/null and b/app/public/tac/images/icon.png differ diff --git a/app/public/tac/js/tac.min.js b/app/public/tac/js/tac.min.js new file mode 100644 index 0000000..6a4e2f4 --- /dev/null +++ b/app/public/tac/js/tac.min.js @@ -0,0 +1 @@ +(()=>{"use strict";var t,e,a={783:(t,e,a)=>{var i=a(618),n=Object.create(null),r="undefined"==typeof document,s=Array.prototype.forEach;function c(){}function o(t,e){if(!e){if(!t.href)return;e=t.href.split("?")[0]}if(l(e)&&!1!==t.isLoaded&&e&&e.indexOf(".css")>-1){t.visited=!0;var a=t.cloneNode();a.isLoaded=!1,a.addEventListener("load",(function(){a.isLoaded||(a.isLoaded=!0,t.parentNode.removeChild(t))})),a.addEventListener("error",(function(){a.isLoaded||(a.isLoaded=!0,t.parentNode.removeChild(t))})),a.href="".concat(e,"?").concat(Date.now()),t.nextSibling?t.parentNode.insertBefore(a,t.nextSibling):t.parentNode.appendChild(a)}}function d(t){if(!t)return!1;var e=document.querySelectorAll("link"),a=!1;return s.call(e,(function(e){if(e.href){var n=function(t,e){var a;return t=i(t),e.some((function(i){t.indexOf(e)>-1&&(a=i)})),a}(e.href,t);l(n)&&!0!==e.visited&&n&&(o(e,n),a=!0)}})),a}function h(){var t=document.querySelectorAll("link");s.call(t,(function(t){!0!==t.visited&&o(t)}))}function l(t){return!!/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(t)}t.exports=function(t,e){if(r)return c;var a,s,o,l=function(t){var e=n[t];if(!e){if(document.currentScript)e=document.currentScript.src;else{var a=document.getElementsByTagName("script"),r=a[a.length-1];r&&(e=r.src)}n[t]=e}return function(t){if(!e)return null;var a=e.split(/([^\\/]+)\.js$/),n=a&&a[1];return n&&t?t.split(",").map((function(t){var a=new RegExp("".concat(n,"\\.js$"),"g");return i(e.replace(a,"".concat(t.replace(/{fileName}/g,n),".css")))})):[e.replace(".js",".css")]}}(t);return a=function(){var t=d(l(e.filename));e.locals?h():t||h()},s=50,o=0,function(){var t=this,e=arguments;clearTimeout(o),o=setTimeout((function(){return a.apply(t,e)}),s)}}},618:t=>{t.exports=function(t){if(t=t.trim(),/^data:/i.test(t))return t;var e=-1!==t.indexOf("//")?t.split("//")[0]+"//":"",a=t.replace(new RegExp(e,"i"),"").split("/"),i=a[0].toLowerCase().replace(/\.$/,"");return a[0]="",e+i+a.reduce((function(t,e){switch(e){case"..":t.pop();break;case".":break;default:t.push(e)}return t}),[]).join("/")}},488:(t,e,a)=>{var i=a(783)(t.id,{locals:!1});t.hot.dispose(i),t.hot.accept(void 0,i)},523:(t,e,a)=>{var i=a(783)(t.id,{locals:!1});t.hot.dispose(i),t.hot.accept(void 0,i)},991:(t,e,a)=>{var i=a(783)(t.id,{locals:!1});t.hot.dispose(i),t.hot.accept(void 0,i)},2:(t,e,a)=>{var i=a(783)(t.id,{locals:!1});t.hot.dispose(i),t.hot.accept(void 0,i)},492:(t,e,a)=>{var i=a(783)(t.id,{locals:!1});t.hot.dispose(i),t.hot.accept(void 0,i)},305:(t,e,a)=>{var i=a(783)(t.id,{locals:!1});t.hot.dispose(i),t.hot.accept(void 0,i)},444:(t,e,a)=>{var i=a(783)(t.id,{locals:!1});t.hot.dispose(i),t.hot.accept(void 0,i)},687:(t,e,a)=>{a(488),a(523),a(444);function i(t){t.preventDefault&&t.preventDefault()}function n(t){f(t).each((t=>{t.addEventListener("touchmove",i,{passive:!1}),t.addEventListener("mousemove",i,{passive:!1})}))}function r(t){if(null!==t.pageX&&void 0!==t.pageX)return{x:Math.round(t.pageX),y:Math.round(t.pageY)};let e;return t.changedTouches?e=t.changedTouches:t.targetTouches?e=t.targetTouches:t.originalEvent&&t.originalEvent.targetTouches&&(e=t.originalEvent.targetTouches),null!==e[0].pageX&&void 0!==e[0].pageX?{x:Math.round(e[0].pageX),y:Math.round(e[0].pageY)}:{x:Math.round(e[0].clientX),y:Math.round(e[0].clientY)}}function s(t,e){const a=r(e);let i=a.x,n=a.y;t.currentCaptchaData.startX=i,t.currentCaptchaData.startY=n;const s=t.currentCaptchaData.trackList;t.currentCaptchaData.startTime=new Date;const o=t.currentCaptchaData.startTime;s.push({x:a.x,y:a.y,type:"down",t:(new Date).getTime()-o.getTime()}),t.__m__=c.bind(null,t),t.__u__=d.bind(null,t),window.addEventListener("mousemove",t.__m__),window.addEventListener("mouseup",t.__u__),window.addEventListener("touchmove",t.__m__,!1),window.addEventListener("touchend",t.__u__,!1),t&&t.doDown&&t.doDown(e,t)}function c(t,e){e.touches&&e.touches.length>0&&(e=e.touches[0]);const a=r(e);let i=a.x,n=a.y;const s=t.currentCaptchaData.startX,c=t.currentCaptchaData.startY,o=t.currentCaptchaData.startTime,d=t.currentCaptchaData.end,h=(t.currentCaptchaData.bgImageWidth,t.currentCaptchaData.trackList);let l=i-s,p=n-c;const u={x:a.x,y:a.y,type:"move",t:(new Date).getTime()-o.getTime()};h.push(u),l<0?l=0:l>d&&(l=d),t.currentCaptchaData.moveX=l,t.currentCaptchaData.moveY=p,t.doMove&&t.doMove(e,t)}function o(t){t&&(t.__m__&&(window.removeEventListener("mousemove",t.__m__),window.removeEventListener("touchmove",t.__m__)),t.__u__&&(window.removeEventListener("mouseup",t.__u__),window.removeEventListener("touchend",t.__u__)))}function d(t,e){o(t);const a=r(e);t.currentCaptchaData.stopTime=new Date;const i=t.currentCaptchaData.startTime,n=t.currentCaptchaData.trackList,s={x:a.x,y:a.y,type:"up",t:(new Date).getTime()-i.getTime()};n.push(s),t.doUp&&t.doUp(e,t),t.endCallback(t.currentCaptchaData,t)}function h(t,e,a,i,n){const r={startTime:new Date,trackList:[],movePercent:0,clickCount:0,bgImageWidth:Math.round(t),bgImageHeight:Math.round(e),templateImageWidth:Math.round(a),templateImageHeight:Math.round(i),end:n};return r}function l(t,e){f(t).find("#tianai-captcha-tips").removeClass("tianai-captcha-tips-on"),e&&setTimeout(e,.35)}function p(t,e,a,i){const n=f(t).find("#tianai-captcha-tips");n.text(e),1===a?(n.removeClass("tianai-captcha-tips-error"),n.addClass("tianai-captcha-tips-success")):(n.removeClass("tianai-captcha-tips-success"),n.addClass("tianai-captcha-tips-error")),n.addClass("tianai-captcha-tips-on"),setTimeout(i,1e3)}class u{showTips(t,e,a){p(this.el,t,e,a)}closeTips(t,e){l(this.el,t)}}function f(t,e){return new m(t,e)}class m{constructor(t,e){if(e&&"object"==typeof e&&void 0!==e.nodeType)return this.dom=e,void(this.domStr=t);if(t instanceof m)this.dom=t.dom,this.domStr=t.domStr;else if("string"==typeof t)this.dom=document.querySelector(t),this.domStr=t;else{if("object"!=typeof document||void 0===document.nodeType)throw new Error("不支持的类型");this.dom=t,this.domStr=t.nodeName}}each(t){this.getTarget().querySelectorAll("*").forEach(t)}removeClass(t){let e=this.getTarget();if(e.classList)e.classList.remove(t);else{const a=e.className,i=new RegExp("(?:^|\\s)"+t+"(?!\\S)","g");e.className=a.replace(i,"")}return this}addClass(t){const e=this.getTarget();if(e.classList)e.classList.add(t);else{let a=e.className;-1===a.indexOf(t)&&(e.className=a+" "+t)}return this}find(t){const e=this.getTarget().querySelector(t);return e?new m(t,e):null}children(t){const e=this.getTarget().childNodes;for(let a=0;a\n
\n ${t.i18n.slider_title}\n
\n
\n
\n \n \n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n\n\n`}(this.styleConfig)),this.el=this.boxEl.find("#tianai-captcha"),this.loadStyle(),this.el.find("#tianai-captcha-slider-move-btn").mousedown(s.bind(null,this)),this.el.find("#tianai-captcha-slider-move-btn").touchstart(s.bind(null,this)),this.loadCaptchaForData(this,t),this.endCallback=e,a&&a(this),this}showTips(t,e,a){p(this.el,t,e,a)}closeTips(t){l(this.el,t)}destroy(){const t=this.boxEl.children("#tianai-captcha");t&&t.remove(),o()}doMove(){const t=this.currentCaptchaData.moveX;this.el.find("#tianai-captcha-slider-move-btn").css("transform","translate("+t+"px, 0px)"),this.el.find("#tianai-captcha-slider-img-div").css("transform","translate("+t+"px, 0px)"),this.el.find("#tianai-captcha-slider-move-track-mask").css("width",t+"px")}loadStyle(){let t="",e="#00f4ab",a="#a9ffe5";const i=this.styleConfig;i&&(t=i.btnUrl,a=i.moveTrackMaskBgColor,e=i.moveTrackMaskBorderColor),this.el.find(".slider-move .slider-move-btn").css("background-image","url("+t+")"),this.el.find("#tianai-captcha-slider-move-track-mask").css("border-color",e),this.el.find("#tianai-captcha-slider-move-track-mask").css("background-color",a)}loadCaptchaForData(t,e){const a=t.el.find("#tianai-captcha-slider-bg-img"),i=t.el.find("#tianai-captcha-slider-move-img");a.attr("src",e.data.backgroundImage),i.attr("src",e.data.templateImage),a.on("load",(()=>{t.currentCaptchaData=h(a.width(),a.height(),i.width(),i.height(),242),t.currentCaptchaData.currentCaptchaId=e.data.id}))}};a(305);const g=class extends u{constructor(t,e){super(),this.boxEl=t,this.styleConfig=e,this.type="ROTATE",this.currentCaptchaData={}}init(t,e,a){return this.destroy(),this.boxEl.append(function(t){return`\n
\n
\n ${t.i18n.rotate_title}\n
\n
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n`}(this.styleConfig)),this.el=this.boxEl.find("#tianai-captcha"),this.loadStyle(),this.el.find("#tianai-captcha-slider-move-btn").mousedown(s.bind(null,this)),this.el.find("#tianai-captcha-slider-move-btn").touchstart(s.bind(null,this)),this.loadCaptchaForData(this,t),this.endCallback=e,a&&a(this),this}destroy(){const t=this.boxEl.children("#tianai-captcha");t&&t.remove(),o()}doMove(){const t=this.currentCaptchaData.moveX;this.el.find("#tianai-captcha-slider-move-btn").css("transform","translate("+t+"px, 0px)"),this.el.find("#tianai-captcha-slider-move-img").css("transform","rotate("+t/(this.currentCaptchaData.end/360)+"deg)"),this.el.find("#tianai-captcha-slider-move-track-mask").css("width",t+"px")}loadStyle(){let t="",e="#00f4ab",a="#a9ffe5";const i=this.styleConfig;i&&(t=i.btnUrl,a=i.moveTrackMaskBgColor,e=i.moveTrackMaskBorderColor),this.el.find(".slider-move .slider-move-btn").css("background-image","url("+t+")"),this.el.find("#tianai-captcha-slider-move-track-mask").css("border-color",e),this.el.find("#tianai-captcha-slider-move-track-mask").css("background-color",a)}loadCaptchaForData(t,e){const a=t.el.find("#tianai-captcha-slider-bg-img"),i=t.el.find("#tianai-captcha-slider-move-img");a.attr("src",e.data.backgroundImage),i.attr("src",e.data.templateImage),a.on("load",(()=>{t.currentCaptchaData=h(a.width(),a.height(),i.width(),i.height(),242),t.currentCaptchaData.currentCaptchaId=e.data.id}))}};a(991);const b=class extends u{constructor(t,e){super(),this.boxEl=f(t),this.styleConfig=e,this.type="CONCAT",this.currentCaptchaData={}}init(t,e,a){return this.destroy(),this.boxEl.append((this.styleConfig,'\n
\n
\n 拖动滑块完成拼图\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n ')),this.el=this.boxEl.find("#tianai-captcha"),this.loadStyle(),this.el.find("#tianai-captcha-slider-move-btn").mousedown(s.bind(null,this)),this.el.find("#tianai-captcha-slider-move-btn").touchstart(s.bind(null,this)),n(this.el),window.currentCaptcha=this,this.loadCaptchaForData(this,t),this.endCallback=e,a&&a(this),this}destroy(){o();const t=this.boxEl.children("#tianai-captcha");t&&t.remove()}doMove(){const t=this.currentCaptchaData.moveX;this.el.find("#tianai-captcha-slider-move-btn").css("transform","translate("+t+"px, 0px)"),this.el.find("#tianai-captcha-slider-concat-img-div").css("background-position-x",t+"px"),this.el.find("#tianai-captcha-slider-move-track-mask").css("width",t+"px")}loadStyle(){let t="",e="#00f4ab",a="#a9ffe5";const i=this.styleConfig;i&&(t=i.btnUrl,a=i.moveTrackMaskBgColor,e=i.moveTrackMaskBorderColor),this.el.find(".slider-move .slider-move-btn").css("background-image","url("+t+")"),this.el.find("#tianai-captcha-slider-move-track-mask").css("border-color",e),this.el.find("#tianai-captcha-slider-move-track-mask").css("background-color",a)}loadCaptchaForData(t,e){const a=t.el.find(".tianai-captcha-slider-concat-bg-img"),i=t.el.find("#tianai-captcha-slider-concat-img-div");a.css("background-image","url("+e.data.backgroundImage+")"),i.css("background-image","url("+e.data.backgroundImage+")"),i.css("background-position","0px 0px");var n=e.data.backgroundImageHeight,r=(n-e.data.data.randomY)/n*180;i.css("height",r+"px"),t.currentCaptchaData=h(a.width(),a.height(),i.width(),i.height(),242),t.currentCaptchaData.currentCaptchaId=e.data.id}};a(2);const C=class{constructor(t,e){this.boxEl=t,this.styleConfig=e,this.type="DISABLE",this.currentCaptchaData={}}init(t,e,a){return this.destroy(),this.boxEl.append(function(t){return`\n
\n
\n ${t.i18n.disable_title}\n
\n
\n
\n\x3c!-- --\x3e\n\x3c!-- --\x3e\n\x3c!-- --\x3e\n\x3c!-- --\x3e\n \n
\n
\n
\n `}(this.styleConfig)),this.el=this.boxEl.find("#tianai-captcha"),this.loadCaptchaForData(this,t),this.endCallback=e,a&&a(this),this}destroy(){const t=this.boxEl.find("#tianai-captcha");t&&t.remove()}loadCaptchaForData(t,e){const a=e.msg||e.message||"接口异常";t.el.find("#content-span").text(a)}};a(492);const y=class extends u{constructor(t,e){super(),this.boxEl=t,this.styleConfig=e,this.type="IMAGE_CLICK",this.currentCaptchaData={}}init(t,e,a){this.destroy(),this.boxEl.append(function(t){return`\n
\n
\n ${t.i18n.image_click_title}\n \n
\n
\n
\n \n \n
\n
\n
\n
\n
确定
\n
\n`}(this.styleConfig)),this.el=this.boxEl.find("#tianai-captcha"),this.loadCaptchaForData(this,t),this.endCallback=e;const i=c.bind(null,this);return this.el.find("#bg-img-click-mask").click((t=>{if("click-span"===t.target.className)return;this.currentCaptchaData.clickCount++;const e=this.currentCaptchaData.trackList;1===this.currentCaptchaData.clickCount&&(this.currentCaptchaData.startTime=new Date,window.addEventListener("mousemove",i),this.currentCaptchaData.startX=t.offsetX,this.currentCaptchaData.startY=t.offsetY);const a=this.currentCaptchaData.startTime;e.push({x:Math.round(t.offsetX),y:Math.round(t.offsetY),type:"click",t:(new Date).getTime()-a.getTime()});const n=t.offsetX-10,r=t.offsetY-10;this.el.find("#bg-img-click-mask").append(""+this.currentCaptchaData.clickCount+"")})),this.el.find(".click-confirm-btn").click((()=>{this.currentCaptchaData.clickCount>0&&(this.currentCaptchaData.stopTime=new Date,window.removeEventListener("mousemove",i),this.endCallback(this.currentCaptchaData,this))})),a&&a(this),this}destroy(){const t=this.boxEl.children("#tianai-captcha");t&&t.remove(),o()}loadCaptchaForData(t,e){const a=t.el.find("#tianai-captcha-slider-bg-img"),i=t.el.find("#tianai-captcha-tip-img");a.on("load",(()=>{t.currentCaptchaData=h(a.width(),a.height(),i.width(),i.height()),t.currentCaptchaData.currentCaptchaId=e.data.id})),a.attr("src",e.data.backgroundImage),i.attr("src",e.data.templateImage)}};const w=class extends y{constructor(t,e){super(t,e),this.type="WORD_IMAGE_CLICK"}},k={btnUrl:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIwAAABkCAYAAABU19jRAAAJcUlEQVR4nO2d63MT1xmHf9rV6mr5fgNMuSW+ENsY8N0EE2BMhinJNB8y/dD2Qz/0v+gMf0w/JHTKNJAhICwbsA02TpNAHEMgQIwNBSEb8F2rvXTeY1kjYyA+TmVJmfeZ8YiRWa9299E57/mdI63Dtm3E+RjAKTDMaj4F8AU9uyzMCQBn+EQxb+EjAF+RMH8AcJrPFLMGvCSMzWeKWSN/I2GiAFx8xpi1oPBZYiTQWRhGChaGkYKFYaRgYRgpWBhGChaGkYKFYaRgYRgpWBhGChaGkYKFYaRgYRgpWBhGChaGkYKFYaRgYRgpWBhGChaGkYKFYaRgYRgpWBhGChaGkYKFYaRgYRgpWBhGCiefrtShGwZiup74+4qqwu12Z/W7lIVJEfN6FDfv3sPXfYOIRRfpm1UQKC7EkQ+PYFtRcdZKw8KkiLsPJ/CfgSFcH7yOxWhU7MSluYQoR44fxdaCoqyUhoVJEfZ8FN99c1N0Sx6PR+zEMAz0XAgBNtB14hi25OXDkWXHxUVvinA4ln6ScTqdsGwbvRd7EPwyiEcvXyDbvpyHhUkRaq4fe/c3wEWSWFZiJySNYZroCYYQPHsBY1OTWSWNevLkyb/TYwa8lt8UAb8ftluDW9UwPj4hDs0Rb3JUVRXd09j9nwELKKgoR4HXlw2Hb3INkyK8mob9NdUwLROq4sCVKwMrdqRpGkzTFN0TaWR2HcKu0rKMr2lYmBTi1jS01dUt7UBx4PKlfvHP5JaGuqseIY0DjmOHsKukNKOPiYVJMU5VRXt9PSwboO+fvHJ5QEiiKEvlIz3S86HuHiiqAhw9iJ0lpRnb0rAwG4CqKHh/Tz0UhwOWaWGg/5oofEkmJLU4wfPdQia765CQJhNHJCzMBkEtSVtdLRw2YNo2hgaGEDMMMWpahrwJBUMUCkM9djgjE2EWZgOhFqW5rlbMKdm2heHBYUT1mCiAEW9pKKfpPh8Sj5mYCLMwG4zLqWJfTZWQgL5S++uhYURjBrR4S0MtUSYnwixMGvBoGvZUV4quh0S4Pjgsaho1XtOIcM8wxJCb+qmu33dljDS/CWEeTb/E/Pw89EUdebkBVBQWrnnbWVjQoMAtsT9asGDQhf8VUbnX5UJ9VaVoZahVuXZ1cMXoiaSJxWIiEab/dPj4UXFczjRrk/VJ70/hp/jhuxF89o9TGP1+FH6fD9OxGHw5Pnicb34/PJ2dweitu7hwLojvb47A9rhQmJeXGLm8iQeP/4uRH27h88/+iZhhYs40UFZQsK7XrqkqigvyYbk18VrHH74+EX74YAzRqI66mupE15UmzKwW5kEkgtFvRxA8ex7hJ2HMzczgzu0f8fjxExRt2YzcgB9udfUJjuo6Tv/7HE6f+pe4GHd//AkwLRhuDeXFRW+U5v7EI4yMjKI3GMLt0Tt4cO8BAoEcWJoTZYXrl6asqBC6U0GOy42HY+MrZi1JmoWFRZQW5sNyuVBeUpxOabJ7aiASjiB4/iKmnj+H5loaacwvLOL2jRF4AjnY8dc/I/DKbTdoSHvr8SO8DD/DzPSMWHrg1JwYvHZdpK2NVZWU26/aF3VDTyLP0N/bh4mJR3C7XZiZnRVdht/nx7u7tsOzzg5qORFWHAocigO9vX2Jronwej24cXMEbq8XrfW169rH/4usnq02o1FEo9FEE47luN22sTAzC0OPrd7ItnHn9h0MDg3D6/WKbZdHJqYRg26ar92XDgvD39zA2Ng4VKdTbEf7mpmeRX/fAPRfeRch+luNNTXICeSu+h3ti7okUzdgp3luO6uFUTUN9lLmnniOCkdKVnML8uB0r76rD72Di4qL4NI0IUnydpZlw/WmGsY00bRvDzZvKhfFKLAU9VOG8v7BdijW+i8kLX649yyMz0+fwVQksur3NILyejzw5efCoaT3kmW1MN68AMq2bBIXXtd18WMZBt6r242DBzvgda3uWhQ4xNzOkeNdohZYXFjA4vwCfD4/Sio2i9bjdeSoGirKylFYXirykehiFHpUR2FJCbZu+x1yXlMrrQWSZWwygv6Ll3DxXBCX+66u6I7o2DRFRWtbM1o62xNdb7rI7lGSqqBs+zZMTj4XLYY/x49t7+zABx8eReWO7ciLL41ctZmqoqRiE/x+P6amp5FbkI9jx7tw+GgncqmbesPuPAEfduzcgenZOTg0FaWby/GXP/0RdZXvrOvlkyzjzyfR81UIoQs9IpRJniqglszt0tDc1oS9bc2o37lTLMhKI2bW35HtRXQRs3MLmH/xUrzzVb8HJQUFyHX/crJCQ+JwOALFqaKspGjNRWtkbg5zc7PQXC5szl/f6Ig6MFqiSavuqHCmumuFLIYBt+ZEY0sTGtua0VBTJQK/NKPzLfzSQEKWL4NiiG5a1gpZzPhMdnNrE/a3N2NPVaUI+jIAnacGNhiShdbx9pzrFgunSA4tqeUQRbuqoLW9BQ0tjSINzhBZBCzMBvPzVAS950KiG6KWJVkWGnXRELrjQBtqG/eioTqzZAELs3FQy3Iv/BR9wUtiUtGOr+tNhoptGt1V7atD4+4aEehlGizMBnH/WRj9wcuiG7LjI7Vllm8d3nnoAKoaakXq+0tzWumChUkxdlyWge4rYt0uzRMpSck01SzUDR3s7MC7e2pFRqSmOZx7GyxMCrESLcsldAd7oCgrEx6xrldRRM1SvbceHfV1K0K7TISFSREx28L41KRIcGmdruOVz82KBFd1oqWjBe/tb0ArLd3McFnAwqSOiclJ9JwP4fLFXtEtJXdDywluU2uTGDpTgZupNcur8GerU8R0eBJDV6+LRVbJLYdIcF2aSHD3tzaL9b20zjdbYGFShB0z4HY6V9QtFNLRXFATxf2U4FZXZkLcLwULkyJoaUXMNMV6HbyS4O6jicQMS3DXCguTInJKC9HU0YoPOg8k1uy0t7eivnmfSHB9WSgLwZOPKcKwLcT0GL69cxe3b46KoK6+ZS92V2zNyAR3jfBsdaox6LPSpiVyf/rEo/rq11JlFzxbnWoomEMW5CtrhWsYRgoWhpGChWGkYGEYKVgYRgoWhpGChWGkYGEYKVgYRgoWhpGChWGkYGEYKVgYRgoWhpGChWGkYGEYKVgYRgoWhpGChWGkYGEYKVgYRgoWhpGChWGkYGEYKVgYRgr6qGx6b4/BZBXUwnzCl4xZI5844g3MCQBn+Kwxb+EjAGcdST3SxwBO8RljXsOnAL4AgP8BXnVIgIvemwsAAAAASUVORK5CYII=",moveTrackMaskBgColor:"#89d2ff",moveTrackMaskBorderColor:"#0298f8",i18n:{tips_success:"验证成功,耗时%s秒",tips_error:"验证失败,请重新尝试!",slider_title:"拖动滑块完成拼图",concat_title:"拖动滑块完成拼图",image_click_title:"请依次点击:",rotate_title:"拖动滑块完成拼图",slider_title_size:"15px",concat_title_size:"15px",image_click_title_size:"20px",rotate_title_size:"15px"}};class E{constructor(t){if(!t.bindEl)throw new Error("[TAC] 必须配置 [bindEl]用于将验证码绑定到该元素上");if(!t.requestCaptchaDataUrl)throw new Error("[TAC] 必须配置 [requestCaptchaDataUrl]请求验证码接口");if(!t.validCaptchaUrl)throw new Error("[TAC] 必须配置 [validCaptchaUrl]验证验证码接口");this.bindEl=t.bindEl,this.domBindEl=f(t.bindEl),this.requestCaptchaDataUrl=t.requestCaptchaDataUrl,this.validCaptchaUrl=t.validCaptchaUrl,t.validSuccess&&(this.validSuccess=t.validSuccess),t.validFail&&(this.validFail=t.validFail),t.requestHeaders?this.requestHeaders=t.requestHeaders:this.requestHeaders={},t.btnCloseFun&&(this.btnCloseFun=t.btnCloseFun),t.btnRefreshFun&&(this.btnRefreshFun=t.btnRefreshFun),this.requestChain=[],this.timeToTimestamp=t.timeToTimestamp||!0,this.insertRequestChain(0,{preRequest(t,e,a,i){if(this.timeToTimestamp&&e.data)for(let t in e.data)e.data[t]instanceof Date&&(e.data[t]=e.data[t].getTime());return!0}})}addRequestChain(t){this.requestChain.push(t)}insertRequestChain(t,e){this.requestChain.splice(t,0,e)}removeRequestChain(t){this.requestChain.splice(t,1)}requestCaptchaData(){const t={};t.headers=this.requestHeaders||{},t.data={},t.headers["Content-Type"]="application/json;charset=UTF-8",t.method="POST",t.url=this.requestCaptchaDataUrl,this._preRequest("requestCaptchaData",t);return this.doSendRequest(t).then((e=>(this._postRequest("requestCaptchaData",t,e),e)))}doSendRequest(t){if(t.headers)for(const e in t.headers)if(t.headers[e].indexOf("application/json")>-1){"string"!=typeof t.data&&(t.data=JSON.stringify(t.data));break}return(e=t,new Promise((function(t,a){var i=new XMLHttpRequest;if(i.open(e.method||"GET",e.url),e.headers)for(const t in e.headers)e.headers.hasOwnProperty(t)&&i.setRequestHeader(t,e.headers[t]);i.onreadystatechange=function(){if(i.readyState===XMLHttpRequest.DONE)if(i.status>=200&&i.status<=500){const e=i.getResponseHeader("Content-Type");e&&-1!==e.indexOf("application/json")?t(JSON.parse(i.responseText)):t(i.responseText)}else a(new Error("Request failed with status: "+i.status))},i.onerror=function(){a(new Error("Network Error"))},i.send(e.data)}))).then((t=>{try{return JSON.parse(t)}catch(e){return t}}));var e}_preRequest(t,e,a,i){for(let n=0;n(this._postRequest("validCaptcha",r,t,a,i),t))).then((t=>{if(200==t.code){const n=(e.stopTime-e.startTime)/1e3;a.showTips(`验证成功,耗时${n}秒`,1,(()=>this.validSuccess(t,a,i)))}else{let e="验证失败,请重新尝试!";t.code&&4001!=t.code&&(e="验证码被黑洞吸走了!"),a.showTips(e,0,(()=>this.validFail(t,a,i)))}})).catch((t=>{let e=a.styleConfig.i18n.tips_error;t.code&&200!=t.code&&(4001!=res.code&&(e=a.styleConfig.i18n.tips_4001),a.showTips(e,0,(()=>this.validFail(res,a,i))))}))}validSuccess(t,e,a){window.currentCaptchaRes=t,a.destroyWindow()}validFail(t,e,a){a.reloadCaptcha()}}window.TAC=class{constructor(t,e){this.config=function(t){return t instanceof E?t:new E(t)}(t),this.config.btnRefreshFun&&(this.btnRefreshFun=this.config.btnRefreshFun),this.config.btnCloseFun&&(this.btnCloseFun=this.config.btnCloseFun),this.style=function(t){let e={...k,...t};return e.i18n={...k.i18n,...t?.i18n},e}(e)}init(){return this.destroyWindow(),this.config.domBindEl.append('\n
\n
\n
\n
\n
\n \x3c!-- 底部 --\x3e\n
\n \n
\n
\n
\n
\n '),this.domTemplate=this.config.domBindEl.find("#tianai-captcha-parent"),n(this.domTemplate),this.loadStyle(),this.config.domBindEl.find("#tianai-captcha-slider-refresh-btn").click((t=>{this.btnRefreshFun(t,this)})),this.config.domBindEl.find("#tianai-captcha-slider-close-btn").click((t=>{this.btnCloseFun(t,this)})),this.reloadCaptcha(),this}btnRefreshFun(t,e){e.reloadCaptcha()}btnCloseFun(t,e){e.destroyWindow()}reloadCaptcha(){this.showLoading(),this.destroyCaptcha((()=>{this.createCaptcha()}))}showLoading(){this.config.domBindEl.find("#tianai-captcha-loading").css("display","block")}closeLoading(){this.config.domBindEl.find("#tianai-captcha-loading").css("display","none")}loadStyle(){const t=this.style.bgUrl,e=this.style.logoUrl;t&&this.config.domBindEl.find("#tianai-captcha-bg-img").css("background-image","url("+t+")"),e&&""!==e?this.config.domBindEl.find("#tianai-captcha-logo").attr("src",e):null===e&&this.config.domBindEl.find("#tianai-captcha-logo").css("display","none")}destroyWindow(){this.C&&(this.C.destroy(),this.C=void 0),this.domTemplate&&this.domTemplate.remove()}openCaptcha(){setTimeout((()=>{this.C.el.css("transform","translateX(0)")}),10)}createCaptcha(){this.config.requestCaptchaData().then((t=>{if(this.closeLoading(),!t.code)throw new Error("[TAC] 后台验证码接口数据错误!!!");let e=200===t.code?t.data?.type:"DISABLED";const a=function(t,e){const a=e.config.domBindEl.find("#tianai-captcha-box"),i=e.style;switch(t){case"SLIDER":return new v(a,i);case"ROTATE":return new g(a,i);case"CONCAT":return new b(a,i);case"WORD_IMAGE_CLICK":return new w(a,i);case"DISABLED":return new C(a,i);default:return null}}(e,this);if(null==a)throw new Error("[TAC] 未知的验证码类型["+e+"]");a.init(t,((t,e)=>{const a=e.currentCaptchaData,i={bgImageWidth:a.bgImageWidth,bgImageHeight:a.bgImageHeight,templateImageWidth:a.templateImageWidth,templateImageHeight:a.templateImageHeight,startTime:a.startTime.getTime(),stopTime:a.stopTime.getTime(),trackList:a.trackList};"ROTATE_DEGREE"!==e.type&&"ROTATE"!==e.type||(i.bgImageWidth=e.currentCaptchaData.end),a.data&&(i.data=a.data);const n=e.currentCaptchaData.currentCaptchaId;e.currentCaptchaData=void 0,this.config.validCaptcha(n,i,e,this)})),this.C=a,this.openCaptcha()}))}destroyCaptcha(t){this.C?(this.C.el.css("transform","translateX(300px)"),setTimeout((()=>{this.C.destroy(),t&&t()}),500)):t()}},window.CaptchaConfig=E}},i={};function n(t){var e=i[t];if(void 0!==e){if(void 0!==e.error)throw e.error;return e.exports}var r=i[t]={id:t,exports:{}};try{var s={id:t,module:r,factory:a[t],require:n};n.i.forEach((function(t){t(s)})),r=s.module,s.factory.call(r.exports,r,r.exports,s.require)}catch(t){throw r.error=t,t}return r.exports}n.m=a,n.c=i,n.i=[],n.hu=t=>t+"."+n.h()+".hot-update.js",n.miniCssF=t=>{},n.hmrF=()=>"main."+n.h()+".hot-update.json",n.h=()=>"7ca661d8917e051f770d",n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),t={},e="webpack-demo:",n.l=(a,i,r,s)=>{if(t[a])t[a].push(i);else{var c,o;if(void 0!==r)for(var d=document.getElementsByTagName("script"),h=0;h{c.onerror=c.onload=null,clearTimeout(u);var n=t[a];if(delete t[a],c.parentNode&&c.parentNode.removeChild(c),n&&n.forEach((t=>t(i))),e)return e(i)},u=setTimeout(p.bind(null,void 0,{type:"timeout",target:c}),12e4);c.onerror=p.bind(null,c.onerror),c.onload=p.bind(null,c.onload),o&&document.head.appendChild(c)}},(()=>{var t,e,a,i={},r=n.c,s=[],c=[],o="idle",d=0,h=[];function l(t){o=t;for(var e=[],a=0;a0)return l("abort").then((function(){throw n[0]}));var r=l("dispose");i.forEach((function(t){t.dispose&&t.dispose()}));var s,c=l("apply"),o=function(t){s||(s=t)},d=[];return i.forEach((function(t){if(t.apply){var e=t.apply(o);if(e)for(var a=0;a=0&&b._disposeHandlers.splice(e,1)},invalidate:function(){switch(this._selfInvalidated=!0,o){case"idle":e=[],Object.keys(n.hmrI).forEach((function(t){n.hmrI[t](m,e)})),l("ready");break;case"ready":Object.keys(n.hmrI).forEach((function(t){n.hmrI[t](m,e)}));break;case"prepare":case"check":case"dispose":case"apply":(a=a||[]).push(m)}},check:u,apply:f,status:function(t){if(!t)return o;c.push(t)},addStatusHandler:function(t){c.push(t)},removeStatusHandler:function(t){var e=c.indexOf(t);e>=0&&c.splice(e,1)},data:i[m]},t=void 0,b),C.parents=s,C.children=[],s=[],h.require=y})),n.hmrC={},n.hmrI={}})(),(()=>{var t;n.g.importScripts&&(t=n.g.location+"");var e=n.g.document;if(!t&&e&&(e.currentScript&&(t=e.currentScript.src),!t)){var a=e.getElementsByTagName("script");if(a.length)for(var i=a.length-1;i>-1&&!t;)t=a[i--].src}if(!t)throw new Error("Automatic publicPath is not supported in this browser");t=t.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),n.p=t+"../../"})(),(()=>{if("undefined"!=typeof document){var t=(t,e,a,i,n)=>{var r=document.createElement("link");r.rel="stylesheet",r.type="text/css";return r.onerror=r.onload=a=>{if(r.onerror=r.onload=null,"load"===a.type)i();else{var s=a&&("load"===a.type?"missing":a.type),c=a&&a.target&&a.target.href||e,o=new Error("Loading CSS chunk "+t+" failed.\n("+c+")");o.code="CSS_CHUNK_LOAD_FAILED",o.type=s,o.request=c,r.parentNode&&r.parentNode.removeChild(r),n(o)}},r.href=e,a?a.parentNode.insertBefore(r,a.nextSibling):document.head.appendChild(r),r},e=(t,e)=>{for(var a=document.getElementsByTagName("link"),i=0;i({dispose:()=>{for(var t=0;t{for(var t=0;t{h.push(r),s.forEach((r=>{var s=n.miniCssF(r),c=n.p+s,o=e(s,c);o&&d.push(new Promise(((e,n)=>{var s=t(r,c,o,(()=>{s.as="style",s.rel="preload",e()}),n);a.push(o),i.push(s)})))}))}}})(),(()=>{var t,e,a,i,r,s=n.hmrS_jsonp=n.hmrS_jsonp||{179:0},c={};function o(e,a){return t=a,new Promise(((t,a)=>{c[e]=t;var i=n.p+n.hu(e),r=new Error;n.l(i,(t=>{if(c[e]){c[e]=void 0;var i=t&&("load"===t.type?"missing":t.type),n=t&&t.target&&t.target.src;r.message="Loading hot update chunk "+e+" failed.\n("+i+": "+n+")",r.name="ChunkLoadError",r.type=i,r.request=n,a(r)}}))}))}function d(t){function c(t){for(var e=[t],a={},i=e.map((function(t){return{chain:[t],id:t}}));i.length>0;){var r=i.pop(),s=r.id,c=r.chain,d=n.c[s];if(d&&(!d.hot._selfAccepted||d.hot._selfInvalidated)){if(d.hot._selfDeclined)return{type:"self-declined",chain:c,moduleId:s};if(d.hot._main)return{type:"unaccepted",chain:c,moduleId:s};for(var h=0;h ")),f.type){case"self-declined":t.onDeclined&&t.onDeclined(f),t.ignoreDeclined||(v=new Error("Aborted because of self decline: "+f.moduleId+C));break;case"declined":t.onDeclined&&t.onDeclined(f),t.ignoreDeclined||(v=new Error("Aborted because of declined dependency: "+f.moduleId+" in "+f.parentId+C));break;case"unaccepted":t.onUnaccepted&&t.onUnaccepted(f),t.ignoreUnaccepted||(v=new Error("Aborted because "+u+" is not accepted"+C));break;case"accepted":t.onAccepted&&t.onAccepted(f),g=!0;break;case"disposed":t.onDisposed&&t.onDisposed(f),b=!0;break;default:throw new Error("Unexception type "+f.type)}if(v)return{error:v};if(g)for(u in l[u]=m,o(h,f.outdatedModules),f.outdatedDependencies)n.o(f.outdatedDependencies,u)&&(d[u]||(d[u]=[]),o(d[u],f.outdatedDependencies[u]));b&&(o(h,[f.moduleId]),l[u]=p)}a=void 0;for(var y,w=[],k=0;k0;){var r=a.pop(),c=n.c[r];if(c){var o={},l=c.hot._disposeHandlers;for(k=0;k=0&&p.parents.splice(t,1))}}}for(var u in d)if(n.o(d,u)&&(c=n.c[u]))for(y=d[u],k=0;k=0&&c.children.splice(t,1)},apply:function(e){for(var a in l)n.o(l,a)&&(n.m[a]=l[a]);for(var i=0;i{for(var o in i)n.o(i,o)&&(a[o]=i[o],t&&t.push(o));s&&r.push(s),c[e]&&(c[e](),c[e]=void 0)},n.hmrI.jsonp=function(t,e){a||(a={},r=[],i=[],e.push(d)),n.o(a,t)||(a[t]=n.m[t])},n.hmrC.jsonp=function(t,c,h,l,p,u){p.push(d),e={},i=c,a=h.reduce((function(t,e){return t[e]=!1,t}),{}),r=[],t.forEach((function(t){n.o(s,t)&&void 0!==s[t]?(l.push(o(t,u)),e[t]=!0):e[t]=!1})),n.f&&(n.f.jsonpHmr=function(t,a){e&&n.o(e,t)&&!e[t]&&(a.push(o(t)),e[t]=!0)})},n.hmrM=()=>{if("undefined"==typeof fetch)throw new Error("No browser support: need fetch API");return fetch(n.p+n.hmrF()).then((t=>{if(404!==t.status){if(!t.ok)throw new Error("Failed to fetch update manifest "+t.statusText);return t.json()}}))}})();n(687)})(); diff --git a/app/src/adapter/component/index.ts b/app/src/adapter/component/index.ts new file mode 100644 index 0000000..25d2fad --- /dev/null +++ b/app/src/adapter/component/index.ts @@ -0,0 +1,331 @@ +/** + * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用 + * 可用于 aiflowy-form、aiflowy-modal、aiflowy-drawer 等组件使用, + */ + +import type { Component } from 'vue'; + +import type { BaseFormComponentType } from '@aiflowy/common-ui'; +import type { Recordable } from '@aiflowy/types'; + +import { defineAsyncComponent, defineComponent, h, ref } from 'vue'; + +import { ApiComponent, globalShareState, IconPicker } from '@aiflowy/common-ui'; +import { $t } from '@aiflowy/locales'; + +import { ElNotification } from 'element-plus'; + +const ElButton = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/button/index'), + import('element-plus/es/components/button/style/css'), + ]).then(([res]) => res.ElButton), +); +const ElCheckbox = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/checkbox/index'), + import('element-plus/es/components/checkbox/style/css'), + ]).then(([res]) => res.ElCheckbox), +); +const ElCheckboxButton = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/checkbox/index'), + import('element-plus/es/components/checkbox-button/style/css'), + ]).then(([res]) => res.ElCheckboxButton), +); +const ElCheckboxGroup = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/checkbox/index'), + import('element-plus/es/components/checkbox-group/style/css'), + ]).then(([res]) => res.ElCheckboxGroup), +); +const ElDatePicker = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/date-picker/index'), + import('element-plus/es/components/date-picker/style/css'), + ]).then(([res]) => res.ElDatePicker), +); +const ElDivider = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/divider/index'), + import('element-plus/es/components/divider/style/css'), + ]).then(([res]) => res.ElDivider), +); +const ElInput = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/input/index'), + import('element-plus/es/components/input/style/css'), + ]).then(([res]) => res.ElInput), +); +const ElInputNumber = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/input-number/index'), + import('element-plus/es/components/input-number/style/css'), + ]).then(([res]) => res.ElInputNumber), +); +const ElRadio = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/radio/index'), + import('element-plus/es/components/radio/style/css'), + ]).then(([res]) => res.ElRadio), +); +const ElRadioButton = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/radio/index'), + import('element-plus/es/components/radio-button/style/css'), + ]).then(([res]) => res.ElRadioButton), +); +const ElRadioGroup = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/radio/index'), + import('element-plus/es/components/radio-group/style/css'), + ]).then(([res]) => res.ElRadioGroup), +); +const ElSelectV2 = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/select-v2/index'), + import('element-plus/es/components/select-v2/style/css'), + ]).then(([res]) => res.ElSelectV2), +); +const ElSpace = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/space/index'), + import('element-plus/es/components/space/style/css'), + ]).then(([res]) => res.ElSpace), +); +const ElSwitch = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/switch/index'), + import('element-plus/es/components/switch/style/css'), + ]).then(([res]) => res.ElSwitch), +); +const ElTimePicker = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/time-picker/index'), + import('element-plus/es/components/time-picker/style/css'), + ]).then(([res]) => res.ElTimePicker), +); +const ElTreeSelect = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/tree-select/index'), + import('element-plus/es/components/tree-select/style/css'), + ]).then(([res]) => res.ElTreeSelect), +); +const ElUpload = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/upload/index'), + import('element-plus/es/components/upload/style/css'), + ]).then(([res]) => res.ElUpload), +); + +const withDefaultPlaceholder = ( + component: T, + type: 'input' | 'select', + componentProps: Recordable = {}, +) => { + return defineComponent({ + name: component.name, + inheritAttrs: false, + setup: (props: any, { attrs, expose, slots }) => { + const placeholder = + props?.placeholder || + attrs?.placeholder || + $t(`ui.placeholder.${type}`); + // 透传组件暴露的方法 + const innerRef = ref(); + expose( + new Proxy( + {}, + { + get: (_target, key) => innerRef.value?.[key], + has: (_target, key) => key in (innerRef.value || {}), + }, + ), + ); + return () => + h( + component, + { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef }, + slots, + ); + }, + }); +}; + +// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 +export type ComponentType = + | 'ApiSelect' + | 'ApiTreeSelect' + | 'Checkbox' + | 'CheckboxGroup' + | 'DatePicker' + | 'Divider' + | 'IconPicker' + | 'Input' + | 'InputNumber' + | 'RadioGroup' + | 'Select' + | 'Space' + | 'Switch' + | 'TimePicker' + | 'TreeSelect' + | 'Upload' + | BaseFormComponentType; + +async function initComponentAdapter() { + const components: Partial> = { + // 如果你的组件体积比较大,可以使用异步加载 + // Button: () => + // import('xxx').then((res) => res.Button), + ApiSelect: withDefaultPlaceholder( + { + ...ApiComponent, + name: 'ApiSelect', + }, + 'select', + { + component: ElSelectV2, + loadingSlot: 'loading', + visibleEvent: 'onVisibleChange', + }, + ), + ApiTreeSelect: withDefaultPlaceholder( + { + ...ApiComponent, + name: 'ApiTreeSelect', + }, + 'select', + { + component: ElTreeSelect, + props: { label: 'label', children: 'children' }, + nodeKey: 'value', + loadingSlot: 'loading', + optionsPropName: 'data', + visibleEvent: 'onVisibleChange', + }, + ), + Checkbox: ElCheckbox, + CheckboxGroup: (props, { attrs, slots }) => { + let defaultSlot; + if (Reflect.has(slots, 'default')) { + defaultSlot = slots.default; + } else { + const { options, isButton } = attrs; + if (Array.isArray(options)) { + defaultSlot = () => + options.map((option) => + h(isButton ? ElCheckboxButton : ElCheckbox, option), + ); + } + } + return h( + ElCheckboxGroup, + { ...props, ...attrs }, + { ...slots, default: defaultSlot }, + ); + }, + // 自定义默认按钮 + DefaultButton: (props, { attrs, slots }) => { + return h(ElButton, { ...props, attrs, type: 'info' }, slots); + }, + // 自定义主要按钮 + PrimaryButton: (props, { attrs, slots }) => { + return h(ElButton, { ...props, attrs, type: 'primary' }, slots); + }, + Divider: ElDivider, + IconPicker: withDefaultPlaceholder(IconPicker, 'select', { + iconSlot: 'append', + modelValueProp: 'model-value', + inputComponent: ElInput, + }), + Input: withDefaultPlaceholder(ElInput, 'input'), + InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'), + RadioGroup: (props, { attrs, slots }) => { + let defaultSlot; + if (Reflect.has(slots, 'default')) { + defaultSlot = slots.default; + } else { + const { options } = attrs; + if (Array.isArray(options)) { + defaultSlot = () => + options.map((option) => + h(attrs.isButton ? ElRadioButton : ElRadio, option), + ); + } + } + return h( + ElRadioGroup, + { ...props, ...attrs }, + { ...slots, default: defaultSlot }, + ); + }, + Select: (props, { attrs, slots }) => { + return h(ElSelectV2, { ...props, attrs }, slots); + }, + Space: ElSpace, + Switch: ElSwitch, + TimePicker: (props, { attrs, slots }) => { + const { name, id, isRange } = props; + const extraProps: Recordable = {}; + if (isRange) { + if (name && !Array.isArray(name)) { + extraProps.name = [name, `${name}_end`]; + } + if (id && !Array.isArray(id)) { + extraProps.id = [id, `${id}_end`]; + } + } + return h( + ElTimePicker, + { + ...props, + ...attrs, + ...extraProps, + }, + slots, + ); + }, + DatePicker: (props, { attrs, slots }) => { + const { name, id, type } = props; + const extraProps: Recordable = {}; + if (type && type.includes('range')) { + if (name && !Array.isArray(name)) { + extraProps.name = [name, `${name}_end`]; + } + if (id && !Array.isArray(id)) { + extraProps.id = [id, `${id}_end`]; + } + } + return h( + ElDatePicker, + { + ...props, + ...attrs, + ...extraProps, + }, + slots, + ); + }, + TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'), + Upload: ElUpload, + }; + + // 将组件注册到全局共享状态中 + globalShareState.setComponents(components); + + // 定义全局共享状态中的消息提示 + globalShareState.defineMessage({ + // 复制成功消息提示 + copyPreferencesSuccess: (title, content) => { + ElNotification({ + title, + message: content, + position: 'bottom-right', + duration: 0, + type: 'success', + }); + }, + }); +} + +export { initComponentAdapter }; diff --git a/app/src/adapter/form.ts b/app/src/adapter/form.ts new file mode 100644 index 0000000..9542f52 --- /dev/null +++ b/app/src/adapter/form.ts @@ -0,0 +1,41 @@ +import type { + AIFlowyFormSchema as FormSchema, + AIFlowyFormProps, +} from '@aiflowy/common-ui'; + +import type { ComponentType } from './component'; + +import { setupAIFlowyForm, useAIFlowyForm as useForm, z } from '@aiflowy/common-ui'; +import { $t } from '@aiflowy/locales'; + +async function initSetupAIFlowyForm() { + setupAIFlowyForm({ + config: { + modelPropNameMap: { + Upload: 'fileList', + CheckboxGroup: 'model-value', + }, + }, + defineRules: { + required: (value, _params, ctx) => { + if (value === undefined || value === null || value.length === 0) { + return $t('ui.formRules.required', [ctx.label]); + } + return true; + }, + selectRequired: (value, _params, ctx) => { + if (value === undefined || value === null) { + return $t('ui.formRules.selectRequired', [ctx.label]); + } + return true; + }, + }, + }); +} + +const useAIFlowyForm = useForm; + +export { initSetupAIFlowyForm, useAIFlowyForm, z }; + +export type AIFlowyFormSchema = FormSchema; +export type { AIFlowyFormProps }; diff --git a/app/src/adapter/vxe-table.ts b/app/src/adapter/vxe-table.ts new file mode 100644 index 0000000..9a96581 --- /dev/null +++ b/app/src/adapter/vxe-table.ts @@ -0,0 +1,70 @@ +import type { VxeTableGridOptions } from '@aiflowy/plugins/vxe-table'; + +import { h } from 'vue'; + +import { setupAIFlowyVxeTable, useAIFlowyVxeGrid } from '@aiflowy/plugins/vxe-table'; + +import { ElButton, ElImage } from 'element-plus'; + +import { useAIFlowyForm } from './form'; + +setupAIFlowyVxeTable({ + configVxeTable: (vxeUI) => { + vxeUI.setConfig({ + grid: { + align: 'center', + border: false, + columnConfig: { + resizable: true, + }, + minHeight: 180, + formConfig: { + // 全局禁用vxe-table的表单配置,使用formOptions + enabled: false, + }, + proxyConfig: { + autoLoad: true, + response: { + result: 'items', + total: 'total', + list: 'items', + }, + showActiveMsg: true, + showResponseMsg: false, + }, + round: true, + showOverflow: true, + size: 'small', + } as VxeTableGridOptions, + }); + + // 表格配置项可以用 cellRender: { name: 'CellImage' }, + vxeUI.renderer.add('CellImage', { + renderTableDefault(_renderOpts, params) { + const { column, row } = params; + const src = row[column.field]; + return h(ElImage, { src, previewSrcList: [src] }); + }, + }); + + // 表格配置项可以用 cellRender: { name: 'CellLink' }, + vxeUI.renderer.add('CellLink', { + renderTableDefault(renderOpts) { + const { props } = renderOpts; + return h( + ElButton, + { size: 'small', link: true }, + { default: () => props?.text }, + ); + }, + }); + + // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 + // vxeUI.formats.add + }, + useAIFlowyForm, +}); + +export { useAIFlowyVxeGrid }; + +export type * from '@aiflowy/plugins/vxe-table'; diff --git a/app/src/api/ai/bot.ts b/app/src/api/ai/bot.ts new file mode 100644 index 0000000..1f4e249 --- /dev/null +++ b/app/src/api/ai/bot.ts @@ -0,0 +1,138 @@ +import type { + AiLlm, + BotInfo, + ChatMessage, + RequestResult, + Session, +} from '@aiflowy/types'; + +import { api } from '#/api/request.js'; + +/** 获取bot详情 */ +export const getBotDetails = (id: string) => { + return api.get>('/api/v1/bot/getDetail', { + params: { id }, + }); +}; + +export interface GetSessionListParams { + botId: string; + tempUserId: string; +} +/** 获取bot对话列表 */ +export const getSessionList = (params: GetSessionListParams) => { + return api.get>( + '/api/v1/conversation/externalList', + { params }, + ); +}; + +export interface SaveBotParams { + icon: string; + title: string; + alias: string; + description: string; + categoryId: any; + status: number; +} +/** 创建Bot */ +export const saveBot = (params: SaveBotParams) => { + return api.post('/api/v1/bot/save', { ...params }); +}; + +export interface UpdateBotParams extends SaveBotParams { + id: string; +} +/** 修改Bot */ +export const updateBotApi = (params: UpdateBotParams) => { + return api.post('/api/v1/bot/update', { ...params }); +}; + +/** 删除Bot */ +export const removeBotFromId = (id: string) => { + return api.post('/api/v1/bot/update', { id }); +}; + +export interface GetMessageListParams { + conversationId: string; + botId: string; + tempUserId: string; +} +/** 获取单个对话的信息列表 */ +export const getMessageList = (params: GetMessageListParams) => { + return api.get>( + '/api/v1/botMessage/messageList', + { + params, + }, + ); +}; + +/** 更新Bot的LLM配置 */ +export interface UpdateLlmOptionsParams { + id: string; + llmOptions: { + [key: string]: any; + }; +} +export interface UpdateBotOptionsParams { + id: string; + options: { + [key: string]: any; + }; +} + +export const updateLlmOptions = (params: UpdateLlmOptionsParams) => { + return api.post('/api/v1/bot/updateLlmOptions', { + ...params, + }); +}; + +export const updateBotOptions = (params: UpdateBotOptionsParams) => { + return api.post('/api/v1/bot/updateOptions', { + ...params, + }); +}; + +/** 更新Bot的LLM配置 */ +export interface GetAiLlmListParams { + [key: string]: any; +} +export const getAiLlmList = (params: GetAiLlmListParams) => { + return api.get>('/api/v1/model/list', { + params, + }); +}; + +/** 更新modelId */ +export interface UpdateLlmIdParams { + id: string; + modelId: string; +} +export const updateLlmId = (params: UpdateLlmIdParams) => { + return api.post('/api/v1/bot/updateLlmId', { + ...params, + }); +}; + +export const doPostBotPluginTools = (botId: string) => { + return api.post>('/api/v1/pluginItem/tool/list', { + id: botId, + }); +}; + +export const getPerQuestions = (presetQuestions: any[]) => { + if (!presetQuestions) { + return []; + } + return presetQuestions + .filter((item: any) => { + return ( + typeof item.description === 'string' && item.description.trim() !== '' + ); + }) + .map((item: any) => ({ + key: item.key, + description: item.description, + })); +}; diff --git a/app/src/api/ai/index.ts b/app/src/api/ai/index.ts new file mode 100644 index 0000000..9db119a --- /dev/null +++ b/app/src/api/ai/index.ts @@ -0,0 +1,2 @@ +export * from './bot'; +export * from './llm'; diff --git a/app/src/api/ai/knowledge.ts b/app/src/api/ai/knowledge.ts new file mode 100644 index 0000000..e69de29 diff --git a/app/src/api/ai/llm.ts b/app/src/api/ai/llm.ts new file mode 100644 index 0000000..76cb2b3 --- /dev/null +++ b/app/src/api/ai/llm.ts @@ -0,0 +1,44 @@ +import { api } from '#/api/request.js'; + +// 获取LLM供应商 +export async function getLlmProviderList() { + return api.get('/api/v1/modelProvider/list'); +} + +// 保存LLM +export async function saveLlm(data: string) { + return api.post('/api/v1/model/save', data); +} + +// 删除LLM +export async function deleteLlm(data: any) { + return api.post(`/api/v1/model/remove`, data); +} + +// 修改LLM +export async function updateLlm(data: any) { + return api.post(`/api/v1/model/update`, data); +} + +// 一键添加LLM +export async function quickAddLlm(data: any) { + return api.post(`/api/v1/model/quickAdd`, data); +} + +export interface llmType { + id: string; + title: string; + modelProvider: { + icon: string; + providerName: string; + providerType: string; + }; + withUsed: boolean; + llmModel: string; + icon: string; + description: string; + modelType: string; + groupName: string; + added: boolean; + aiLlmProvider: any; +} diff --git a/app/src/api/common/file.ts b/app/src/api/common/file.ts new file mode 100644 index 0000000..525beb2 --- /dev/null +++ b/app/src/api/common/file.ts @@ -0,0 +1,27 @@ +/** + * 格式化文件大小(字节转 B/KB/MB/GB/TB) + * @param bytes - 文件大小(单位:字节 Byte) + * @param decimalPlaces - 保留小数位数(默认 2 位) + * @returns 格式化后的大小字符串(如:1.23 MB、456 B、7.8 GB) + */ +export function formatFileSize( + bytes: number, + decimalPlaces: number = 2, +): string { + // 处理特殊情况:bytes 为 0 或非数字 + if (Number.isNaN(bytes) || bytes < 0) return '0 B'; + if (bytes === 0) return '0 B'; + + // 单位数组(从 Byte 到 TB) + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + // 计算合适的单位索引(1 KB = 1024 B,每次除以 1024 切换单位) + const unitIndex = Math.floor(Math.log(bytes) / Math.log(1024)); + // 计算对应单位的大小(保留指定小数位) + const formattedSize = (bytes / 1024 ** unitIndex).toFixed(decimalPlaces); + + // 移除末尾多余的 .00(如 2.00 MB → 2 MB,1.50 KB → 1.5 KB) + const sizeWithoutTrailingZeros = Number.parseFloat(formattedSize).toString(); + + // 返回格式化结果(单位与大小拼接) + return `${sizeWithoutTrailingZeros} ${units[unitIndex]}`; +} diff --git a/app/src/api/common/hasPermission.ts b/app/src/api/common/hasPermission.ts new file mode 100644 index 0000000..e1aa91b --- /dev/null +++ b/app/src/api/common/hasPermission.ts @@ -0,0 +1,9 @@ +import { useAccessStore } from '@aiflowy/stores'; + +export function hasPermission(codes: string[]) { + const accessStore = useAccessStore(); + const userCodesSet = new Set(accessStore.accessCodes); + + const intersection = codes.filter((item) => userCodesSet.has(item)); + return intersection.length > 0; +} diff --git a/app/src/api/core/auth.ts b/app/src/api/core/auth.ts new file mode 100644 index 0000000..d0e8700 --- /dev/null +++ b/app/src/api/core/auth.ts @@ -0,0 +1,52 @@ +import { baseRequestClient, requestClient } from '#/api/request'; + +export namespace AuthApi { + /** 登录接口参数 */ + export interface LoginParams { + password?: string; + username?: string; + } + + /** 登录接口返回值 */ + export interface LoginResult { + accessToken: string; + token: string; + } + + export interface RefreshTokenResult { + data: string; + status: number; + } +} + +/** + * 登录 + */ +export async function loginApi(data: AuthApi.LoginParams) { + return requestClient.post('/api/v1/auth/login', data); +} + +/** + * 刷新accessToken + */ +export async function refreshTokenApi() { + return baseRequestClient.post('/auth/refresh', { + withCredentials: true, + }); +} + +/** + * 退出登录 + */ +export async function logoutApi() { + return requestClient.post('/api/v1/auth/logout', { + withCredentials: true, + }); +} + +/** + * 获取用户权限码 + */ +export async function getAccessCodesApi() { + return requestClient.get('/api/v1/auth/getPermissions'); +} diff --git a/app/src/api/core/index.ts b/app/src/api/core/index.ts new file mode 100644 index 0000000..28a5aef --- /dev/null +++ b/app/src/api/core/index.ts @@ -0,0 +1,3 @@ +export * from './auth'; +export * from './menu'; +export * from './user'; diff --git a/app/src/api/core/menu.ts b/app/src/api/core/menu.ts new file mode 100644 index 0000000..42af3ac --- /dev/null +++ b/app/src/api/core/menu.ts @@ -0,0 +1,12 @@ +import type { RouteRecordStringComponent } from '@aiflowy/types'; + +import { requestClient } from '#/api/request'; + +/** + * 获取用户所有菜单 + */ +export async function getAllMenusApi() { + return requestClient.get( + '/api/v1/sysMenu/treeV2', + ); +} diff --git a/app/src/api/core/user.ts b/app/src/api/core/user.ts new file mode 100644 index 0000000..5fa2b14 --- /dev/null +++ b/app/src/api/core/user.ts @@ -0,0 +1,10 @@ +import type { UserInfo } from '@aiflowy/types'; + +import { requestClient } from '#/api/request'; + +/** + * 获取用户信息 + */ +export async function getUserInfoApi() { + return requestClient.get('/api/v1/sysAccount/myProfile'); +} diff --git a/app/src/api/index.ts b/app/src/api/index.ts new file mode 100644 index 0000000..3b785a2 --- /dev/null +++ b/app/src/api/index.ts @@ -0,0 +1,2 @@ +export * from './ai'; +export * from './core'; diff --git a/app/src/api/request.ts b/app/src/api/request.ts new file mode 100644 index 0000000..f62a542 --- /dev/null +++ b/app/src/api/request.ts @@ -0,0 +1,221 @@ +import type { ServerSentEventMessage } from 'fetch-event-stream'; + +/** + * 该文件可自行根据业务逻辑进行调整 + */ +import type { RequestClientOptions } from '@aiflowy/request'; + +import { useAppConfig } from '@aiflowy/hooks'; +import { preferences } from '@aiflowy/preferences'; +import { + authenticateResponseInterceptor, + defaultResponseInterceptor, + errorMessageResponseInterceptor, + RequestClient, +} from '@aiflowy/request'; +import { useAccessStore } from '@aiflowy/stores'; + +import { ElMessage } from 'element-plus'; +import { events } from 'fetch-event-stream'; + +import { useAuthStore } from '#/store'; + +import { refreshTokenApi } from './core'; + +const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); + +function createRequestClient(baseURL: string, options?: RequestClientOptions) { + const client = new RequestClient({ + ...options, + baseURL, + }); + + /** + * 重新认证逻辑 + */ + async function doReAuthenticate() { + console.warn('Access token or refresh token is invalid or expired. '); + const accessStore = useAccessStore(); + const authStore = useAuthStore(); + accessStore.setAccessToken(null); + if ( + preferences.app.loginExpiredMode === 'modal' && + accessStore.isAccessChecked + ) { + accessStore.setLoginExpired(true); + } else { + await authStore.logout(); + } + } + + /** + * 刷新token逻辑 + */ + async function doRefreshToken() { + const accessStore = useAccessStore(); + const resp = await refreshTokenApi(); + const newToken = resp.data; + accessStore.setAccessToken(newToken); + return newToken; + } + + function formatToken(token: null | string) { + return token ? `${token}` : null; + } + + // 请求头处理 + client.addRequestInterceptor({ + fulfilled: async (config) => { + const accessStore = useAccessStore(); + + config.headers['aiflowy-token'] = formatToken(accessStore.accessToken); + config.headers['Accept-Language'] = preferences.app.locale; + return config; + }, + }); + + // 处理返回的响应数据格式 + client.addResponseInterceptor( + defaultResponseInterceptor({ + codeField: 'errorCode', + dataField: 'data', + showErrorMessage: (message) => { + ElMessage.error(message); + }, + successCode: 0, + }), + ); + + // token过期的处理 + client.addResponseInterceptor( + authenticateResponseInterceptor({ + client, + doReAuthenticate, + doRefreshToken, + enableRefreshToken: preferences.app.enableRefreshToken, + formatToken, + }), + ); + + // 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里 + client.addResponseInterceptor( + errorMessageResponseInterceptor((msg: string, error) => { + // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg + // 当前mock接口返回的错误字段是 error 或者 message + const responseData = error?.response?.data ?? {}; + const errorMessage = responseData?.error ?? responseData?.message ?? ''; + // 如果没有错误信息,则会根据状态码进行提示 + ElMessage.error(errorMessage || msg); + }), + ); + + return client; +} + +export const requestClient = createRequestClient(apiURL, { + responseReturn: 'data', +}); + +export const api = createRequestClient(apiURL, { + responseReturn: 'body', +}); + +export const baseRequestClient = new RequestClient({ baseURL: apiURL }); + +export interface SseOptions { + onMessage?: (message: ServerSentEventMessage) => void; + onError?: (err: any) => void; + onFinished?: () => void; +} +export class SseClient { + private controller: AbortController | null = null; + private currentRequestId = 0; + + abort(): void { + if (this.controller) { + this.controller.abort(); + this.controller = null; + } + } + + isActive(): boolean { + return this.controller !== null; + } + + async post(url: string, data?: any, options?: SseOptions): Promise { + // 生成唯一的请求ID + const requestId = ++this.currentRequestId; + const currentRequestId = requestId; + + // 如果已有请求,先取消 + this.abort(); + + // 创建新的控制器 + const controller = new AbortController(); + this.controller = controller; + + // 保存信号的引用到局部变量 + const signal = controller.signal; + + try { + const res = await fetch(apiURL + url, { + method: 'POST', + signal, // 使用局部变量 signal + headers: this.getHeaders(), + body: JSON.stringify(data), + }); + + if (!res.ok) { + const error = new Error(`HTTP ${res.status}: ${res.statusText}`); + options?.onError?.(error); + return; + } + + // 在开始事件流之前检查是否还是同一个请求 + if (this.currentRequestId !== currentRequestId) { + return; + } + + const msgEvents = events(res, signal); + + try { + for await (const event of msgEvents) { + // 每次迭代都检查是否还是同一个请求 + if (this.currentRequestId !== currentRequestId) { + break; + } + options?.onMessage?.(event); + } + } catch (innerError) { + options?.onError?.(innerError); + } + + // 只有在还是同一个请求的情况下才调用 onFinished + if (this.currentRequestId === currentRequestId) { + options?.onFinished?.(); + } + } catch (error) { + if (this.currentRequestId !== currentRequestId) { + return; + } + console.error('SSE错误:', error); + options?.onError?.(error); + } finally { + // 只有当还是当前请求时才清除 controller + if (this.currentRequestId === currentRequestId) { + this.controller = null; + } + } + } + + private getHeaders() { + const accessStore = useAccessStore(); + return { + Accept: 'text/event-stream', + 'Content-Type': 'application/json', + 'aiflowy-token': accessStore.accessToken || '', + }; + } +} + +export const sseClient = new SseClient(); diff --git a/app/src/app.vue b/app/src/app.vue new file mode 100644 index 0000000..db9459c --- /dev/null +++ b/app/src/app.vue @@ -0,0 +1,17 @@ + + + diff --git a/app/src/assets/ai/bot/defaultBotAvatar.png b/app/src/assets/ai/bot/defaultBotAvatar.png new file mode 100644 index 0000000..817cc26 Binary files /dev/null and b/app/src/assets/ai/bot/defaultBotAvatar.png differ diff --git a/app/src/assets/ai/knowledge/book.png b/app/src/assets/ai/knowledge/book.png new file mode 100644 index 0000000..6a2d8f2 Binary files /dev/null and b/app/src/assets/ai/knowledge/book.png differ diff --git a/app/src/assets/ai/knowledge/book.svg b/app/src/assets/ai/knowledge/book.svg new file mode 100644 index 0000000..840d42e --- /dev/null +++ b/app/src/assets/ai/knowledge/book.svg @@ -0,0 +1,17 @@ + + + 编组备份 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/assets/ai/knowledge/document.svg b/app/src/assets/ai/knowledge/document.svg new file mode 100644 index 0000000..53589b8 --- /dev/null +++ b/app/src/assets/ai/knowledge/document.svg @@ -0,0 +1,27 @@ + + + 编组 9 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/assets/ai/plugin/defaultPluginIcon.png b/app/src/assets/ai/plugin/defaultPluginIcon.png new file mode 100644 index 0000000..4f1aa84 Binary files /dev/null and b/app/src/assets/ai/plugin/defaultPluginIcon.png differ diff --git a/app/src/assets/ai/resource/audio-icon.png b/app/src/assets/ai/resource/audio-icon.png new file mode 100644 index 0000000..97a01b4 Binary files /dev/null and b/app/src/assets/ai/resource/audio-icon.png differ diff --git a/app/src/assets/ai/resource/doc-icon.png b/app/src/assets/ai/resource/doc-icon.png new file mode 100644 index 0000000..26853bb Binary files /dev/null and b/app/src/assets/ai/resource/doc-icon.png differ diff --git a/app/src/assets/ai/resource/other-icon.png b/app/src/assets/ai/resource/other-icon.png new file mode 100644 index 0000000..0ae38ae Binary files /dev/null and b/app/src/assets/ai/resource/other-icon.png differ diff --git a/app/src/assets/ai/resource/video-icon.png b/app/src/assets/ai/resource/video-icon.png new file mode 100644 index 0000000..43598b9 Binary files /dev/null and b/app/src/assets/ai/resource/video-icon.png differ diff --git a/app/src/assets/ai/workflow/confirm-file.png b/app/src/assets/ai/workflow/confirm-file.png new file mode 100644 index 0000000..fa28fb8 Binary files /dev/null and b/app/src/assets/ai/workflow/confirm-file.png differ diff --git a/app/src/assets/ai/workflow/confirm-icon.png b/app/src/assets/ai/workflow/confirm-icon.png new file mode 100644 index 0000000..985da1e Binary files /dev/null and b/app/src/assets/ai/workflow/confirm-icon.png differ diff --git a/app/src/assets/ai/workflow/confirm-other.png b/app/src/assets/ai/workflow/confirm-other.png new file mode 100644 index 0000000..b3e00c5 Binary files /dev/null and b/app/src/assets/ai/workflow/confirm-other.png differ diff --git a/app/src/assets/ai/workflow/fileIcon.png b/app/src/assets/ai/workflow/fileIcon.png new file mode 100644 index 0000000..adde504 Binary files /dev/null and b/app/src/assets/ai/workflow/fileIcon.png differ diff --git a/app/src/assets/ai/workflow/workflowIcon.png b/app/src/assets/ai/workflow/workflowIcon.png new file mode 100644 index 0000000..69d3c8b Binary files /dev/null and b/app/src/assets/ai/workflow/workflowIcon.png differ diff --git a/app/src/assets/datacenter/table2x.png b/app/src/assets/datacenter/table2x.png new file mode 100644 index 0000000..7e41bbe Binary files /dev/null and b/app/src/assets/datacenter/table2x.png differ diff --git a/app/src/assets/datacenter/upload.png b/app/src/assets/datacenter/upload.png new file mode 100644 index 0000000..2fdfe76 Binary files /dev/null and b/app/src/assets/datacenter/upload.png differ diff --git a/app/src/assets/defaultUserAvatar.png b/app/src/assets/defaultUserAvatar.png new file mode 100644 index 0000000..207dc70 Binary files /dev/null and b/app/src/assets/defaultUserAvatar.png differ diff --git a/app/src/assets/login/dingding-60.png b/app/src/assets/login/dingding-60.png new file mode 100644 index 0000000..3b86c17 Binary files /dev/null and b/app/src/assets/login/dingding-60.png differ diff --git a/app/src/assets/login/wx-60.png b/app/src/assets/login/wx-60.png new file mode 100644 index 0000000..52383ef Binary files /dev/null and b/app/src/assets/login/wx-60.png differ diff --git a/app/src/bootstrap.ts b/app/src/bootstrap.ts new file mode 100644 index 0000000..366e088 --- /dev/null +++ b/app/src/bootstrap.ts @@ -0,0 +1,90 @@ +import { createApp, watchEffect } from 'vue'; +import { + BubbleList, + Conversations, + Sender, + XMarkdown, +} from 'vue-element-plus-x'; + +import { registerAccessDirective } from '@aiflowy/access'; +import { registerLoadingDirective } from '@aiflowy/common-ui'; +import { preferences } from '@aiflowy/preferences'; +import { initStores } from '@aiflowy/stores'; +import '@aiflowy/styles'; +import '@aiflowy/styles/ele'; + +import { useTitle } from '@vueuse/core'; +import { ElLoading } from 'element-plus'; + +import { $t, setupI18n } from '#/locales'; + +import { initComponentAdapter } from './adapter/component'; +import { initSetupAIFlowyForm } from './adapter/form'; +import App from './app.vue'; +import { router } from './router'; + +async function bootstrap(namespace: string) { + // 初始化组件适配器 + await initComponentAdapter(); + + // 初始化表单组件 + await initSetupAIFlowyForm(); + + // // 设置弹窗的默认配置 + // setDefaultModalProps({ + // fullscreenButton: false, + // }); + // // 设置抽屉的默认配置 + // setDefaultDrawerProps({ + // zIndex: 2000, + // }); + const app = createApp(App); + + // 注册Element Plus提供的v-loading指令 + app.directive('loading', ElLoading.directive); + + app.component('ElBubbleList', BubbleList); + app.component('ElConversations', Conversations); + app.component('ElSender', Sender); + app.component('XMarkdown', XMarkdown); + + // 注册AIFlowy提供的v-loading和v-spinning指令 + registerLoadingDirective(app, { + loading: false, // AIFlowy提供的v-loading指令和Element Plus提供的v-loading指令二选一即可,此处false表示不注册AIFlowy提供的v-loading指令 + spinning: 'spinning', + }); + + // 国际化 i18n 配置 + await setupI18n(app); + + // 配置 pinia-tore + await initStores(app, { namespace }); + + // 安装权限指令 + registerAccessDirective(app); + + // 初始化 tippy + const { initTippy } = await import('@aiflowy/common-ui/es/tippy'); + initTippy(app); + + // 配置路由及路由守卫 + app.use(router); + + // 配置Motion插件 + const { MotionPlugin } = await import('@aiflowy/plugins/motion'); + app.use(MotionPlugin); + + // 动态更新标题 + watchEffect(() => { + if (preferences.app.dynamicTitle) { + const routeTitle = router.currentRoute.value.meta?.title; + const pageTitle = + (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name; + useTitle(pageTitle); + } + }); + + app.mount('#app'); +} + +export { bootstrap }; diff --git a/app/src/components/botAvatar/botAvatar.vue b/app/src/components/botAvatar/botAvatar.vue new file mode 100644 index 0000000..8b6bc90 --- /dev/null +++ b/app/src/components/botAvatar/botAvatar.vue @@ -0,0 +1,14 @@ + + + diff --git a/app/src/components/cardPage/CardPage.vue b/app/src/components/cardPage/CardPage.vue new file mode 100644 index 0000000..3621443 --- /dev/null +++ b/app/src/components/cardPage/CardPage.vue @@ -0,0 +1,355 @@ + + + + + diff --git a/app/src/components/categoryPanel/CategoryCrudPanel.vue b/app/src/components/categoryPanel/CategoryCrudPanel.vue new file mode 100644 index 0000000..21d0dd1 --- /dev/null +++ b/app/src/components/categoryPanel/CategoryCrudPanel.vue @@ -0,0 +1,325 @@ + + + + + diff --git a/app/src/components/categoryPanel/CategoryPanel.vue b/app/src/components/categoryPanel/CategoryPanel.vue new file mode 100644 index 0000000..4a32d23 --- /dev/null +++ b/app/src/components/categoryPanel/CategoryPanel.vue @@ -0,0 +1,370 @@ + + + + + diff --git a/app/src/components/chat/ProblemPresupposition.vue b/app/src/components/chat/ProblemPresupposition.vue new file mode 100644 index 0000000..0d99199 --- /dev/null +++ b/app/src/components/chat/ProblemPresupposition.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/app/src/components/chat/PublishWxOfficalAccount.vue b/app/src/components/chat/PublishWxOfficalAccount.vue new file mode 100644 index 0000000..6a336df --- /dev/null +++ b/app/src/components/chat/PublishWxOfficalAccount.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/app/src/components/chat/SenderHeader.vue b/app/src/components/chat/SenderHeader.vue new file mode 100644 index 0000000..ac63180 --- /dev/null +++ b/app/src/components/chat/SenderHeader.vue @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/components/chat/chat.vue b/app/src/components/chat/chat.vue new file mode 100644 index 0000000..9204473 --- /dev/null +++ b/app/src/components/chat/chat.vue @@ -0,0 +1,407 @@ + + + + + diff --git a/app/src/components/collapse/CustomCoolapse.vue b/app/src/components/collapse/CustomCoolapse.vue new file mode 100644 index 0000000..0ff32f2 --- /dev/null +++ b/app/src/components/collapse/CustomCoolapse.vue @@ -0,0 +1,309 @@ + + + + + diff --git a/app/src/components/collapseViewItem/CollapseViewItem.vue b/app/src/components/collapseViewItem/CollapseViewItem.vue new file mode 100644 index 0000000..e38b664 --- /dev/null +++ b/app/src/components/collapseViewItem/CollapseViewItem.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/app/src/components/commonSelectModal/CommonSelectDataModal.vue b/app/src/components/commonSelectModal/CommonSelectDataModal.vue new file mode 100644 index 0000000..725c1e0 --- /dev/null +++ b/app/src/components/commonSelectModal/CommonSelectDataModal.vue @@ -0,0 +1,471 @@ + + + + + diff --git a/app/src/components/cron/CronGenerator.vue b/app/src/components/cron/CronGenerator.vue new file mode 100644 index 0000000..a0951b5 --- /dev/null +++ b/app/src/components/cron/CronGenerator.vue @@ -0,0 +1,281 @@ + + + + + diff --git a/app/src/components/cron/CronPicker.vue b/app/src/components/cron/CronPicker.vue new file mode 100644 index 0000000..78775f0 --- /dev/null +++ b/app/src/components/cron/CronPicker.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/app/src/components/cron/CronTabPane.vue b/app/src/components/cron/CronTabPane.vue new file mode 100644 index 0000000..afeb34c --- /dev/null +++ b/app/src/components/cron/CronTabPane.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/app/src/components/dict/DictSelect.vue b/app/src/components/dict/DictSelect.vue new file mode 100644 index 0000000..4baa82e --- /dev/null +++ b/app/src/components/dict/DictSelect.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/app/src/components/headerSearch/HeaderSearch.vue b/app/src/components/headerSearch/HeaderSearch.vue new file mode 100644 index 0000000..c4e5e56 --- /dev/null +++ b/app/src/components/headerSearch/HeaderSearch.vue @@ -0,0 +1,221 @@ + + + + + diff --git a/app/src/components/icons/CategorizeIcon.vue b/app/src/components/icons/CategorizeIcon.vue new file mode 100644 index 0000000..9aeffdd --- /dev/null +++ b/app/src/components/icons/CategorizeIcon.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/app/src/components/icons/DesignIcon.vue b/app/src/components/icons/DesignIcon.vue new file mode 100644 index 0000000..c5052ae --- /dev/null +++ b/app/src/components/icons/DesignIcon.vue @@ -0,0 +1,18 @@ + diff --git a/app/src/components/icons/EditIcon.vue b/app/src/components/icons/EditIcon.vue new file mode 100644 index 0000000..92141a8 --- /dev/null +++ b/app/src/components/icons/EditIcon.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/app/src/components/icons/MagicStaffIcon.vue b/app/src/components/icons/MagicStaffIcon.vue new file mode 100644 index 0000000..318bf7b --- /dev/null +++ b/app/src/components/icons/MagicStaffIcon.vue @@ -0,0 +1,55 @@ + diff --git a/app/src/components/icons/ManageIcon.vue b/app/src/components/icons/ManageIcon.vue new file mode 100644 index 0000000..866f6e4 --- /dev/null +++ b/app/src/components/icons/ManageIcon.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/app/src/components/icons/PluginIcon.vue b/app/src/components/icons/PluginIcon.vue new file mode 100644 index 0000000..0e8497e --- /dev/null +++ b/app/src/components/icons/PluginIcon.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/app/src/components/icons/PluginToolIcon.vue b/app/src/components/icons/PluginToolIcon.vue new file mode 100644 index 0000000..dcc0e42 --- /dev/null +++ b/app/src/components/icons/PluginToolIcon.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/app/src/components/icons/RecordingIcon.vue b/app/src/components/icons/RecordingIcon.vue new file mode 100644 index 0000000..6165ebd --- /dev/null +++ b/app/src/components/icons/RecordingIcon.vue @@ -0,0 +1,129 @@ + diff --git a/app/src/components/icons/SendEnableIcon.vue b/app/src/components/icons/SendEnableIcon.vue new file mode 100644 index 0000000..bfa1d10 --- /dev/null +++ b/app/src/components/icons/SendEnableIcon.vue @@ -0,0 +1,37 @@ + diff --git a/app/src/components/icons/SendIcon.vue b/app/src/components/icons/SendIcon.vue new file mode 100644 index 0000000..a400927 --- /dev/null +++ b/app/src/components/icons/SendIcon.vue @@ -0,0 +1,38 @@ + diff --git a/app/src/components/icons/SendingIcon.vue b/app/src/components/icons/SendingIcon.vue new file mode 100644 index 0000000..d29473b --- /dev/null +++ b/app/src/components/icons/SendingIcon.vue @@ -0,0 +1,47 @@ + diff --git a/app/src/components/json/ShowJson.vue b/app/src/components/json/ShowJson.vue new file mode 100644 index 0000000..193fcf5 --- /dev/null +++ b/app/src/components/json/ShowJson.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/app/src/components/page/CardList.vue b/app/src/components/page/CardList.vue new file mode 100644 index 0000000..4e7cea8 --- /dev/null +++ b/app/src/components/page/CardList.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/app/src/components/page/PageData.vue b/app/src/components/page/PageData.vue new file mode 100644 index 0000000..a1cc74f --- /dev/null +++ b/app/src/components/page/PageData.vue @@ -0,0 +1,129 @@ + + + diff --git a/app/src/components/page/PageSide.vue b/app/src/components/page/PageSide.vue new file mode 100644 index 0000000..dc193ef --- /dev/null +++ b/app/src/components/page/PageSide.vue @@ -0,0 +1,236 @@ + + + + + diff --git a/app/src/components/tag/Tag.vue b/app/src/components/tag/Tag.vue new file mode 100644 index 0000000..85a6cf3 --- /dev/null +++ b/app/src/components/tag/Tag.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/app/src/components/tree/Tree.vue b/app/src/components/tree/Tree.vue new file mode 100644 index 0000000..61556c3 --- /dev/null +++ b/app/src/components/tree/Tree.vue @@ -0,0 +1,262 @@ + + + + + diff --git a/app/src/components/upload/Cropper.vue b/app/src/components/upload/Cropper.vue new file mode 100644 index 0000000..0dae046 --- /dev/null +++ b/app/src/components/upload/Cropper.vue @@ -0,0 +1,461 @@ + + + + + diff --git a/app/src/components/upload/CropperMulti.vue b/app/src/components/upload/CropperMulti.vue new file mode 100644 index 0000000..8afe5af --- /dev/null +++ b/app/src/components/upload/CropperMulti.vue @@ -0,0 +1,603 @@ + + + + + diff --git a/app/src/components/upload/DragFileUpload.vue b/app/src/components/upload/DragFileUpload.vue new file mode 100644 index 0000000..3c042dc --- /dev/null +++ b/app/src/components/upload/DragFileUpload.vue @@ -0,0 +1,83 @@ + + + diff --git a/app/src/components/upload/Upload.vue b/app/src/components/upload/Upload.vue new file mode 100644 index 0000000..b6d889c --- /dev/null +++ b/app/src/components/upload/Upload.vue @@ -0,0 +1,72 @@ + + + diff --git a/app/src/components/upload/UploadAvatar.vue b/app/src/components/upload/UploadAvatar.vue new file mode 100644 index 0000000..22719a1 --- /dev/null +++ b/app/src/components/upload/UploadAvatar.vue @@ -0,0 +1,125 @@ + + + + + + + diff --git a/app/src/layouts/auth.vue b/app/src/layouts/auth.vue new file mode 100644 index 0000000..ed889f8 --- /dev/null +++ b/app/src/layouts/auth.vue @@ -0,0 +1,27 @@ + + + diff --git a/app/src/layouts/basic.vue b/app/src/layouts/basic.vue new file mode 100644 index 0000000..35ee1e8 --- /dev/null +++ b/app/src/layouts/basic.vue @@ -0,0 +1,201 @@ + + + diff --git a/app/src/layouts/index.ts b/app/src/layouts/index.ts new file mode 100644 index 0000000..53bd2b7 --- /dev/null +++ b/app/src/layouts/index.ts @@ -0,0 +1,6 @@ +const BasicLayout = () => import('./basic.vue'); +const AuthPageLayout = () => import('./auth.vue'); + +const IFrameView = () => import('@aiflowy/layouts').then((m) => m.IFrameView); + +export { AuthPageLayout, BasicLayout, IFrameView }; diff --git a/app/src/locales/README.md b/app/src/locales/README.md new file mode 100644 index 0000000..7b45103 --- /dev/null +++ b/app/src/locales/README.md @@ -0,0 +1,3 @@ +# locale + +每个app使用的国际化可能不同,这里用于扩展国际化的功能,例如扩展 dayjs、antd组件库的多语言切换,以及app本身的国际化文件。 diff --git a/app/src/locales/index.ts b/app/src/locales/index.ts new file mode 100644 index 0000000..551dff8 --- /dev/null +++ b/app/src/locales/index.ts @@ -0,0 +1,102 @@ +import type { Language } from 'element-plus/es/locale'; + +import type { App } from 'vue'; + +import type { LocaleSetupOptions, SupportedLanguagesType } from '@aiflowy/locales'; + +import { ref } from 'vue'; + +import { + $t, + setupI18n as coreSetup, + loadLocalesMapFromDir, +} from '@aiflowy/locales'; +import { preferences } from '@aiflowy/preferences'; + +import dayjs from 'dayjs'; +import enLocale from 'element-plus/es/locale/lang/en'; +import defaultLocale from 'element-plus/es/locale/lang/zh-cn'; + +const elementLocale = ref(defaultLocale); + +const modules = import.meta.glob('./langs/**/*.json'); + +const localesMap = loadLocalesMapFromDir( + /\.\/langs\/([^/]+)\/(.*)\.json$/, + modules, +); +/** + * 加载应用特有的语言包 + * 这里也可以改造为从服务端获取翻译数据 + * @param lang + */ +async function loadMessages(lang: SupportedLanguagesType) { + const [appLocaleMessages] = await Promise.all([ + localesMap[lang]?.(), + loadThirdPartyMessage(lang), + ]); + return appLocaleMessages?.default; +} + +/** + * 加载第三方组件库的语言包 + * @param lang + */ +async function loadThirdPartyMessage(lang: SupportedLanguagesType) { + await Promise.all([loadElementLocale(lang), loadDayjsLocale(lang)]); +} + +/** + * 加载dayjs的语言包 + * @param lang + */ +async function loadDayjsLocale(lang: SupportedLanguagesType) { + let locale; + switch (lang) { + case 'en-US': { + locale = await import('dayjs/locale/en'); + break; + } + case 'zh-CN': { + locale = await import('dayjs/locale/zh-cn'); + break; + } + // 默认使用英语 + default: { + locale = await import('dayjs/locale/en'); + } + } + if (locale) { + dayjs.locale(locale); + } else { + console.error(`Failed to load dayjs locale for ${lang}`); + } +} + +/** + * 加载element-plus的语言包 + * @param lang + */ +async function loadElementLocale(lang: SupportedLanguagesType) { + switch (lang) { + case 'en-US': { + elementLocale.value = enLocale; + break; + } + case 'zh-CN': { + elementLocale.value = defaultLocale; + break; + } + } +} + +async function setupI18n(app: App, options: LocaleSetupOptions = {}) { + await coreSetup(app, { + defaultLocale: preferences.app.locale, + loadMessages, + missingWarn: !import.meta.env.PROD, + ...options, + }); +} + +export { $t, elementLocale, setupI18n }; diff --git a/app/src/locales/langs/en-US/aiResource.json b/app/src/locales/langs/en-US/aiResource.json new file mode 100644 index 0000000..f18527a --- /dev/null +++ b/app/src/locales/langs/en-US/aiResource.json @@ -0,0 +1,20 @@ +{ + "id": "Id", + "deptId": "DeptId", + "tenantId": "TenantId", + "resourceType": "ResourceType", + "resourceName": "ResourceName", + "suffix": "Suffix", + "resourceUrl": "ResourceUrl", + "origin": "Origin", + "status": "Status", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "options": "Options", + "isDeleted": "IsDeleted", + "fileSize": "FileSize", + "categoryId": "Category", + "choose": "Choose" +} diff --git a/app/src/locales/langs/en-US/aiWorkflow.json b/app/src/locales/langs/en-US/aiWorkflow.json new file mode 100644 index 0000000..e3f7393 --- /dev/null +++ b/app/src/locales/langs/en-US/aiWorkflow.json @@ -0,0 +1,71 @@ +{ + "id": "Id", + "alias": "Alias", + "deptId": "DeptId", + "tenantId": "TenantId", + "title": "Title", + "description": "Description", + "icon": "Icon", + "content": "Content", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "englishName": "EnglishName", + "status": "ShowInUserCenter", + "categoryId": "Category", + "params": "Params", + "steps": "Steps", + "result": "Result", + "confirm": "For contents to be confirmed, please confirm first!", + "completed": "Chain has been completed, please start a new one.", + "fileContentExtraction": "FileContentExtraction", + "documentAddress": "DocumentAddress", + "parsedText": "ParsedText", + "resourceSync": "ResourceSync", + "originUrl": "OriginUrl", + "savedUrl": "SavedUrl", + "saveOptions": "SaveOptions", + "image": "Image", + "video": "Video", + "audio": "Audio", + "document": "Document", + "other": "Other", + "fileGeneration": "FileGeneration", + "fileSettings": "FileSettings", + "fileDownloadURL": "FileDownloadURL", + "pluginSelect": "PluginSelect", + "saveData": "SaveData", + "dataToBeSaved": "DataToBeSaved", + "successInsertedRecords": "SuccessInsertedRecords", + "dataTable": "DataTable", + "queryData": "QueryData", + "queryResult": "QueryResult", + "filterConditions": "FilterConditions", + "limit": "Limit", + "sqlQuery": "SQL Query", + "subProcess": "SubProcess", + "workflowSelect": "WorkflowSelect", + "bochaSearch": "BochaSearch", + "descriptions": { + "fileContentExtraction": "Extract text content from PDF or Word documents, etc", + "documentAddress": "Document URL address", + "parsedText": "Parsed text content", + "resourceSync": "Download resource files and save to system resource library", + "originUrl": "File origin URL", + "resourceType": "Please select the type of resource", + "fileGeneration": "Generate Word, PDF, HTML, etc. files for users to download", + "fileType": "Please select the type of file to generate", + "fileDownloadURL": "Generated file URL", + "plugin": "Select a predefined plugin", + "saveData": "Save data to data hub", + "dataToBeSaved": "List of data to be saved", + "dataTable": "Please select a data table", + "queryData": "Query data from the data hub", + "filterConditions": "For example: name='张三' and age=21 or field = {{process variable}}", + "sqlQuery": "Query the database via SQL", + "enterSQL": "Please enter the SQL statement", + "queryResultJson": "Query result (JSON object)", + "subProcess": "Select a predefined process" + } +} diff --git a/app/src/locales/langs/en-US/aiWorkflowCategory.json b/app/src/locales/langs/en-US/aiWorkflowCategory.json new file mode 100644 index 0000000..ed18a81 --- /dev/null +++ b/app/src/locales/langs/en-US/aiWorkflowCategory.json @@ -0,0 +1,10 @@ +{ + "id": "Id", + "categoryName": "CategoryName", + "sortNo": "SortNo", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "status": "Status" +} \ No newline at end of file diff --git a/app/src/locales/langs/en-US/aiWorkflowExecRecord.json b/app/src/locales/langs/en-US/aiWorkflowExecRecord.json new file mode 100644 index 0000000..a568b58 --- /dev/null +++ b/app/src/locales/langs/en-US/aiWorkflowExecRecord.json @@ -0,0 +1,25 @@ +{ + "id": "Id", + "execKey": "ExecKey", + "workflowId": "WorkflowId", + "title": "Title", + "description": "Description", + "input": "Input", + "output": "Output", + "workflowJson": "WorkflowJson", + "startTime": "StartTime", + "endTime": "EndTime", + "tokens": "Tokens", + "status": "Status", + "createdKey": "CreatedKey", + "createdBy": "CreatedBy", + "errorInfo": "ErrorInfo", + "moduleName": "ExecuteRecords", + "execTime": "TimeConsuming", + "status1": "Running", + "status5": "Suspend", + "status10": "Error", + "status20": "Success", + "status21": "Failed", + "status22": "Cancel" +} diff --git a/app/src/locales/langs/en-US/aiWorkflowRecordStep.json b/app/src/locales/langs/en-US/aiWorkflowRecordStep.json new file mode 100644 index 0000000..0aa8c11 --- /dev/null +++ b/app/src/locales/langs/en-US/aiWorkflowRecordStep.json @@ -0,0 +1,22 @@ +{ + "id": "Id", + "recordId": "RecordId", + "execKey": "ExecKey", + "nodeId": "NodeId", + "nodeName": "NodeName", + "input": "Input", + "output": "Output", + "nodeData": "NodeData", + "startTime": "StartTime", + "endTime": "EndTime", + "tokens": "Tokens", + "status": "Status", + "errorInfo": "ErrorInfo", + "moduleName": "Steps", + "execTime": "TimeConsuming", + "status1": "Running", + "status6": "Suspend", + "status10": "Error", + "status20": "Success", + "status21": "Failed" +} diff --git a/app/src/locales/langs/en-US/bot.json b/app/src/locales/langs/en-US/bot.json new file mode 100644 index 0000000..d1615d9 --- /dev/null +++ b/app/src/locales/langs/en-US/bot.json @@ -0,0 +1,26 @@ +{ + "problemPresupposition": "ProblemPresupposition", + "noPermission": "NoPermission", + "llm": "LLM", + "temperature": "Temperature", + "maxReplyLength": "MaxReplyLength", + "historyCount": "HistoryCount", + "skill": "Skill", + "conversationSettings": "ConversationSettings", + "welcomeMessage": "WelcomeMessage", + "deepThinking": "DeepThinking", + "enableDeepThinking": "EnableDeepThinking", + "publish": "Publish", + "postToWeChatOfficialAccount": "PostToWeChatOfficialAccount", + "configured": "Configured", + "notConfigured": "NotConfigured", + "placeholder": { + "welcome": "Please enter welcome message", + "prompt": "You are an AI assistant. Please provide clear and accurate answers based on the user's questions.", + "permission": "No permission to configure the bot!" + }, + "systemPrompt": "System Prompt", + "aiOptimization": "AI Optimization", + "weChatOfficialAccountConfiguration": "WeChat Official Account configuration", + "aiOptimizedPrompts": "AI Optimized Prompts" +} diff --git a/app/src/locales/langs/en-US/button.json b/app/src/locales/langs/en-US/button.json new file mode 100644 index 0000000..6facfab --- /dev/null +++ b/app/src/locales/langs/en-US/button.json @@ -0,0 +1,46 @@ +{ + "query": "Query", + "reset": "Reset", + "add": "Add", + "edit": "Edit", + "delete": "Delete", + "export": "Export", + "import": "Import", + "save": "Save", + "cancel": "Cancel", + "confirm": "Confirm", + "addLlm": "New large model added", + "oneClickAdd": "One-click add", + "newConversation": "New Conversation", + "start": "Start", + "stop": "Stop", + "log": "Log", + "back": "Back", + "importFile": "ImportFile", + "view": "View", + "download": "Download", + "upload": "Upload", + "preview": "Preview", + "nextStep": "NextStep", + "previousStep": "PreviousStep", + "addLine": "AddRecord", + "batchImport": "BatchImport", + "startImport": "StartImport", + "design": "Design", + "run": "Run", + "runTest": "RunTest", + "copy": "Copy", + "selectAll": "Select All", + "choose": "Select", + "setting": "Setting", + "create": "Create", + "update": "Update", + "oneClickOptimization": "One-click optimization", + "replace": "Replace", + "markAsRead": "MarkAsRead", + "markAsResolved": "MarkAsResolved", + "optimizing": "Optimizing", + "regenerate": "Regenerate", + "hide": "Hide", + "more": "Mode" +} diff --git a/app/src/locales/langs/en-US/common.json b/app/src/locales/langs/en-US/common.json new file mode 100644 index 0000000..5cbb07c --- /dev/null +++ b/app/src/locales/langs/en-US/common.json @@ -0,0 +1,25 @@ +{ + "handle": "Operate", + "searchPlaceholder": "Please enter search content", + "allCategories": "All", + "history": "History", + "noDataAvailable": "No data available", + "isRequired": " is required", + "avatar": "Avatar", + "otherLoginType": "Other login methods", + "Sun": "Sun", + "Mon": "Mon", + "Tue": "Tue", + "Wed": "Wed", + "Thu": "Thu", + "Fri": "Fri", + "Sat": "Sat", + "Second": "Second", + "Min": "Min", + "Hour": "Hour", + "Day": "Day", + "Month": "Month", + "Week": "Week", + "yes": "Yes", + "no": "No" +} diff --git a/app/src/locales/langs/en-US/cron.json b/app/src/locales/langs/en-US/cron.json new file mode 100644 index 0000000..cb967bb --- /dev/null +++ b/app/src/locales/langs/en-US/cron.json @@ -0,0 +1,18 @@ +{ + "cronExpressionGenerator": "CronExpressionGenerator", + "GenerateResult": "GenerateResult", + "CronExpression": "CronExpression", + "UseThisValue": "UseThisValue", + "CheckLast5ExecutionTimes": "CheckLast5ExecutionTimes", + "Last5ExecutionTimes": "Last5ExecutionTimes", + "ClickGenerate": "ClickGenerate", + "Per": "Per", + "NotSpecified": "NotSpecified", + "Cycle": "Cycle", + "From": "From", + "StartPer": "Start, Per", + "ExecuteOnce": "ExecuteOnce", + "Rang": "Rang", + "To": "To", + "Specify": "Specify" +} diff --git a/app/src/locales/langs/en-US/cropper.json b/app/src/locales/langs/en-US/cropper.json new file mode 100644 index 0000000..142d076 --- /dev/null +++ b/app/src/locales/langs/en-US/cropper.json @@ -0,0 +1,20 @@ +{ + "ImageCropping": "ImageCropping", + "message": { + "onlyImage": "Only image files can be uploaded!", + "imgSize": "Image size must not exceed {limit}MB!", + "uploadFailed": "Upload failed", + "notUrl": "Upload successful but no image URL returned", + "uploadSuccessful": "Upload successful!", + "reuploadSuccessful": "Re-upload successful!", + "notInitialized": "Cropper not initialized", + "cropFailed": "Crop failed, unable to get cropped image", + "fileCount": "Maximum {count} files allowed", + "avatarFormat": "Avatar must be {format} format only", + "avatarSize": "Avatar limit {limit} MB" + }, + "Uploading": "Uploading...", + "ConfirmCrop": "ConfirmCrop", + "Re-upload": "Re-upload", + "ClickToUpload": "ClickToUpload" +} diff --git a/app/src/locales/langs/en-US/datacenterTable.json b/app/src/locales/langs/en-US/datacenterTable.json new file mode 100644 index 0000000..fa9c56d --- /dev/null +++ b/app/src/locales/langs/en-US/datacenterTable.json @@ -0,0 +1,33 @@ +{ + "title": "DataCenter", + "id": "Id", + "deptId": "DeptId", + "tenantId": "TenantId", + "tableName": "TableName", + "tableDesc": "TableDesc", + "actualTable": "ActualTable", + "status": "Status", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "options": "Options", + "fields": "Fields", + "fieldName": "FiledName", + "fieldDesc": "Description", + "fieldType": "Type", + "required": "Required", + "noFieldError": "At least one field is required", + "fieldInfoError": "Field info is not completed", + "nameRegx": "It can only contain lowercase letters, numbers, and underscores, and must start with a lowercase letter", + "structure": "TableStructure", + "data": "Data", + "uploadTitle": "Click to upload", + "uploadDesc": "Choose an excel file to upload, file size limit 10MB.", + "downloadTemplate": "Download template", + "importComplete": "Import is completed", + "totalNum": "Total Count", + "successNum": "Success Count", + "failNum": "Fail Count", + "failList": "Fail List" +} diff --git a/app/src/locales/langs/en-US/datacenterTableFields.json b/app/src/locales/langs/en-US/datacenterTableFields.json new file mode 100644 index 0000000..a125757 --- /dev/null +++ b/app/src/locales/langs/en-US/datacenterTableFields.json @@ -0,0 +1,13 @@ +{ + "id": "Id", + "tableId": "TableId", + "fieldName": "FieldName", + "fieldDesc": "FieldDesc", + "fieldType": "FieldType", + "required": "Required", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "options": "Options" +} diff --git a/app/src/locales/langs/en-US/demos.json b/app/src/locales/langs/en-US/demos.json new file mode 100644 index 0000000..f6f01bd --- /dev/null +++ b/app/src/locales/langs/en-US/demos.json @@ -0,0 +1,14 @@ +{ + "title": "Demos", + "elementPlus": "Element Plus", + "form": "Form", + "aiflowy": { + "title": "Project", + "about": "About", + "document": "Document", + "antdv": "Ant Design Vue Version", + "naive-ui": "Naive UI Version", + "element-plus": "Element Plus Version", + "tdesign": "TDesign Vue Version" + } +} diff --git a/app/src/locales/langs/en-US/dictSelect.json b/app/src/locales/langs/en-US/dictSelect.json new file mode 100644 index 0000000..db64bc8 --- /dev/null +++ b/app/src/locales/langs/en-US/dictSelect.json @@ -0,0 +1,4 @@ +{ + "placeholder": "Please select", + "getError": "Get Data Error!" +} diff --git a/app/src/locales/langs/en-US/documentCollection.json b/app/src/locales/langs/en-US/documentCollection.json new file mode 100644 index 0000000..bcbc238 --- /dev/null +++ b/app/src/locales/langs/en-US/documentCollection.json @@ -0,0 +1,81 @@ +{ + "id": "Id", + "alias": "Alias", + "deptId": "DeptId", + "tenantId": "TenantId", + "icon": "Icon", + "title": "Title", + "description": "Description", + "slug": "Slug", + "vectorStoreEnable": "VectorStoreEnable", + "vectorStoreType": "VectorStoreType", + "vectorStoreCollection": "VectorStoreCollection", + "vectorStoreConfig": "VectorStoreConfig", + "vectorEmbedLlmId": "VectorEmbedLlm", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "options": "Options", + "rerankLlmId": "RerankLlm", + "searchEngineEnable": "SearchEngineEnable", + "englishName": "EnglishName", + "documentType": "DocumentType", + "fileName": "fileName", + "knowledgeCount": "Number of knowledge items", + "createdModifyTime": "Creation/update time", + "documentList": "documentList", + "knowledgeRetrieval": "knowledgeRetrieval", + "sorting": "Sorting", + "content": "Content", + "placeholder": { + "title": "Please input title", + "description": "Please provide a description so that the large model can better understand the knowledge base and make calls", + "englishName": "Please enter an English name", + "alias": "Please enter an alias, Chinese is not allowed", + "embedLlm": "Please choose a vector model", + "rerankLlm": "Please choose to rearrange the model", + "vectorStoreCollection": "Can only contain letters, numbers, and underscores with a length between 3-20 characters", + "vectorStoreType": "Please select the vector database type" + }, + "importDoc": { + "fileUpload": "File upload", + "parameterSettings": "ParameterSettings", + "segmentedPreview": "SegmentedPreview", + "confirmImport": "ConfirmImport", + "fileName": "File Name", + "progressUpload": "Progress of file upload", + "fileSize": "File size" + }, + "splitterDoc": { + "fileType": "FileType", + "splitterName": "Segmenter", + "chunkSize": "SegmentLength", + "overlapSize": "SegmentOverlap", + "regex": "RegularExpression", + "document": "Document", + "simpleDocumentSplitter": "SimpleDocumentSplitter", + "simpleTokenizeSplitter": "SimpleTokenizeSplitter", + "regexDocumentSplitter": "RegexDocumentSplitter", + "uploadStatus": "UploadStatus", + "pendingUpload": "PendingUpload", + "completed": "Completed", + "uploading": "Parsing in progress", + "importSuccess": "ImportSuccess" + }, + "documentManagement": "Document management", + "actions": { + "knowledge": "Knowledge", + "retrieve": "Retrieve", + "addKnowledge": "AddKnowledge", + "confirmImport": "ConfirmImport", + "cancelImport": "CancelImport" + }, + "searchResults": "SearchResults", + "documentPreview": "DocumentPreview", + "total": "Total", + "segments": "Segments", + "similarityScore": "SimilarityScore", + "alibabaCloud": "AlibabaCloud", + "tencentCloud": "tencentCloud" +} diff --git a/app/src/locales/langs/en-US/headerSearch.json b/app/src/locales/langs/en-US/headerSearch.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/app/src/locales/langs/en-US/headerSearch.json @@ -0,0 +1 @@ +{} diff --git a/app/src/locales/langs/en-US/llm.json b/app/src/locales/langs/en-US/llm.json new file mode 100644 index 0000000..41709b1 --- /dev/null +++ b/app/src/locales/langs/en-US/llm.json @@ -0,0 +1,79 @@ +{ + "filed": + { + "id": "Id", + "deptId": "DeptId", + "tenantId": "TenantId", + "title": "Title", + "brand": "Brand", + "icon": "Icon", + "description": "Description", + "supportChat": "SupportChat", + "supportFunctionCalling": "SupportFunctionCalling", + "supportEmbed": "SupportEmbed", + "supportReranker": "SupportReranker", + "supportTextToImage": "SupportTextToImage", + "supportImageToImage": "SupportImageToImage", + "supportTextToAudio": "SupportTextToAudio", + "supportAudioToAudio": "SupportAudioToAudio", + "supportTextToVideo": "SupportTextToVideo", + "supportImageToVideo": "SupportImageToVideo", + "multimodal": "multimodal", + "llmEndpoint": "LlmEndpoint", + "chatPath": "ChatPath", + "embedPath": "embedPath", + "llmModel": "LlmModel", + "llmApiKey": "apiKey", + "llmExtraConfig": "LlmExtraConfig", + "options": "Options", + "ability": "Ability" + } + , + "llmModal": { + "TitleRequired": "Please enter the name", + "BrandRequired": "Please enter the brand", + "ModelRequired": "Please enter the model", + "ApiKeyRequired": "Please enter the apiKey", + "QuickAddLlm": "One-click addition of large models" + }, + "placeholder": { + "title": "Please enter the title", + "brand": "Please enter the brand", + "llmModel": "Please enter the llmModel", + "description": "Please enter the description" + }, + "actions": { + "verifyConfiguration": "Verify Configuration" + }, + "message": { + "verifySuccess": "Verification successful" + }, + "addProvider": "Provider list", + "modelType": "ModelType", + "llmModel": "ModelName", + "title": "name", + "groupName": "GroupName", + "provider": "供应商", + "ability": "ModelAbility", + "button": { + "management": "Management", + "test": "Test", + "addAllLlm": "Add models from the list", + "RetrieveAgain": "Retrieve the model list again" + }, + "all": "All", + "verifyLlmTitle": "Verify Large Model", + "searchTextPlaceholder": "Search for model name or name", + "testSuccess": "TestSuccess", + "modelAbility": { + "supportThinking": "Thinking", + "supportTool": "Tool", + "SupportAudio": "Audio", + "SupportVideo": "Video", + "SupportImage": "Image", + "supportFree": "Free", + "supportImageB64Only": "ImageB64Only", + "supportToolMessage": "SupportToolMessage" + }, + "requestPath": "RequestPath" +} diff --git a/app/src/locales/langs/en-US/llmProvider.json b/app/src/locales/langs/en-US/llmProvider.json new file mode 100644 index 0000000..b0e9d6f --- /dev/null +++ b/app/src/locales/langs/en-US/llmProvider.json @@ -0,0 +1,15 @@ +{ + "providerName": "ProviderName", + "provider": "Provider", + "icon": "Icon", + "apiKey": "ApiKey", + "endpoint": "APIAddress", + "embedPath": "EmbedPath", + "chatPath": "ChatPath", + "rerankPath": "RerankPath", + "embeddingModel": "EmbeddingModel", + "chatModel": "ChatModel", + "rerankModel": "RerankModel", + "model": "Model", + "apiType": "ApiType" +} diff --git a/app/src/locales/langs/en-US/mcp.json b/app/src/locales/langs/en-US/mcp.json new file mode 100644 index 0000000..01c823f --- /dev/null +++ b/app/src/locales/langs/en-US/mcp.json @@ -0,0 +1,20 @@ +{ + "id": "Id", + "title": "Title", + "description": "Description", + "configJson": "ConfigJson", + "deptId": "DeptId", + "tenantId": "TenantId", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "status": "Status", + "modal": { + "config": "Config", + "tool": "Tool", + "table": { + "availableTools": "Available Tools" + } + } +} diff --git a/app/src/locales/langs/en-US/menus.json b/app/src/locales/langs/en-US/menus.json new file mode 100644 index 0000000..342b95d --- /dev/null +++ b/app/src/locales/langs/en-US/menus.json @@ -0,0 +1,33 @@ +{ + "system": { + "title": "System", + "sysAccount": "Account", + "sysRole": "Role", + "sysMenu": "Menu", + "sysDept": "Department", + "sysPosition": "Position", + "sysDict": "Dictionary", + "sysJob": "Job", + "sysLog": "Log", + "sysFeedback": "UserFeedback", + "sysAppearance": "Appearance", + "oauth": "OAuth" + }, + "ai": { + "bots": "ChatAssistant", + "title": "AI", + "resources": "Resources", + "datacenter": "Datacenter", + "workflow": "Workflow", + "plugin": "Plugin", + "model": "Model", + "documentCollection": "DocumentCollection", + "knowledge": "DocumentManagement", + "mcp": "MCP" + }, + "settings": { + "title": "SettingsConfiguration", + "settingsConfig": "SettingsConfig", + "apiKey": "API Key" + } +} diff --git a/app/src/locales/langs/en-US/message.json b/app/src/locales/langs/en-US/message.json new file mode 100644 index 0000000..0cfb8e9 --- /dev/null +++ b/app/src/locales/langs/en-US/message.json @@ -0,0 +1,46 @@ +{ + "deleteAlert": "Do you want to delete this item?", + "noticeTitle": "Notice", + "ok": "Yes", + "cancel": "No", + "required": "Item is required", + "saveOkMessage": "Saved successfully", + "saveFailMessage": "Save failed", + "deleteOkMessage": "Deleted successfully", + "deleteFailMessage": "Delete failed", + "loading": "Loading...", + "getDataError": "Get data error", + "updateOkMessage": "Updated successfully", + "startAlert": "Are you sure to start?", + "stopAlert": "Are you sure to stop?", + "preview": "Preview", + "notEmpty": "Can not be empty", + "success": "Success", + "fail": "Fail", + "pleaseSelect": "Please Select {name}", + "pleaseInputContent": "Please input content", + "notSupported": "Not supported yet", + "englishNameRule": "Rural subsistence allowance please enter characters consisting of letters, numbers, underscores, and hyphens, with a length not exceeding 64 characters", + "downloadSuccess": "Download success", + "downloadFail": "Download fail", + "copySuccess": "Copy success", + "copyFail": "Copy fail", + "upload": { + "title": "Click or drag and drop files here to upload", + "description": "TXT, PDF, DOCX, MD, PPT, PPTX, and XLSX files are supported. Each upload allows one file only, with a maximum size of 20M per file." + }, + "uploadFileFirst": "Please upload the file first", + "deleteModelAlert": "This operation will delete the large model. Are you sure to delete it?", + "deleteModelGroupAlert": "This operation will delete all large models under this group. Are you sure to delete them?", + "cannotBeEmpty": { + "name": "Parameter name cannot be empty", + "description": "Parameter description cannot be empty", + "method": "Input method cannot be empty", + "type": "Parameter type cannot be empty", + "all": "Please complete all required fields before submitting", + "error": "Parameter validation failed. Please complete all required fields" + }, + "completeForm": "Please complete the form information", + "notVideo": "Your browser does not support the video element.", + "notAudio": "Your browser does not support the audio element." +} diff --git a/app/src/locales/langs/en-US/page.json b/app/src/locales/langs/en-US/page.json new file mode 100644 index 0000000..fada1d6 --- /dev/null +++ b/app/src/locales/langs/en-US/page.json @@ -0,0 +1,23 @@ +{ + "auth": { + "login": "Login", + "register": "Register", + "codeLogin": "Code Login", + "qrcodeLogin": "Qr Code Login", + "forgetPassword": "Forget Password", + "profile": "Profile", + "accountPassword": "Account Password", + "systemMessage": "System Message", + "todoTasks": "Todo Tasks" + }, + "dashboard": { + "title": "Dashboard", + "analytics": "Analytics", + "workspace": "Workspace" + }, + "description": { + "accountPassword": "Messages from other users will be notified via site message", + "systemMessage": "System messages will be notified via site message", + "todoTasks": "Todo tasks will be notified via site message." + } +} diff --git a/app/src/locales/langs/en-US/plugin.json b/app/src/locales/langs/en-US/plugin.json new file mode 100644 index 0000000..a2589e4 --- /dev/null +++ b/app/src/locales/langs/en-US/plugin.json @@ -0,0 +1,33 @@ +{ + "pluginCategory": "Plugin category", + "id": "Id", + "alias": "Alias", + "name": "Name", + "description": "Description", + "type": "Type", + "baseUrl": "BaseUrl", + "authType": "AuthType", + "created": "Created", + "icon": "Icon", + "position": "Position", + "headers": "Headers", + "tokenKey": "TokenKey", + "tokenValue": "TokenValue", + "deptId": "DeptId", + "tenantId": "TenantId", + "createdBy": "CreatedBy", + "category": "Category", + "placeholder": { + "name": "Please enter plugin name", + "description": "Please enter plugin description", + "categorize": "Please enter categorize" + }, + "button": { + "addPlugin": "Add Plugin", + "categorize": "categorize", + "tools": "tools" + }, + "toolsManagement": "Tools Management", + "searchUsers": "Search Users", + "parameterValue": "ParameterValue" +} diff --git a/app/src/locales/langs/en-US/pluginItem.json b/app/src/locales/langs/en-US/pluginItem.json new file mode 100644 index 0000000..54dc215 --- /dev/null +++ b/app/src/locales/langs/en-US/pluginItem.json @@ -0,0 +1,35 @@ +{ + "id": "Id", + "pluginId": "PluginId", + "name": "Tool name", + "description": "Description", + "basePath": "BasePath", + "created": "Created", + "status": "Status", + "inputData": "InputData", + "outputData": "OutputData", + "requestMethod": "RequestMethod", + "serviceStatus": "ServiceStatus", + "debugStatus": "DebugStatus", + "englishName": "EnglishName", + "createPluginTool": "Create tool", + "pluginToolEdit": { + "basicInfo": "Basic Info", + "configureInputParameters": "Configure input parameters", + "configureOutputParameters": "Configure output parameters", + "trialRun": "Trial run", + "toolPath": "Tool path", + "requestMethod": "RequestMethod", + "runResult": "Run result", + "run": "run" + }, + "parameterName": "Name", + "parameterDescription": "Description", + "parameterType": "Type", + "inputMethod": "InputMethod", + "required": "Required", + "defaultValue": "DefaultValue", + "enabledStatus": "EnabledStatus", + "addChildNode": "AddChildNode", + "addParameter": "Add Parameter" +} diff --git a/app/src/locales/langs/en-US/settingsConfig.json b/app/src/locales/langs/en-US/settingsConfig.json new file mode 100644 index 0000000..171e521 --- /dev/null +++ b/app/src/locales/langs/en-US/settingsConfig.json @@ -0,0 +1,10 @@ +{ + "title": "Large Model Configuration", + "modelOfChat": "Chat Model Provider", + "dialogModel": "Chat Model Settings", + "modelName": "Model Name", + "basic": "BasicInformation", + "updatePwd": "UpdatePassword", + "systemAIFunctionSettings": "System AI Function Settings", + "note": "Note: This config only applies to system AI features, not [Chat Assistant]." +} diff --git a/app/src/locales/langs/en-US/sysAccount.json b/app/src/locales/langs/en-US/sysAccount.json new file mode 100644 index 0000000..e7796dc --- /dev/null +++ b/app/src/locales/langs/en-US/sysAccount.json @@ -0,0 +1,27 @@ +{ + "id": "Id", + "deptId": "Dept", + "tenantId": "TenantId", + "loginName": "LoginName", + "password": "Password", + "accountType": "AccountType", + "nickname": "Nickname", + "mobile": "Mobile", + "email": "Email", + "avatar": "Avatar", + "dataScope": "DataScope", + "deptIdList": "DeptIdList", + "status": "Status", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "remark": "Remark", + "isDeleted": "IsDeleted", + "roleIds": "Role", + "oldPwd": "OldPassword", + "newPwd": "NewPassword", + "confirmPwd": "ConfirmPassword", + "repeatPwd": "Please confirm your password again", + "notSamePwd": "The two passwords are inconsistent" +} diff --git a/app/src/locales/langs/en-US/sysApiKey.json b/app/src/locales/langs/en-US/sysApiKey.json new file mode 100644 index 0000000..19cc1ec --- /dev/null +++ b/app/src/locales/langs/en-US/sysApiKey.json @@ -0,0 +1,18 @@ +{ + "id": "Id", + "apiKey": "ApiKey", + "created": "Created", + "status": "Status", + "deptId": "DeptId", + "tenantId": "TenantId", + "expiredAt": "ExpiredAt", + "createdBy": "CreatedBy", + "addApiKey": "addApiKey", + "actions": { + "enable": "Enable", + "disable": "NotDisable", + "failure": "Failure" + }, + "permissions": "AuthInterface", + "addApiKeyNotice": "This operation will generate an API key. Please confirm whether to proceed" +} diff --git a/app/src/locales/langs/en-US/sysApiKeyResourcePermission.json b/app/src/locales/langs/en-US/sysApiKeyResourcePermission.json new file mode 100644 index 0000000..484e442 --- /dev/null +++ b/app/src/locales/langs/en-US/sysApiKeyResourcePermission.json @@ -0,0 +1,6 @@ +{ + "id": "Id", + "requestInterface": "RequestInterface", + "title": "Title", + "addPermission": "AddRequestInterface" +} diff --git a/app/src/locales/langs/en-US/sysAppearance.json b/app/src/locales/langs/en-US/sysAppearance.json new file mode 100644 index 0000000..bb0dae1 --- /dev/null +++ b/app/src/locales/langs/en-US/sysAppearance.json @@ -0,0 +1,24 @@ +{ + "Theme and Color Scheme": "Theme and Color Scheme", + "Theme Mode": "Theme Mode", + "Theme Color": "Theme Color", + "Layout & Navigation": "Layout & Navigation", + "Layout Mode": "Layout Mode", + "Interface Display": "Interface Display", + "Page Tabs": "Page Tabs", + "Animation": "Animation", + "Login Page Appearance": "Login Page Appearance", + "Login Page Layout": "Login Page Layout", + "Login Page Image": "Login Page Image", + "OnlyJPG": "Only .jpg format supported", + "Login Page Brand Copy": "Login Page Brand Copy", + "Welcome Title": "Welcome Title", + "Please enter the welcome title": "Please enter the welcome title", + "Welcome Description": "Welcome Description", + "Please enter the welcome description": "Please enter the welcome description", + "Slogan Title": "Slogan Title", + "Please enter the slogan title": "Please enter the slogan title", + "Slogan Description": "Slogan Description", + "Please enter the slogan description": "Please enter the slogan description", + "Thumbnail": "Thumbnail" +} diff --git a/app/src/locales/langs/en-US/sysDept.json b/app/src/locales/langs/en-US/sysDept.json new file mode 100644 index 0000000..9f9cbd4 --- /dev/null +++ b/app/src/locales/langs/en-US/sysDept.json @@ -0,0 +1,17 @@ +{ + "root": "Root", + "id": "Id", + "tenantId": "TenantId", + "parentId": "Parent", + "ancestors": "Ancestors", + "deptName": "DeptName", + "deptCode": "DeptCode", + "sortNo": "SortNo", + "status": "Status", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "remark": "Remark", + "isDeleted": "IsDeleted" +} diff --git a/app/src/locales/langs/en-US/sysDict.json b/app/src/locales/langs/en-US/sysDict.json new file mode 100644 index 0000000..4d39378 --- /dev/null +++ b/app/src/locales/langs/en-US/sysDict.json @@ -0,0 +1,12 @@ +{ + "id": "Id", + "name": "Name", + "code": "Code", + "description": "Description", + "dictType": "DictType", + "sortNo": "SortNo", + "status": "Status", + "options": "Options", + "created": "Created", + "modified": "Modified" +} diff --git a/app/src/locales/langs/en-US/sysFeedback.json b/app/src/locales/langs/en-US/sysFeedback.json new file mode 100644 index 0000000..7905c58 --- /dev/null +++ b/app/src/locales/langs/en-US/sysFeedback.json @@ -0,0 +1,20 @@ +{ + "feedbackType": "FeedbackType", + "processingStatus": "ProcessingStatus", + "category": "Category", + "description": "Description", + "contactInformation": "ContactInformation", + "submittedAt": "SubmittedAt", + "functionalFailure": "FunctionalFailure", + "optimizationSuggestions": "OptimizationSuggestions", + "accountIssue": "AccountIssue", + "other": "Other", + "notViewed": "NotViewed", + "viewed": "Viewed", + "processed": "Processed", + "closed/Invalid": "Closed/Invalid", + "markedSuccessfully": "Marked Successfully!", + "basicInformation": "BasicInformation", + "feedbackContent": "FeedbackContent", + "attachments": "Attachments" +} diff --git a/app/src/locales/langs/en-US/sysJob.json b/app/src/locales/langs/en-US/sysJob.json new file mode 100644 index 0000000..35437af --- /dev/null +++ b/app/src/locales/langs/en-US/sysJob.json @@ -0,0 +1,23 @@ +{ + "id": "Id", + "deptId": "DeptId", + "tenantId": "TenantId", + "jobName": "JobName", + "jobType": "JobType", + "jobParams": "JobParams", + "cronExpression": "CronExpression", + "allowConcurrent": "AllowConcurrent", + "misfirePolicy": "MisfirePolicy", + "options": "Options", + "status": "Status", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "remark": "Remark", + "isDeleted": "IsDeleted", + "workflow": "Workflow", + "beanMethod": "BeanMethod", + "javaMethod": "JavaMethod", + "example": "example" +} diff --git a/app/src/locales/langs/en-US/sysJobLog.json b/app/src/locales/langs/en-US/sysJobLog.json new file mode 100644 index 0000000..521ac9b --- /dev/null +++ b/app/src/locales/langs/en-US/sysJobLog.json @@ -0,0 +1,14 @@ +{ + "title": "JobLog", + "id": "Id", + "jobId": "JobId", + "jobName": "JobName", + "jobParams": "JobParams", + "jobResult": "JobResult", + "errorInfo": "ErrorInfo", + "status": "Status", + "startTime": "StartTime", + "endTime": "EndTime", + "created": "Created", + "remark": "Remark" +} diff --git a/app/src/locales/langs/en-US/sysLog.json b/app/src/locales/langs/en-US/sysLog.json new file mode 100644 index 0000000..651d7be --- /dev/null +++ b/app/src/locales/langs/en-US/sysLog.json @@ -0,0 +1,14 @@ +{ + "id": "Id", + "accountId": "AccountId", + "actionName": "ActionName", + "actionType": "ActionType", + "actionClass": "ActionClass", + "actionMethod": "ActionMethod", + "actionUrl": "ActionUrl", + "actionIp": "ActionIp", + "actionParams": "ActionParams", + "actionBody": "ActionBody", + "status": "Status", + "created": "Created" +} diff --git a/app/src/locales/langs/en-US/sysMenu.json b/app/src/locales/langs/en-US/sysMenu.json new file mode 100644 index 0000000..acda776 --- /dev/null +++ b/app/src/locales/langs/en-US/sysMenu.json @@ -0,0 +1,20 @@ +{ +"root":"Top", +"id": "Id", +"parentId": "Parent", +"menuType": "MenuType", +"menuTitle": "MenuTitle", +"menuUrl": "MenuUrl", +"component": "Component", +"menuIcon": "MenuIcon", +"isShow": "IsShow", +"permissionTag": "PermissionTag", +"sortNo": "SortNo", +"status": "Status", +"created": "Created", +"createdBy": "CreatedBy", +"modified": "Modified", +"modifiedBy": "ModifiedBy", +"remark": "Remark", +"isDeleted": "IsDeleted" +} diff --git a/app/src/locales/langs/en-US/sysOption.json b/app/src/locales/langs/en-US/sysOption.json new file mode 100644 index 0000000..614f41d --- /dev/null +++ b/app/src/locales/langs/en-US/sysOption.json @@ -0,0 +1,4 @@ +{ + "oauthWxWeb": "WechatLogin", + "oauthDingTalk": "DingTalkLogin" +} diff --git a/app/src/locales/langs/en-US/sysPosition.json b/app/src/locales/langs/en-US/sysPosition.json new file mode 100644 index 0000000..a3d2a53 --- /dev/null +++ b/app/src/locales/langs/en-US/sysPosition.json @@ -0,0 +1,24 @@ +{ + "id": "Id", + "tenantId": "TenantId", + "deptId": "Dept", + "positionName": "PositionName", + "positionCode": "PositionCode", + "sortNo": "SortNo", + "status": "Status", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "remark": "Remark", + "isDeleted": "IsDeleted", + "enable": "Enable", + "disable": "Disable", + "message": { + "title": "Confirm to {actionText} \"{positionName}\"?" + }, + "placeholder": { + "positionName": "Please enter position name", + "positionCode": "Please enter position code" + } +} diff --git a/app/src/locales/langs/en-US/sysRole.json b/app/src/locales/langs/en-US/sysRole.json new file mode 100644 index 0000000..8759257 --- /dev/null +++ b/app/src/locales/langs/en-US/sysRole.json @@ -0,0 +1,17 @@ +{ + "id": "Id", + "tenantId": "TenantId", + "roleName": "RoleName", + "roleKey": "RoleKey", + "status": "Status", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "remark": "Remark", + "isDeleted": "IsDeleted", + "menuPermission": "MenuPermission", + "dataPermission": "DataPermission", + "checkStrictlyTrue": "Linked", + "checkStrictlyFalse": "NotLinked" +} diff --git a/app/src/locales/langs/zh-CN/aiResource.json b/app/src/locales/langs/zh-CN/aiResource.json new file mode 100644 index 0000000..999dc3e --- /dev/null +++ b/app/src/locales/langs/zh-CN/aiResource.json @@ -0,0 +1,20 @@ +{ + "id": "主键", + "deptId": "部门ID", + "tenantId": "租户ID", + "resourceType": "素材类型", + "resourceName": "素材名称", + "suffix": "后缀", + "resourceUrl": "素材地址", + "origin": "素材来源", + "status": "数据状态", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "options": "扩展项", + "isDeleted": "删除标识", + "fileSize": "文件大小", + "categoryId": "分类", + "choose": "选择素材" +} diff --git a/app/src/locales/langs/zh-CN/aiWorkflow.json b/app/src/locales/langs/zh-CN/aiWorkflow.json new file mode 100644 index 0000000..5d3ea6b --- /dev/null +++ b/app/src/locales/langs/zh-CN/aiWorkflow.json @@ -0,0 +1,71 @@ +{ + "id": "ID 主键", + "alias": "别名", + "deptId": "部门ID", + "tenantId": "租户ID", + "title": "名称", + "description": "描述", + "icon": "图标", + "content": "工作流设计的 JSON 内容", + "created": "创建时间", + "createdBy": "创建人", + "modified": "最后修改时间", + "modifiedBy": "最后修改的人", + "englishName": "英文名称", + "status": "在用户中心显示", + "categoryId": "分类", + "params": "执行参数", + "steps": "执行步骤", + "result": "执行结果", + "confirm": "有待确认的内容,请先确认!", + "completed": "流程已执行完毕,请重新发起。", + "fileContentExtraction": "文件内容提取", + "documentAddress": "文档地址", + "parsedText": "解析后的文本", + "resourceSync": "素材同步", + "originUrl": "源地址", + "savedUrl": "保存后的地址", + "saveOptions": "保存选项", + "image": "图片", + "video": "视频", + "audio": "音频", + "document": "文档", + "other": "其他", + "fileGeneration": "文件生成", + "fileSettings": "文件设置", + "fileDownloadURL": "文件下载地址", + "pluginSelect": "插件选择", + "saveData": "保存数据", + "dataToBeSaved": "待保存的数据", + "successInsertedRecords": "成功插入条数", + "dataTable": "数据表", + "queryData": "查询数据", + "queryResult": "查询结果", + "filterConditions": "过滤条件", + "limit": "限制条数", + "sqlQuery": "SQL 查询", + "subProcess": "子流程", + "workflowSelect": "工作流选择", + "bochaSearch": "博查搜索", + "descriptions": { + "fileContentExtraction": "提取 PDF 或者 Word 等文件中的文字内容", + "documentAddress": "文档的url地址", + "parsedText": "解析后的文本内容", + "resourceSync": "下载素材文件并保存到系统素材库", + "originUrl": "文件的源地址", + "resourceType": "请选择素材的类型", + "fileGeneration": "生成 Word、PDF、HTML 等文件供用户下载", + "fileType": "请选择生成的文件类型", + "fileDownloadURL": "生成后的文件地址", + "plugin": "选择定义好的插件", + "saveData": "保存数据到数据中枢", + "dataToBeSaved": "待保存的数据列表", + "dataTable": "请选择数据表", + "queryData": "查询数据中枢的数据", + "filterConditions": "如:name='张三' and age=21 or field = {{流程变量}}", + "sqlQuery": "通过 SQL 查询数据库", + "enterSQL": "请输入SQL语句", + "queryResultJson": "查询结果(json对象)", + "subProcess": "选择定义好的流程" + } +} diff --git a/app/src/locales/langs/zh-CN/aiWorkflowCategory.json b/app/src/locales/langs/zh-CN/aiWorkflowCategory.json new file mode 100644 index 0000000..45ce6ba --- /dev/null +++ b/app/src/locales/langs/zh-CN/aiWorkflowCategory.json @@ -0,0 +1,10 @@ +{ + "id": "主键", + "categoryName": "分类名称", + "sortNo": "排序", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "status": "数据状态" +} \ No newline at end of file diff --git a/app/src/locales/langs/zh-CN/aiWorkflowExecRecord.json b/app/src/locales/langs/zh-CN/aiWorkflowExecRecord.json new file mode 100644 index 0000000..25d4a6b --- /dev/null +++ b/app/src/locales/langs/zh-CN/aiWorkflowExecRecord.json @@ -0,0 +1,25 @@ +{ + "id": "主键", + "execKey": "执行标识", + "workflowId": "工作流ID", + "title": "标题", + "description": "描述", + "input": "输入", + "output": "输出", + "workflowJson": "工作流执行时的配置", + "startTime": "开始时间", + "endTime": "结束时间", + "tokens": "消耗总token", + "status": "状态", + "createdKey": "执行人标识[有可能是用户|外部|定时任务等情况]", + "createdBy": "执行人", + "errorInfo": "错误信息", + "moduleName": "执行记录", + "execTime": "耗时", + "status1": "运行中", + "status5": "挂起", + "status10": "错误", + "status20": "成功", + "status21": "失败", + "status22": "取消" +} diff --git a/app/src/locales/langs/zh-CN/aiWorkflowRecordStep.json b/app/src/locales/langs/zh-CN/aiWorkflowRecordStep.json new file mode 100644 index 0000000..a4d0f12 --- /dev/null +++ b/app/src/locales/langs/zh-CN/aiWorkflowRecordStep.json @@ -0,0 +1,22 @@ +{ + "id": "主键", + "recordId": "执行记录ID", + "execKey": "执行标识", + "nodeId": "节点ID", + "nodeName": "节点名称", + "input": "输入", + "output": "输出", + "nodeData": "节点信息", + "startTime": "开始时间", + "endTime": "结束时间", + "tokens": "消耗总token", + "status": "状态", + "errorInfo": "错误信息", + "moduleName": "步骤信息", + "execTime": "耗时", + "status1": "运行中", + "status6": "挂起", + "status10": "错误", + "status20": "成功", + "status21": "失败" +} diff --git a/app/src/locales/langs/zh-CN/bot.json b/app/src/locales/langs/zh-CN/bot.json new file mode 100644 index 0000000..b74429c --- /dev/null +++ b/app/src/locales/langs/zh-CN/bot.json @@ -0,0 +1,26 @@ +{ + "problemPresupposition": "问题预设", + "noPermission": "没有权限", + "llm": "大模型", + "temperature": "温度", + "maxReplyLength": "最大回复长度", + "historyCount": "携带历史条数", + "skill": "技能", + "conversationSettings": "对话设置", + "welcomeMessage": "欢迎语", + "deepThinking": "深度思考", + "enableDeepThinking": "是否启用深度思考", + "publish": "发布", + "postToWeChatOfficialAccount": "发布到微信公众号", + "configured": "已配置", + "notConfigured": "未配置", + "placeholder": { + "welcome": "请输入欢迎语", + "prompt": "你是一个AI助手,请根据用户的问题给出清晰、准确的回答。", + "permission": "你没有配置bot的权限!" + }, + "systemPrompt": "系统提示词", + "aiOptimization": "AI优化", + "weChatOfficialAccountConfiguration": "微信公众号配置", + "aiOptimizedPrompts": "AI优化提示词" +} diff --git a/app/src/locales/langs/zh-CN/button.json b/app/src/locales/langs/zh-CN/button.json new file mode 100644 index 0000000..ee092d8 --- /dev/null +++ b/app/src/locales/langs/zh-CN/button.json @@ -0,0 +1,46 @@ +{ + "query": "查询", + "reset": "重置", + "add": "添加", + "edit": "编辑", + "delete": "删除", + "export": "导出", + "import": "导入", + "save": "保存", + "cancel": "取消", + "confirm": "确认", + "addLlm": "新增大模型", + "oneClickAdd": "一键添加", + "newConversation": "新建会话", + "start": "启动", + "stop": "停止", + "log": "日志", + "back": "返回", + "importFile": "导入文件", + "view": "查看", + "download": "下载", + "upload": "上传", + "preview": "预览", + "nextStep": "下一步", + "previousStep": "上一步", + "addLine": "增加行", + "batchImport": "批量导入", + "startImport": "开始导入", + "design": "设计", + "run": "运行", + "runTest": "试运行", + "copy": "复制", + "selectAll": "全选", + "choose": "选择", + "setting": "设置", + "create": "创建", + "update": "更新", + "oneClickOptimization": "一键优化", + "replace": "替换", + "markAsRead": "标记已查看", + "markAsResolved": "标记已处理", + "optimizing": "正在优化中...", + "regenerate": "重新生成", + "hide": "隐藏", + "more": "更多" +} diff --git a/app/src/locales/langs/zh-CN/common.json b/app/src/locales/langs/zh-CN/common.json new file mode 100644 index 0000000..552d2d9 --- /dev/null +++ b/app/src/locales/langs/zh-CN/common.json @@ -0,0 +1,25 @@ +{ + "handle": "操作", + "searchPlaceholder": "请输入搜索内容", + "allCategories": "全部", + "history": "历史记录", + "noDataAvailable": "暂无数据", + "isRequired": "不能为空", + "avatar": "头像", + "otherLoginType": "其他登录方式", + "Sun": "周日", + "Mon": "周一", + "Tue": "周二", + "Wed": "周三", + "Thu": "周四", + "Fri": "周五", + "Sat": "周六", + "Second": "秒", + "Min": "分", + "Hour": "时", + "Day": "日", + "Month": "月", + "Week": "周", + "yes": "是", + "no": "否" +} diff --git a/app/src/locales/langs/zh-CN/cron.json b/app/src/locales/langs/zh-CN/cron.json new file mode 100644 index 0000000..de4c1a1 --- /dev/null +++ b/app/src/locales/langs/zh-CN/cron.json @@ -0,0 +1,18 @@ +{ + "cronExpressionGenerator": "Cron 表达式生成器", + "GenerateResult": "生成结果", + "CronExpression": "Cron 表达式", + "UseThisValue": "使用该值", + "CheckLast5ExecutionTimes": "查看最近5次执行时间", + "Last5ExecutionTimes": "最近5次执行时间", + "ClickGenerate": "点击生成", + "Per": "每", + "NotSpecified": "不指定", + "Cycle": "周期", + "From": "从", + "StartPer": "开始,每", + "ExecuteOnce": "执行一次", + "Rang": "区间", + "To": "至", + "Specify": "指定" +} diff --git a/app/src/locales/langs/zh-CN/cropper.json b/app/src/locales/langs/zh-CN/cropper.json new file mode 100644 index 0000000..af8ccbc --- /dev/null +++ b/app/src/locales/langs/zh-CN/cropper.json @@ -0,0 +1,20 @@ +{ + "ImageCropping": "图片裁剪", + "message": { + "onlyImage": "只能上传图片文件!", + "imgSize": "图片大小不能超过 {limit}MB!", + "uploadFailed": "上传失败", + "notUrl": "上传成功但未返回图片URL", + "uploadSuccessful": "上传成功!", + "reuploadSuccessful": "重新上传成功!", + "notInitialized": "裁剪器未初始化", + "cropFailed": "裁剪失败,无法获取裁剪后的图片", + "fileCount": "最多只能上传 {count} 个文件", + "avatarFormat": "头像只能是{format}格式", + "avatarSize": "头像限制 {limit} MB" + }, + "Uploading": "上传中...", + "ConfirmCrop": "确认裁剪", + "Re-upload": "重新上传", + "ClickToUpload": "点击上传" +} diff --git a/app/src/locales/langs/zh-CN/datacenterTable.json b/app/src/locales/langs/zh-CN/datacenterTable.json new file mode 100644 index 0000000..4cbc381 --- /dev/null +++ b/app/src/locales/langs/zh-CN/datacenterTable.json @@ -0,0 +1,33 @@ +{ + "title": "数据中枢", + "id": "主键", + "deptId": "部门ID", + "tenantId": "租户ID", + "tableName": "数据表名", + "tableDesc": "数据表描述", + "actualTable": "物理表名", + "status": "数据状态", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "options": "扩展项", + "fields": "字段", + "fieldName": "字段名", + "fieldDesc": "字段描述", + "fieldType": "字段类型", + "required": "是否必填", + "noFieldError": "至少包含一个字段", + "fieldInfoError": "字段信息不完善", + "nameRegx": "只能包含小写字母、数字和下划线,且必须以小写字母开头", + "structure": "表结构", + "data": "数据", + "uploadTitle": "点击或将文件拖拽到这里上传", + "uploadDesc": "上传一份Excel文档,文件大小限制10MB以内。", + "downloadTemplate": "下载模板", + "importComplete": "导入完成", + "totalNum": "总数", + "successNum": "成功数", + "failNum": "失败数", + "failList": "失败记录" +} diff --git a/app/src/locales/langs/zh-CN/datacenterTableFields.json b/app/src/locales/langs/zh-CN/datacenterTableFields.json new file mode 100644 index 0000000..55abd14 --- /dev/null +++ b/app/src/locales/langs/zh-CN/datacenterTableFields.json @@ -0,0 +1,13 @@ +{ + "id": "主键", + "tableId": "数据表ID", + "fieldName": "字段名称", + "fieldDesc": "字段描述", + "fieldType": "字段类型", + "required": "是否必填", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "options": "扩展项" +} diff --git a/app/src/locales/langs/zh-CN/demos.json b/app/src/locales/langs/zh-CN/demos.json new file mode 100644 index 0000000..2959121 --- /dev/null +++ b/app/src/locales/langs/zh-CN/demos.json @@ -0,0 +1,14 @@ +{ + "title": "演示", + "elementPlus": "Element Plus", + "form": "表单演示", + "aiflowy": { + "title": "项目", + "about": "关于", + "document": "文档", + "antdv": "Ant Design Vue 版本", + "naive-ui": "Naive UI 版本", + "element-plus": "Element Plus 版本", + "tdesign": "TDesign Vue 版本" + } +} diff --git a/app/src/locales/langs/zh-CN/dictSelect.json b/app/src/locales/langs/zh-CN/dictSelect.json new file mode 100644 index 0000000..16798be --- /dev/null +++ b/app/src/locales/langs/zh-CN/dictSelect.json @@ -0,0 +1,4 @@ +{ + "placeholder": "请选择", + "getError": "获取字典数据失败" +} diff --git a/app/src/locales/langs/zh-CN/documentCollection.json b/app/src/locales/langs/zh-CN/documentCollection.json new file mode 100644 index 0000000..c10e82b --- /dev/null +++ b/app/src/locales/langs/zh-CN/documentCollection.json @@ -0,0 +1,81 @@ +{ + "id": "Id", + "alias": "别名", + "deptId": "部门ID", + "tenantId": "租户ID", + "icon": "ICON", + "title": "名称", + "description": "描述", + "slug": "URL 别名", + "vectorStoreEnable": "是否启用向量数据库", + "vectorStoreType": "向量数据库类型", + "vectorStoreCollection": "向量数据库集合", + "vectorStoreConfig": "向量数据库配置", + "vectorEmbedLlmId": "向量模型", + "created": "创建时间", + "createdBy": "创建用户ID", + "modified": "最后一次修改时间", + "modifiedBy": "最后一次修改用户ID", + "options": "其他配置", + "rerankLlmId": "重排模型", + "searchEngineEnable": "是否启用搜索引擎", + "englishName": "英文名称", + "documentType": "文件类型", + "fileName": "文件名", + "knowledgeCount": "知识条数", + "createdModifyTime": "创建/更新时间", + "documentList": "文档列表", + "knowledgeRetrieval": "知识检索", + "sorting": "排序", + "content": "内容", + "placeholder": { + "title": "请输入名称", + "description": "请输入描述,以便大模型更好的理解该知识库并且调用", + "englishName": "请输入英文名称", + "alias": "请输入别名,不允许含中文", + "embedLlm": "请选择向量模型", + "rerankLlm": "请选择重排模型", + "vectorStoreCollection": "只能包含字母、数字和下划线且长度在3-20个字符之间", + "vectorStoreType": "请选择向量数据库类型" + }, + "importDoc": { + "fileUpload": "文件上传", + "parameterSettings": "参数设置", + "segmentedPreview": "分段预览", + "confirmImport": "确认导入", + "fileName": "文件名称", + "progressUpload": "文件上传进度", + "fileSize": "文件大小" + }, + "splitterDoc": { + "fileType": "文件类型", + "splitterName": "分割器", + "chunkSize": "分段长度", + "overlapSize": "分段重叠", + "regex": "正则表达式", + "document": "文档", + "simpleDocumentSplitter": "简单文档分割器", + "simpleTokenizeSplitter": "简单分词器", + "regexDocumentSplitter": "正则文档分割器", + "uploadStatus": "上传状态", + "pendingUpload": "待上传", + "completed": "已完成", + "uploading": "解析中", + "importSuccess": "导入成功" + }, + "documentManagement": "文档管理", + "actions": { + "knowledge": "知识", + "retrieve": "检索", + "addKnowledge": "新增知识库", + "confirmImport": "确认导入", + "cancelImport": "取消导入" + }, + "searchResults": "检索结果", + "documentPreview": "文档预览", + "total": "共", + "segments": "个分段", + "similarityScore": "相似度", + "alibabaCloud": "阿里云", + "tencentCloud": "腾讯云" +} diff --git a/app/src/locales/langs/zh-CN/headerSearch.json b/app/src/locales/langs/zh-CN/headerSearch.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/app/src/locales/langs/zh-CN/headerSearch.json @@ -0,0 +1 @@ +{} diff --git a/app/src/locales/langs/zh-CN/llm.json b/app/src/locales/langs/zh-CN/llm.json new file mode 100644 index 0000000..11b6ca3 --- /dev/null +++ b/app/src/locales/langs/zh-CN/llm.json @@ -0,0 +1,76 @@ +{ + "filed": + { + "title": "标题", + "brand": "供应商", + "llmModel": "大模型名称", + "icon": "ICON", + "description": "描述", + "supportChat": "对话模型", + "supportFunctionCalling": "方法调用", + "supportEmbed": "向量化", + "supportReranker": "重排", + "supportTextToImage": "文生图", + "supportImageToImage": "图生图", + "supportTextToAudio": "文生音频", + "supportAudioToAudio": "音频转音频", + "supportTextToVideo": "文生视频", + "supportImageToVideo": "图生成视频", + "llmEndpoint": "请求地址", + "chatPath": "对话路径", + "embedPath": "向量化路径", + "multimodal": "多模态", + "llmApiKey": "apiKey", + "llmExtraConfig": "大模型其他属性配置", + "options": "其他配置内容", + "ability": "能力" + } +, + "llmModal": { + "TitleRequired": "请输入名称", + "BrandRequired": "请选择供应商", + "ModelRequired": "请输入大模型", + "ApiKeyRequired": "请输入apiKey", + "QuickAddLlm": "一键添加大模型" + }, + "placeholder": { + "title": "请输入名称", + "brand": "请选择品牌", + "llmModel": "请输入大模型名称", + "description": "请输入描述" + }, + "actions": { + "verifyConfiguration": "验证配置" + }, + "message": { + "verifySuccess": "验证成功" + }, + "addProvider": "供应商列表", + "modelType": "模型类型", + "llmModel": "模型名称", + "title": "名称", + "groupName": "分组名称", + "provider": "供应商", + "ability": "模型能力", + "button": { + "management": "管理", + "test": "检测", + "addAllLlm": "添加列表中的所有模型", + "RetrieveAgain": "重新获取模型列表" + }, + "all": "全部", + "verifyLlmTitle": "请选择要检测的模型", + "testSuccess": "检测成功", + "searchTextPlaceholder": "搜索模型名称或名称", + "modelAbility": { + "supportThinking": "推理", + "supportTool": "工具", + "supportAudio": "音频", + "supportVideo": "视频", + "supportImage": "图片", + "supportFree": "免费", + "supportImageB64Only": "仅支持Base64图片", + "supportToolMessage": "支持Tool消息" + }, + "requestPath": "请求路径" +} diff --git a/app/src/locales/langs/zh-CN/llmProvider.json b/app/src/locales/langs/zh-CN/llmProvider.json new file mode 100644 index 0000000..5bdc6f8 --- /dev/null +++ b/app/src/locales/langs/zh-CN/llmProvider.json @@ -0,0 +1,15 @@ +{ + "providerName": "供应商名称", + "provider": "供应商", + "icon": "Icon", + "apiKey": "API 密钥", + "endpoint": "API 地址", + "embedPath": "向量地址", + "chatPath": "对话地址", + "rerankPath": "重排地址", + "embeddingModel": "向量模型", + "chatModel": "对话模型", + "rerankModel": "重排模型", + "model": "模型", + "apiType": "API类型" +} diff --git a/app/src/locales/langs/zh-CN/mcp.json b/app/src/locales/langs/zh-CN/mcp.json new file mode 100644 index 0000000..f3b3c6a --- /dev/null +++ b/app/src/locales/langs/zh-CN/mcp.json @@ -0,0 +1,20 @@ +{ + "id": "id", + "title": "名称", + "description": "描述", + "configJson": "MCP配置JSON", + "deptId": "部门ID", + "tenantId": "租户ID", + "created": "创建时间", + "createdBy": "创建者ID", + "modified": "修改时间", + "modifiedBy": "修改者ID", + "status": "是否启用", + "modal": { + "config": "配置", + "tool": "工具", + "table": { + "availableTools": "可用工具" + } + } +} diff --git a/app/src/locales/langs/zh-CN/menus.json b/app/src/locales/langs/zh-CN/menus.json new file mode 100644 index 0000000..877cd7b --- /dev/null +++ b/app/src/locales/langs/zh-CN/menus.json @@ -0,0 +1,33 @@ +{ + "system": { + "title": "系统管理", + "sysAccount": "用户管理", + "sysRole": "角色管理", + "sysMenu": "菜单管理", + "sysDept": "部门管理", + "sysPosition": "岗位管理", + "sysDict": "字典管理", + "sysJob": "定时任务", + "sysLog": "日志管理", + "sysFeedback": "用户反馈", + "sysAppearance": "外观设置", + "oauth": "认证设置" + }, + "ai": { + "bots": "聊天助手", + "title": "AI能力", + "resources": "素材库", + "datacenter": "数据中枢", + "workflow": "工作流", + "plugin": "插件", + "model": "模型管理", + "documentCollection": "知识库", + "knowledge": "知识管理", + "mcp": "MCP" + }, + "settings": { + "title": "系统配置", + "settingsConfig": "系统设置", + "apiKey": "访问令牌" + } +} diff --git a/app/src/locales/langs/zh-CN/message.json b/app/src/locales/langs/zh-CN/message.json new file mode 100644 index 0000000..b41ccdf --- /dev/null +++ b/app/src/locales/langs/zh-CN/message.json @@ -0,0 +1,46 @@ +{ + "deleteAlert": "确定删除吗?", + "noticeTitle": "提示", + "ok": "确定", + "cancel": "取消", + "required": "该项为必填项", + "saveOkMessage": "保存成功!", + "saveFailMessage": "保存失败!", + "deleteOkMessage": "删除成功!", + "deleteFailMessage": "删除失败!", + "loading": "加载中...", + "getDataError": "获取数据失败", + "updateOkMessage": "更新成功!", + "startAlert": "确定启动吗?", + "stopAlert": "确定停止吗?", + "preview": "预览", + "notEmpty": "不能为空", + "success": "成功", + "fail": "失败", + "pleaseSelect": "请选择{name}", + "pleaseInputContent": "请输入内容", + "notSupported": "暂不支持", + "englishNameRule": "请输入由字母、数字、下划线、连字符组成的字符,且长度不超过64位", + "downloadSuccess": "下载成功", + "downloadFail": "下载失败", + "copySuccess": "复制成功", + "copyFail": "复制失败", + "upload": { + "title": "点击或将文件拖拽到这里上传", + "description": "TXT, PDF, DOCX, MD, PPT, PPTX,XLSX 格式文件,单个大小不超过20M。" + }, + "uploadFileFirst": "请先上传文件", + "deleteModelAlert": "该操作会删除大模型,确定删除吗?", + "deleteModelGroupAlert": "该操作会删除该分组下所有大模型,确定删除吗?", + "cannotBeEmpty": { + "name": "参数名称不能为空", + "description": "参数描述不能为空", + "method": "传入方法不能为空", + "type": "参数类型不能为空", + "all": "请完善所有必填项后提交", + "error": "参数校验失败,请完善必填项" + }, + "completeForm": "请完善表单信息", + "notVideo": "您的浏览器不支持 video 元素。", + "notAudio": "您的浏览器不支持 audio 元素。" +} diff --git a/app/src/locales/langs/zh-CN/page.json b/app/src/locales/langs/zh-CN/page.json new file mode 100644 index 0000000..7b61a5a --- /dev/null +++ b/app/src/locales/langs/zh-CN/page.json @@ -0,0 +1,23 @@ +{ + "auth": { + "login": "登录", + "register": "注册", + "codeLogin": "验证码登录", + "qrcodeLogin": "二维码登录", + "forgetPassword": "忘记密码", + "profile": "个人中心", + "accountPassword": "账户密码", + "systemMessage": "系统消息", + "todoTasks": "待办任务" + }, + "dashboard": { + "title": "概览", + "analytics": "分析页", + "workspace": "工作台" + }, + "description": { + "accountPassword": "其他用户的消息将以站内信的形式通知", + "systemMessage": "系统消息将以站内信的形式通知", + "todoTasks": "待办任务将以站内信的形式通知" + } +} diff --git a/app/src/locales/langs/zh-CN/plugin.json b/app/src/locales/langs/zh-CN/plugin.json new file mode 100644 index 0000000..a0444b2 --- /dev/null +++ b/app/src/locales/langs/zh-CN/plugin.json @@ -0,0 +1,33 @@ +{ + "pluginCategory": "插件分类", + "id": "插件id", + "alias": "别名", + "name": "名称", + "description": "描述", + "type": "类型", + "baseUrl": "基础URL", + "authType": "认证方式", + "created": "创建时间", + "icon": "图标地址", + "position": "认证参数位置", + "headers": "请求头", + "tokenKey": "token键", + "tokenValue": "token值", + "deptId": "部门id", + "tenantId": "租户id", + "createdBy": "创建人", + "category": "分类", + "placeholder": { + "name": "请输入插件名称", + "description": "请输入插件描述", + "categorize": "请选择分类" + }, + "button": { + "addPlugin": "新增插件", + "categorize": "归类", + "tools": "工具" + }, + "toolsManagement": "工具管理", + "searchUsers": "搜索用户", + "parameterValue": "参数值" +} diff --git a/app/src/locales/langs/zh-CN/pluginItem.json b/app/src/locales/langs/zh-CN/pluginItem.json new file mode 100644 index 0000000..781a3c9 --- /dev/null +++ b/app/src/locales/langs/zh-CN/pluginItem.json @@ -0,0 +1,35 @@ +{ + "id": "插件工具id", + "pluginId": "插件id", + "name": "工具名称", + "description": "工具描述", + "basePath": "基础路径", + "created": "创建时间", + "status": "是否启用", + "inputData": "输入参数", + "outputData": "输出参数", + "requestMethod": "请求方式【Post, Get, Put, Delete】", + "serviceStatus": "服务状态[0 下线 1 上线]", + "debugStatus": "调试状态【0失败 1成功】", + "englishName": "英文名称", + "createPluginTool": "创建工具", + "pluginToolEdit": { + "basicInfo": "基本信息", + "configureInputParameters": "配置输入参数", + "configureOutputParameters": "配置输出参数", + "trialRun": "试运行", + "toolPath": "工具路径", + "requestMethod": "请求方法", + "runResult": "运行结果", + "run": "运行" + }, + "parameterName": "参数名称", + "parameterDescription": "参数描述", + "parameterType": "参数类型", + "inputMethod": "传入方法", + "required": "是否必填", + "defaultValue": "默认值", + "enabledStatus": "启用状态", + "addChildNode": "添加子节点", + "addParameter": "新增参数" +} diff --git a/app/src/locales/langs/zh-CN/settingsConfig.json b/app/src/locales/langs/zh-CN/settingsConfig.json new file mode 100644 index 0000000..b9ec6c4 --- /dev/null +++ b/app/src/locales/langs/zh-CN/settingsConfig.json @@ -0,0 +1,10 @@ +{ + "title": "大模型配置", + "modelOfChat": "对话模型供应商", + "dialogModel": "对话模型配置", + "modelName": "模型名称", + "basic": "基本设置", + "updatePwd": "修改密码", + "systemAIFunctionSettings": "系统 AI 功能设置", + "note": "注意:此项配置,仅用于系统的 AI 功能,而非【聊天助手】。" +} diff --git a/app/src/locales/langs/zh-CN/sysAccount.json b/app/src/locales/langs/zh-CN/sysAccount.json new file mode 100644 index 0000000..a55262a --- /dev/null +++ b/app/src/locales/langs/zh-CN/sysAccount.json @@ -0,0 +1,28 @@ +{ + "id": "主键", + "deptId": "部门", + "tenantId": "租户ID", + "loginName": "登录账号", + "password": "密码", + "accountType": "账户类型", + "nickname": "昵称", + "mobile": "手机电话", + "email": "邮件", + "avatar": "账户头像", + "dataScope": "数据权限类型", + "deptIdList": "自定义部门权限", + "status": "是否启用", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "remark": "备注", + "isDeleted": "删除标识", + "roleIds": "角色", + "positionIds": "岗位", + "oldPwd": "旧密码", + "newPwd": "新密码", + "confirmPwd": "确认密码", + "repeatPwd": "请再次输入密码", + "notSamePwd": "两次输入的密码不一致" +} diff --git a/app/src/locales/langs/zh-CN/sysApiKey.json b/app/src/locales/langs/zh-CN/sysApiKey.json new file mode 100644 index 0000000..dac3fc0 --- /dev/null +++ b/app/src/locales/langs/zh-CN/sysApiKey.json @@ -0,0 +1,18 @@ +{ + "id": "id", + "apiKey": "apiKey", + "created": "创建时间", + "status": "数据状态", + "deptId": "部门id", + "tenantId": "租户id", + "expiredAt": "失效时间", + "createdBy": "创建人", + "addApiKey": "新增apiKey", + "actions": { + "enable": "启用", + "disable": "未启用", + "failure": "已失效" + }, + "permissions": "授权接口", + "addApiKeyNotice": "该操作会生成一个apiKey,请确认是否生成" +} diff --git a/app/src/locales/langs/zh-CN/sysApiKeyResourcePermission.json b/app/src/locales/langs/zh-CN/sysApiKeyResourcePermission.json new file mode 100644 index 0000000..51e9183 --- /dev/null +++ b/app/src/locales/langs/zh-CN/sysApiKeyResourcePermission.json @@ -0,0 +1,6 @@ +{ + "id": "id", + "requestInterface": "请求接口", + "title": "标题", + "addPermission": "添加请求接口" +} diff --git a/app/src/locales/langs/zh-CN/sysAppearance.json b/app/src/locales/langs/zh-CN/sysAppearance.json new file mode 100644 index 0000000..e6ed991 --- /dev/null +++ b/app/src/locales/langs/zh-CN/sysAppearance.json @@ -0,0 +1,24 @@ +{ + "Theme and Color Scheme": "主题与配色", + "Theme Mode": "主题模式", + "Theme Color": "主题色", + "Layout & Navigation": "布局与导航", + "Layout Mode": "布局模式", + "Interface Display": "界面显示", + "Page Tabs": "页面标签页", + "Animation": "动画", + "Login Page Appearance": "登录页外观", + "Login Page Layout": "登录页布局", + "Login Page Image": "登录页图片", + "OnlyJPG": "只支持.jpg 格式", + "Login Page Brand Copy": "登录页品牌文案", + "Welcome Title": "欢迎语标题", + "Please enter the welcome title": "请填写欢迎语标题", + "Welcome Description": "欢迎语描述", + "Please enter the welcome description": "请填写欢迎语描述", + "Slogan Title": "Slogn标题", + "Please enter the slogan title": "请填写Slogn标题", + "Slogan Description": "Slogn描述", + "Please enter the slogan description": "请填写Slogn描述", + "Thumbnail": "缩略图" +} diff --git a/app/src/locales/langs/zh-CN/sysDept.json b/app/src/locales/langs/zh-CN/sysDept.json new file mode 100644 index 0000000..e1f5130 --- /dev/null +++ b/app/src/locales/langs/zh-CN/sysDept.json @@ -0,0 +1,17 @@ +{ + "root": "根部门", + "id": "主键", + "tenantId": "租户ID", + "parentId": "父级", + "ancestors": "父级部门ID集合", + "deptName": "部门名称", + "deptCode": "部门编码", + "sortNo": "排序", + "status": "数据状态", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "remark": "备注", + "isDeleted": "删除标识" +} diff --git a/app/src/locales/langs/zh-CN/sysDict.json b/app/src/locales/langs/zh-CN/sysDict.json new file mode 100644 index 0000000..f9db052 --- /dev/null +++ b/app/src/locales/langs/zh-CN/sysDict.json @@ -0,0 +1,12 @@ +{ + "id": "主键", + "name": "数据字典名称", + "code": "字典编码", + "description": "字典描述或备注", + "dictType": "字典类型 1 自定义字典、2 数据表字典、 3 枚举类字典、 4 系统字典(自定义 DictLoader)", + "sortNo": "排序编号", + "status": "是否启用", + "options": "扩展字典 存放 json", + "created": "创建时间", + "modified": "修改时间" +} diff --git a/app/src/locales/langs/zh-CN/sysFeedback.json b/app/src/locales/langs/zh-CN/sysFeedback.json new file mode 100644 index 0000000..67a95a7 --- /dev/null +++ b/app/src/locales/langs/zh-CN/sysFeedback.json @@ -0,0 +1,20 @@ +{ + "feedbackType": "反馈类型", + "processingStatus": "处理状态", + "category": "问题类型", + "description": "问题摘要", + "contactInformation": "联系方式", + "submittedAt": "提交时间", + "functionalFailure": "功能故障", + "optimizationSuggestions": "优化建议", + "accountIssue": "账号问题", + "other": "其它", + "notViewed": "未查看", + "viewed": "已查看", + "processed": "已处理", + "closed/Invalid": "已关闭/无效", + "markedSuccessfully": "标记成功!", + "basicInformation": "基础信息", + "feedbackContent": "反馈内容", + "attachments": "附件" +} diff --git a/app/src/locales/langs/zh-CN/sysJob.json b/app/src/locales/langs/zh-CN/sysJob.json new file mode 100644 index 0000000..9813018 --- /dev/null +++ b/app/src/locales/langs/zh-CN/sysJob.json @@ -0,0 +1,23 @@ +{ + "id": "主键", + "deptId": "部门ID", + "tenantId": "租户ID", + "jobName": "任务名称", + "jobType": "任务类型", + "jobParams": "任务参数", + "cronExpression": "cron表达式", + "allowConcurrent": "是否并发执行", + "misfirePolicy": "错过策略", + "options": "其他配置", + "status": "任务状态", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "remark": "备注", + "isDeleted": "删除标识", + "workflow": "工作流", + "beanMethod": "bean方法", + "javaMethod": "java方法", + "example": "示例" +} diff --git a/app/src/locales/langs/zh-CN/sysJobLog.json b/app/src/locales/langs/zh-CN/sysJobLog.json new file mode 100644 index 0000000..32dabc1 --- /dev/null +++ b/app/src/locales/langs/zh-CN/sysJobLog.json @@ -0,0 +1,14 @@ +{ + "title": "任务日志", + "id": "主键", + "jobId": "任务ID", + "jobName": "任务名称", + "jobParams": "任务参数", + "jobResult": "执行结果", + "errorInfo": "错误信息", + "status": "执行状态", + "startTime": "开始时间", + "endTime": "结束时间", + "created": "创建时间", + "remark": "备注" +} diff --git a/app/src/locales/langs/zh-CN/sysLog.json b/app/src/locales/langs/zh-CN/sysLog.json new file mode 100644 index 0000000..82fd4fe --- /dev/null +++ b/app/src/locales/langs/zh-CN/sysLog.json @@ -0,0 +1,14 @@ +{ + "id": "ID", + "accountId": "操作人", + "actionName": "操作名称", + "actionType": "操作的类型", + "actionClass": "操作涉及的类", + "actionMethod": "操作涉及的方法", + "actionUrl": "操作涉及的 URL 地址", + "actionIp": "操作涉及的用户 IP 地址", + "actionParams": "操作请求参数", + "actionBody": "操作请求body", + "status": "操作状态 1 成功 9 失败", + "created": "操作时间" +} diff --git a/app/src/locales/langs/zh-CN/sysMenu.json b/app/src/locales/langs/zh-CN/sysMenu.json new file mode 100644 index 0000000..6aee2d5 --- /dev/null +++ b/app/src/locales/langs/zh-CN/sysMenu.json @@ -0,0 +1,20 @@ +{ +"root":"顶级", +"id": "主键", +"parentId": "父菜单", +"menuType": "菜单类型", +"menuTitle": "菜单标题", +"menuUrl": "菜单url", +"component": "组件路径", +"menuIcon": "图标", +"isShow": "是否显示", +"permissionTag": "权限标识", +"sortNo": "排序", +"status": "数据状态", +"created": "创建时间", +"createdBy": "创建者", +"modified": "修改时间", +"modifiedBy": "修改者", +"remark": "备注", +"isDeleted": "删除标识" +} diff --git a/app/src/locales/langs/zh-CN/sysOption.json b/app/src/locales/langs/zh-CN/sysOption.json new file mode 100644 index 0000000..0ac65bf --- /dev/null +++ b/app/src/locales/langs/zh-CN/sysOption.json @@ -0,0 +1,4 @@ +{ + "oauthWxWeb": "微信登录", + "oauthDingTalk": "钉钉登录" +} diff --git a/app/src/locales/langs/zh-CN/sysPosition.json b/app/src/locales/langs/zh-CN/sysPosition.json new file mode 100644 index 0000000..20c6c18 --- /dev/null +++ b/app/src/locales/langs/zh-CN/sysPosition.json @@ -0,0 +1,24 @@ +{ + "id": "主键", + "tenantId": "租户ID", + "deptId": "部门", + "positionName": "岗位名称", + "positionCode": "岗位编码", + "sortNo": "排序", + "status": "数据状态", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "remark": "备注", + "isDeleted": "删除标识", + "enable": "启用", + "disable": "禁用", + "message": { + "title": "确认要{actionText}\"{positionName}\"吗?" + }, + "placeholder": { + "positionName": "请输入岗位名称", + "positionCode": "请输入岗位编码" + } +} diff --git a/app/src/locales/langs/zh-CN/sysRole.json b/app/src/locales/langs/zh-CN/sysRole.json new file mode 100644 index 0000000..0b77250 --- /dev/null +++ b/app/src/locales/langs/zh-CN/sysRole.json @@ -0,0 +1,17 @@ +{ + "id": "主键", + "tenantId": "租户ID", + "roleName": "角色名称", + "roleKey": "角色标识", + "status": "是否启用", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "remark": "备注", + "isDeleted": "删除标识", + "menuPermission": "菜单权限", + "dataPermission": "数据权限", + "checkStrictlyTrue": "联动", + "checkStrictlyFalse": "不联动" +} diff --git a/app/src/main.ts b/app/src/main.ts new file mode 100644 index 0000000..68e8b77 --- /dev/null +++ b/app/src/main.ts @@ -0,0 +1,31 @@ +import { initPreferences } from '@aiflowy/preferences'; +import { unmountGlobalLoading } from '@aiflowy/utils'; + +import { overridesPreferences } from './preferences'; + +/** + * 应用初始化完成之后再进行页面加载渲染 + */ +async function initApplication() { + // name用于指定项目唯一标识 + // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据 + const env = import.meta.env.PROD ? 'prod' : 'dev'; + const appVersion = import.meta.env.VITE_APP_VERSION; + const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`; + + // app偏好设置初始化 + await initPreferences({ + namespace, + overrides: overridesPreferences, + }); + + // 启动应用并挂载 + // vue应用主要逻辑及视图 + const { bootstrap } = await import('./bootstrap'); + await bootstrap(namespace); + + // 移除并销毁loading + unmountGlobalLoading(); +} + +initApplication(); diff --git a/app/src/preferences.ts b/app/src/preferences.ts new file mode 100644 index 0000000..6342ccd --- /dev/null +++ b/app/src/preferences.ts @@ -0,0 +1,14 @@ +import { defineOverridesPreferences } from '@aiflowy/preferences'; + +/** + * @description 项目配置文件 + * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置 + * !!! 更改配置后请清空缓存,否则可能不生效 + */ +export const overridesPreferences = defineOverridesPreferences({ + // overrides + app: { + name: import.meta.env.VITE_APP_TITLE, + accessMode: 'mixed', + }, +}); diff --git a/app/src/router/access.ts b/app/src/router/access.ts new file mode 100644 index 0000000..27e7482 --- /dev/null +++ b/app/src/router/access.ts @@ -0,0 +1,42 @@ +import type { + ComponentRecordType, + GenerateMenuAndRoutesOptions, +} from '@aiflowy/types'; + +import { generateAccessible } from '@aiflowy/access'; +import { preferences } from '@aiflowy/preferences'; + +import { ElMessage } from 'element-plus'; + +import { getAllMenusApi } from '#/api'; +import { BasicLayout, IFrameView } from '#/layouts'; +import { $t } from '#/locales'; + +const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); + +async function generateAccess(options: GenerateMenuAndRoutesOptions) { + const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue'); + + const layoutMap: ComponentRecordType = { + BasicLayout, + IFrameView, + }; + + return await generateAccessible(preferences.app.accessMode, { + ...options, + fetchMenuListAsync: async () => { + ElMessage({ + duration: 1500, + message: `${$t('common.loadingMenu')}...`, + }); + return await getAllMenusApi(); + }, + // 可以指定没有权限跳转403页面 + forbiddenComponent, + // 如果 route.meta.menuVisibleWithForbidden = true + layoutMap, + pageMap, + }); +} + +export { generateAccess }; diff --git a/app/src/router/guard.ts b/app/src/router/guard.ts new file mode 100644 index 0000000..cdd147c --- /dev/null +++ b/app/src/router/guard.ts @@ -0,0 +1,133 @@ +import type { Router } from 'vue-router'; + +import { LOGIN_PATH } from '@aiflowy/constants'; +import { preferences } from '@aiflowy/preferences'; +import { useAccessStore, useUserStore } from '@aiflowy/stores'; +import { startProgress, stopProgress } from '@aiflowy/utils'; + +import { accessRoutes, coreRouteNames } from '#/router/routes'; +import { useAuthStore } from '#/store'; + +import { generateAccess } from './access'; + +/** + * 通用守卫配置 + * @param router + */ +function setupCommonGuard(router: Router) { + // 记录已经加载的页面 + const loadedPaths = new Set(); + + router.beforeEach((to) => { + to.meta.loaded = loadedPaths.has(to.path); + + // 页面加载进度条 + if (!to.meta.loaded && preferences.transition.progress) { + startProgress(); + } + return true; + }); + + router.afterEach((to) => { + // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行 + + loadedPaths.add(to.path); + + // 关闭页面加载进度条 + if (preferences.transition.progress) { + stopProgress(); + } + }); +} + +/** + * 权限访问守卫配置 + * @param router + */ +function setupAccessGuard(router: Router) { + router.beforeEach(async (to, from) => { + const accessStore = useAccessStore(); + const userStore = useUserStore(); + const authStore = useAuthStore(); + + // 基本路由,这些路由不需要进入权限拦截 + if (coreRouteNames.includes(to.name as string)) { + if (to.path === LOGIN_PATH && accessStore.accessToken) { + return decodeURIComponent( + (to.query?.redirect as string) || + userStore.userInfo?.homePath || + preferences.app.defaultHomePath, + ); + } + return true; + } + + // accessToken 检查 + if (!accessStore.accessToken) { + // 明确声明忽略权限访问权限,则可以访问 + if (to.meta.ignoreAccess) { + return true; + } + + // 没有访问权限,跳转登录页面 + if (to.fullPath !== LOGIN_PATH) { + return { + path: LOGIN_PATH, + // 如不需要,直接删除 query + query: + to.fullPath === preferences.app.defaultHomePath + ? {} + : { redirect: encodeURIComponent(to.fullPath) }, + // 携带当前跳转的页面,登录后重新跳转该页面 + replace: true, + }; + } + return to; + } + + // 是否已经生成过动态路由 + if (accessStore.isAccessChecked) { + return true; + } + + // 生成路由表 + // 当前登录用户拥有的角色标识列表 + const userInfo = userStore.userInfo || (await authStore.fetchUserInfo()); + const userRoles = userInfo.roles ?? []; + + // 生成菜单和路由 + const { accessibleMenus, accessibleRoutes } = await generateAccess({ + roles: userRoles, + router, + // 则会在菜单中显示,但是访问会被重定向到403 + routes: accessRoutes, + }); + + // 保存菜单信息和路由信息 + accessStore.setAccessMenus(accessibleMenus); + accessStore.setAccessRoutes(accessibleRoutes); + accessStore.setIsAccessChecked(true); + const redirectPath = (from.query.redirect ?? + (to.path === preferences.app.defaultHomePath + ? userInfo.homePath || preferences.app.defaultHomePath + : to.fullPath)) as string; + + return { + ...router.resolve(decodeURIComponent(redirectPath)), + replace: true, + }; + }); +} + +/** + * 项目守卫配置 + * @param router + */ +function createRouterGuard(router: Router) { + /** 通用 */ + setupCommonGuard(router); + /** 权限访问 */ + setupAccessGuard(router); +} + +export { createRouterGuard }; diff --git a/app/src/router/index.ts b/app/src/router/index.ts new file mode 100644 index 0000000..18f0080 --- /dev/null +++ b/app/src/router/index.ts @@ -0,0 +1,37 @@ +import { + createRouter, + createWebHashHistory, + createWebHistory, +} from 'vue-router'; + +import { resetStaticRoutes } from '@aiflowy/utils'; + +import { createRouterGuard } from './guard'; +import { routes } from './routes'; + +/** + * @zh_CN 创建vue-router实例 + */ +const router = createRouter({ + history: + import.meta.env.VITE_ROUTER_HISTORY === 'hash' + ? createWebHashHistory(import.meta.env.VITE_BASE) + : createWebHistory(import.meta.env.VITE_BASE), + // 应该添加到路由的初始路由列表。 + routes, + scrollBehavior: (to, _from, savedPosition) => { + if (savedPosition) { + return savedPosition; + } + return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 }; + }, + // 是否应该禁止尾部斜杠。 + // strict: true, +}); + +const resetRoutes = () => resetStaticRoutes(router, routes); + +// 创建路由守卫 +createRouterGuard(router); + +export { resetRoutes, router }; diff --git a/app/src/router/routes/core.ts b/app/src/router/routes/core.ts new file mode 100644 index 0000000..0ff0457 --- /dev/null +++ b/app/src/router/routes/core.ts @@ -0,0 +1,108 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { LOGIN_PATH } from '@aiflowy/constants'; +import { preferences } from '@aiflowy/preferences'; + +import { $t } from '#/locales'; + +const BasicLayout = () => import('#/layouts/basic.vue'); +const AuthPageLayout = () => import('#/layouts/auth.vue'); +/** 全局404页面 */ +const fallbackNotFoundRoute: RouteRecordRaw = { + component: () => import('#/views/_core/fallback/not-found.vue'), + meta: { + hideInBreadcrumb: true, + hideInMenu: true, + hideInTab: true, + title: '404', + }, + name: 'FallbackNotFound', + path: '/:path(.*)*', +}; + +/** 基本路由,这些路由是必须存在的 */ +const coreRoutes: RouteRecordRaw[] = [ + { + component: () => import('#/views/_core/authentication/oauth-page.vue'), + meta: { + hideInBreadcrumb: true, + hideInMenu: true, + hideInTab: true, + title: 'OAuth', + }, + name: 'OAuth', + path: '/oauth', + }, + /** + * 根路由 + * 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。 + * 此路由必须存在,且不应修改 + */ + { + component: BasicLayout, + meta: { + hideInBreadcrumb: true, + title: 'Root', + }, + name: 'Root', + path: '/', + redirect: preferences.app.defaultHomePath, + children: [], + }, + { + component: AuthPageLayout, + meta: { + hideInTab: true, + title: 'Authentication', + }, + name: 'Authentication', + path: '/auth', + redirect: LOGIN_PATH, + children: [ + { + name: 'Login', + path: 'login', + component: () => import('#/views/_core/authentication/login.vue'), + meta: { + title: $t('page.auth.login'), + }, + }, + { + name: 'CodeLogin', + path: 'code-login', + component: () => import('#/views/_core/authentication/code-login.vue'), + meta: { + title: $t('page.auth.codeLogin'), + }, + }, + { + name: 'QrCodeLogin', + path: 'qrcode-login', + component: () => + import('#/views/_core/authentication/qrcode-login.vue'), + meta: { + title: $t('page.auth.qrcodeLogin'), + }, + }, + { + name: 'ForgetPassword', + path: 'forget-password', + component: () => + import('#/views/_core/authentication/forget-password.vue'), + meta: { + title: $t('page.auth.forgetPassword'), + }, + }, + { + name: 'Register', + path: 'register', + component: () => import('#/views/_core/authentication/register.vue'), + meta: { + title: $t('page.auth.register'), + }, + }, + ], + }, +]; + +export { coreRoutes, fallbackNotFoundRoute }; diff --git a/app/src/router/routes/index.ts b/app/src/router/routes/index.ts new file mode 100644 index 0000000..939c35e --- /dev/null +++ b/app/src/router/routes/index.ts @@ -0,0 +1,47 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { mergeRouteModules, traverseTreeValues } from '@aiflowy/utils'; + +import { coreRoutes, fallbackNotFoundRoute } from './core'; + +const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', { + eager: true, +}); + +// 有需要可以自行打开注释,并创建文件夹 +// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); +// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true }); + +/** 动态路由 */ +const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles); + +/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */ +// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); +// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles); +const staticRoutes: RouteRecordRaw[] = []; +const externalRoutes: RouteRecordRaw[] = []; + +/** 路由列表,由基本路由、外部路由和404兜底路由组成 + * 无需走权限验证(会一直显示在菜单中) */ +const routes: RouteRecordRaw[] = [ + ...coreRoutes, + ...externalRoutes, + fallbackNotFoundRoute, +]; + +/** 基本路由列表,这些路由不需要进入权限拦截 */ +const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name); + +/** 有权限校验的路由列表,包含动态路由和静态路由 */ +const accessRoutes = [...dynamicRoutes, ...staticRoutes]; + +const componentKeys: string[] = Object.keys( + import.meta.glob('../../views/**/*.vue'), +) + .filter((item) => !item.includes('/modules/')) + .map((v) => { + const path = v.replace('../../views/', '/'); + return path.endsWith('.vue') ? path.slice(0, -4) : path; + }); + +export { accessRoutes, componentKeys, coreRouteNames, routes }; diff --git a/app/src/router/routes/modules/aiflowy.ts b/app/src/router/routes/modules/aiflowy.ts new file mode 100644 index 0000000..67c9d68 --- /dev/null +++ b/app/src/router/routes/modules/aiflowy.ts @@ -0,0 +1,63 @@ +import type { RouteRecordRaw } from 'vue-router'; + +// import { APP_DOC_URL, APP_GITHUB_URL, APP_LOGO_URL } from '@aiflowy/constants'; +// +// import { IFrameView } from '#/layouts'; +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + // { + // meta: { + // badgeType: 'dot', + // icon: APP_LOGO_URL, + // order: 9998, + // title: $t('demos.aiflowy.title'), + // }, + // name: 'AIFlowyProject', + // path: '/aiflowy-admin', + // children: [ + // { + // name: 'AIFlowyDocument', + // path: '/aiflowy-admin/document', + // component: IFrameView, + // meta: { + // icon: 'lucide:book-open-text', + // link: APP_DOC_URL, + // title: $t('demos.aiflowy.document'), + // }, + // }, + // { + // name: 'AIFlowyGithub', + // path: '/aiflowy-admin/github', + // component: IFrameView, + // meta: { + // icon: 'mdi:github', + // link: APP_GITHUB_URL, + // title: 'Github', + // }, + // }, + // ], + // }, + // { + // name: 'AIFlowyAbout', + // path: '/aiflowy-admin/about', + // component: () => import('#/views/_core/about/index.vue'), + // meta: { + // icon: 'lucide:copyright', + // title: $t('demos.aiflowy.about'), + // order: 9999, + // }, + // }, + { + name: 'Profile', + path: '/profile', + component: () => import('#/views/_core/profile/index.vue'), + meta: { + icon: 'lucide:user', + hideInMenu: true, + title: $t('page.auth.profile'), + }, + }, +]; + +export default routes; diff --git a/app/src/router/routes/modules/bot.ts b/app/src/router/routes/modules/bot.ts new file mode 100644 index 0000000..6975c89 --- /dev/null +++ b/app/src/router/routes/modules/bot.ts @@ -0,0 +1,32 @@ +import type { RouteRecordRaw } from 'vue-router'; + +const routes: RouteRecordRaw[] = [ + { + name: 'BotRun', + path: '/ai/bots/run/:botId/:sessionId?', + component: () => import('#/views/ai/bots/pages/Run.vue'), + meta: { + title: 'Bots', + noBasicLayout: true, + openInNewWindow: true, + hideInMenu: true, + hideInBreadcrumb: true, + hideInTab: true, + }, + }, + { + name: 'BotSetting', + path: '/ai/bots/setting/:id', + component: () => import('#/views/ai/bots/pages/setting/index.vue'), + meta: { + title: 'Bots', + openInNewWindow: true, + hideInMenu: true, + hideInBreadcrumb: true, + hideInTab: true, + activePath: '/ai/bots', + }, + }, +]; + +export default routes; diff --git a/app/src/router/routes/modules/dashboard.ts b/app/src/router/routes/modules/dashboard.ts new file mode 100644 index 0000000..02c9055 --- /dev/null +++ b/app/src/router/routes/modules/dashboard.ts @@ -0,0 +1,38 @@ +import type { RouteRecordRaw } from 'vue-router'; + +// import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + // { + // meta: { + // icon: 'lucide:layout-dashboard', + // order: -1, + // title: $t('page.dashboard.title'), + // }, + // name: 'Dashboard', + // path: '/dashboard', + // children: [ + // { + // name: 'Analytics', + // path: '/analytics', + // component: () => import('#/views/dashboard/analytics/index.vue'), + // meta: { + // affixTab: true, + // icon: 'lucide:area-chart', + // title: $t('page.dashboard.analytics'), + // }, + // }, + // { + // name: 'Workspace', + // path: '/workspace', + // component: () => import('#/views/dashboard/workspace/index.vue'), + // meta: { + // icon: 'carbon:workspace', + // title: $t('page.dashboard.workspace'), + // }, + // }, + // ], + // }, +]; + +export default routes; diff --git a/app/src/router/routes/modules/datacenter.ts b/app/src/router/routes/modules/datacenter.ts new file mode 100644 index 0000000..8de210a --- /dev/null +++ b/app/src/router/routes/modules/datacenter.ts @@ -0,0 +1,20 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + icon: 'clarity:database', + title: $t('datacenterTable.title'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + }, + name: 'TableDetail', + path: '/datacenter/table/tableDetail', + component: () => import('#/views/datacenter/DatacenterTableDetail.vue'), + }, +]; + +export default routes; diff --git a/app/src/router/routes/modules/demos.ts b/app/src/router/routes/modules/demos.ts new file mode 100644 index 0000000..4400576 --- /dev/null +++ b/app/src/router/routes/modules/demos.ts @@ -0,0 +1,52 @@ +// import type { RouteRecordRaw } from 'vue-router'; +// +// import { $t } from '#/locales'; +// +// const routes: RouteRecordRaw[] = [ +// { +// meta: { +// icon: 'ic:baseline-view-in-ar', +// keepAlive: true, +// order: 1000, +// title: $t('demos.title'), +// }, +// name: 'Demos', +// path: '/demos', +// children: [ +// { +// meta: { +// title: $t('demos.elementPlus'), +// }, +// name: 'NaiveDemos', +// path: '/demos/element', +// component: () => import('#/views/demos/element/index.vue'), +// }, +// { +// meta: { +// title: '卡片组件', +// }, +// name: 'NaiveDemos1', +// path: '/demos/cardTest', +// component: () => import('#/views/demos/cardTest/index.vue'), +// }, +// { +// meta: { +// title: '分类组件', +// }, +// name: 'NaiveDemos2', +// path: '/demos/categoryPanel', +// component: () => import('#/views/demos/categoryPanel/index.vue'), +// }, +// { +// meta: { +// title: $t('demos.form'), +// }, +// name: 'BasicForm', +// path: '/demos/form', +// component: () => import('#/views/demos/form/basic.vue'), +// }, +// ], +// }, +// ]; +// +// export default routes; diff --git a/app/src/router/routes/modules/document.ts b/app/src/router/routes/modules/document.ts new file mode 100644 index 0000000..abe9be5 --- /dev/null +++ b/app/src/router/routes/modules/document.ts @@ -0,0 +1,21 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + title: $t('documentCollection.documentManagement'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + fullPathKey: true, + activePath: '/ai/documentCollection', + }, + name: 'Document', + path: '/ai/documentCollection/document', + component: () => import('#/views/ai/documentCollection/Document.vue'), + }, +]; + +export default routes; diff --git a/app/src/router/routes/modules/plugins.ts b/app/src/router/routes/modules/plugins.ts new file mode 100644 index 0000000..49650ea --- /dev/null +++ b/app/src/router/routes/modules/plugins.ts @@ -0,0 +1,32 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + title: $t('plugin.toolsManagement'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + fullPathKey: true, + }, + name: 'PluginTools', + path: '/ai/plugin/tools', + component: () => import('#/views/ai/plugin/PluginTools.vue'), + }, + { + meta: { + title: $t('plugin.toolsManagement'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + fullPathKey: true, + }, + name: 'PluginToolEdit', + path: '/ai/plugin/tool/edit', + component: () => import('#/views/ai/plugin/PluginToolEdit.vue'), + }, +]; + +export default routes; diff --git a/app/src/router/routes/modules/sysFeedback.ts b/app/src/router/routes/modules/sysFeedback.ts new file mode 100644 index 0000000..720ca1e --- /dev/null +++ b/app/src/router/routes/modules/sysFeedback.ts @@ -0,0 +1,20 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + name: 'SysFeedbackDetail', + path: '/sys/sysFeedback/:id', + component: () => import('#/views/system/sysFeedback/sysFeedbackDetail.vue'), + meta: { + title: $t('menus.system.sysFeedback'), + hideInMenu: true, + hideInBreadcrumb: true, + hideInTab: true, + activePath: '/sys/sysFeedback', + }, + }, +]; + +export default routes; diff --git a/app/src/router/routes/modules/sysJob.ts b/app/src/router/routes/modules/sysJob.ts new file mode 100644 index 0000000..a293590 --- /dev/null +++ b/app/src/router/routes/modules/sysJob.ts @@ -0,0 +1,20 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + icon: 'clarity:time-line', + title: $t('sysJobLog.title'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + }, + name: 'SysJobLog', + path: '/sys/sysJob/sysJobLog', + component: () => import('#/views/system/sysJob/SysJobLogList.vue'), + }, +]; + +export default routes; diff --git a/app/src/router/routes/modules/workflow.ts b/app/src/router/routes/modules/workflow.ts new file mode 100644 index 0000000..78168dd --- /dev/null +++ b/app/src/router/routes/modules/workflow.ts @@ -0,0 +1,55 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + icon: 'ant-design:apartment-outlined', + title: $t('datacenterTable.title'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + }, + name: 'WorkflowDesign', + path: '/ai/workflow/design', + component: () => import('#/views/ai/workflow/WorkflowDesign.vue'), + }, + { + meta: { + title: '运行', + openInNewWindow: true, + noBasicLayout: true, + hideInMenu: true, + }, + name: 'RunPage', + path: '/ai/workflow/run', + component: () => import('#/views/ai/workflow/RunPage.vue'), + }, + { + meta: { + title: $t('aiWorkflowExecRecord.moduleName'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + }, + name: 'ExecRecord', + path: '/ai/workflow/executeRecords', + component: () => + import('#/views/ai/workflow/execute/WorkflowExecResultList.vue'), + }, + { + meta: { + title: $t('aiWorkflowRecordStep.moduleName'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + }, + name: 'RecordStep', + path: '/ai/workflow/executeSteps', + component: () => + import('#/views/ai/workflow/execute/WorkflowExecStepList.vue'), + }, +]; + +export default routes; diff --git a/app/src/shims-vue.d.ts b/app/src/shims-vue.d.ts new file mode 100644 index 0000000..7c32d10 --- /dev/null +++ b/app/src/shims-vue.d.ts @@ -0,0 +1,17 @@ +declare module '#/components/page/PageData.vue' { + import type { DefineComponent } from 'vue'; + + interface PageDataSlots { + default: (props: { pageList: any[] }) => any; + } + + const component: DefineComponent; + export default component; +} + +declare module '*.vue' { + import type { DefineComponent } from 'vue'; + + const component: DefineComponent; + export default component; +} diff --git a/app/src/store/auth.ts b/app/src/store/auth.ts new file mode 100644 index 0000000..59a5544 --- /dev/null +++ b/app/src/store/auth.ts @@ -0,0 +1,119 @@ +import type { Recordable, UserInfo } from '@aiflowy/types'; + +import { ref } from 'vue'; +import { useRouter } from 'vue-router'; + +import { LOGIN_PATH } from '@aiflowy/constants'; +import { preferences } from '@aiflowy/preferences'; +import { resetAllStores, useAccessStore, useUserStore } from '@aiflowy/stores'; + +import { ElNotification } from 'element-plus'; +import { defineStore } from 'pinia'; + +import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; +import { $t } from '#/locales'; + +export const useAuthStore = defineStore('auth', () => { + const accessStore = useAccessStore(); + const userStore = useUserStore(); + const router = useRouter(); + + const loginLoading = ref(false); + + /** + * 异步处理登录操作 + * Asynchronously handle the login process + * @param params 登录表单数据 + */ + async function authLogin( + params: Recordable, + onSuccess?: () => Promise | void, + ) { + // 异步处理用户登录操作并获取 accessToken + let userInfo: null | UserInfo = null; + try { + loginLoading.value = true; + const { token: accessToken } = await loginApi(params); + + // 如果成功获取到 accessToken + if (accessToken) { + // 将 accessToken 存储到 accessStore 中 + accessStore.setAccessToken(accessToken); + + // 获取用户信息并存储到 accessStore 中 + const [fetchUserInfoResult, accessCodes] = await Promise.all([ + fetchUserInfo(), + getAccessCodesApi(), + ]); + + userInfo = fetchUserInfoResult; + + userStore.setUserInfo(userInfo); + accessStore.setAccessCodes(accessCodes); + + if (accessStore.loginExpired) { + accessStore.setLoginExpired(false); + } else { + onSuccess + ? await onSuccess?.() + : await router.push( + userInfo.homePath || preferences.app.defaultHomePath, + ); + } + + if (userInfo?.nickname) { + ElNotification({ + message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.nickname}`, + title: $t('authentication.loginSuccess'), + type: 'success', + }); + } + } + } finally { + loginLoading.value = false; + } + + return { + userInfo, + }; + } + + async function logout(redirect: boolean = true) { + try { + await logoutApi(); + } catch { + // 不做任何处理 + } + resetAllStores(); + accessStore.setLoginExpired(false); + + // 回登录页带上当前路由地址 + await router.replace({ + path: LOGIN_PATH, + query: redirect + ? { + redirect: encodeURIComponent(router.currentRoute.value.fullPath), + } + : {}, + }); + } + + async function fetchUserInfo() { + let userInfo: null | UserInfo = null; + userInfo = await getUserInfoApi(); + userStore.setUserInfo(userInfo); + return userInfo; + } + + function $reset() { + loginLoading.value = false; + } + + return { + $reset, + authLogin, + fetchUserInfo, + loginLoading, + logout, + }; +}); diff --git a/app/src/store/dict.ts b/app/src/store/dict.ts new file mode 100644 index 0000000..55fb6c8 --- /dev/null +++ b/app/src/store/dict.ts @@ -0,0 +1,54 @@ +import { defineStore } from 'pinia'; + +import { api } from '#/api/request'; + +export const useDictStore = defineStore('dictionary', { + state: () => ({ + dictCache: new Map(), // 缓存字典数据 + }), + + getters: { + // 获取特定字典的 Map 对象 + getDictByType: (state) => (dictType: string) => { + return state.dictCache.get(dictType) || new Map(); + }, + }, + + actions: { + // 获取字典数据 + async fetchDictionary(dictType: string) { + // 如果已经有缓存数据,直接返回 + if (this.dictCache.has(dictType)) { + return this.dictCache.get(dictType); + } + + try { + const requestPromise = api.get(`/api/v1/dict/items/${dictType}`); + const dictData = await requestPromise; + // 转换为 { value: label } 格式便于查找 + const dictMap = new Map( + dictData.data.map((item: any) => [item.value, item.label]), + ); + + // 缓存数据并清理加载状态 + this.dictCache.set(dictType, dictMap); + + return dictMap; + } catch (error) { + console.error(`get dict ${dictType} error:`, error); + return new Map(); + } + }, + + // 根据字典类型和值获取标签 + getDictLabel(dictType: string, value: any) { + const dictMap = this.dictCache.get(dictType); + if (!dictMap) { + return value; // 返回原值作为降级处理 + } + + const label = dictMap.get(value); + return label === undefined ? value : label; + }, + }, +}); diff --git a/app/src/store/index.ts b/app/src/store/index.ts new file mode 100644 index 0000000..b6a7763 --- /dev/null +++ b/app/src/store/index.ts @@ -0,0 +1,2 @@ +export * from './auth'; +export * from './dict'; diff --git a/app/src/utils/resource.ts b/app/src/utils/resource.ts new file mode 100644 index 0000000..c5f4e94 --- /dev/null +++ b/app/src/utils/resource.ts @@ -0,0 +1,57 @@ +import audioIcon from '#/assets/ai/resource/audio-icon.png'; +import docIcon from '#/assets/ai/resource/doc-icon.png'; +import otherIcon from '#/assets/ai/resource/other-icon.png'; +import videoIcon from '#/assets/ai/resource/video-icon.png'; + +export function getSrc(item: any) { + switch (item.resourceType) { + case 0: { + return item.resourceUrl; + } + case 1: { + return audioIcon; + } + case 2: { + return videoIcon; + } + case 3: { + return docIcon; + } + default: { + return otherIcon; + } + } +} + +export function getResourceTypeColor(item: any) { + switch (item.resourceType) { + case 0: { + return '#0066FF'; + } + case 1: { + return '#FFA200'; + } + case 2: { + return '#5600FF'; + } + case 3: { + return '#0099CC'; + } + default: { + return '#757575'; + } + } +} +export function getResourceOriginColor(item: any) { + switch (item.origin) { + case 0: { + return '#039e90'; + } + case 1: { + return '#0066FF'; + } + default: { + return '#757575'; + } + } +} diff --git a/app/src/views/_core/README.md b/app/src/views/_core/README.md new file mode 100644 index 0000000..8248afe --- /dev/null +++ b/app/src/views/_core/README.md @@ -0,0 +1,3 @@ +# \_core + +此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。 diff --git a/app/src/views/_core/about/index.vue b/app/src/views/_core/about/index.vue new file mode 100644 index 0000000..6c9c8e6 --- /dev/null +++ b/app/src/views/_core/about/index.vue @@ -0,0 +1,39 @@ + + + diff --git a/app/src/views/_core/authentication/code-login.vue b/app/src/views/_core/authentication/code-login.vue new file mode 100644 index 0000000..df01968 --- /dev/null +++ b/app/src/views/_core/authentication/code-login.vue @@ -0,0 +1,69 @@ + + + diff --git a/app/src/views/_core/authentication/forget-password.vue b/app/src/views/_core/authentication/forget-password.vue new file mode 100644 index 0000000..861603f --- /dev/null +++ b/app/src/views/_core/authentication/forget-password.vue @@ -0,0 +1,43 @@ + + + diff --git a/app/src/views/_core/authentication/login.vue b/app/src/views/_core/authentication/login.vue new file mode 100644 index 0000000..0ee864c --- /dev/null +++ b/app/src/views/_core/authentication/login.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/app/src/views/_core/authentication/oauth-page.vue b/app/src/views/_core/authentication/oauth-page.vue new file mode 100644 index 0000000..59773b3 --- /dev/null +++ b/app/src/views/_core/authentication/oauth-page.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/app/src/views/_core/authentication/qrcode-login.vue b/app/src/views/_core/authentication/qrcode-login.vue new file mode 100644 index 0000000..0ccb9fe --- /dev/null +++ b/app/src/views/_core/authentication/qrcode-login.vue @@ -0,0 +1,10 @@ + + + diff --git a/app/src/views/_core/authentication/register.vue b/app/src/views/_core/authentication/register.vue new file mode 100644 index 0000000..bac4a0f --- /dev/null +++ b/app/src/views/_core/authentication/register.vue @@ -0,0 +1,96 @@ + + + diff --git a/app/src/views/_core/fallback/coming-soon.vue b/app/src/views/_core/fallback/coming-soon.vue new file mode 100644 index 0000000..1c806b7 --- /dev/null +++ b/app/src/views/_core/fallback/coming-soon.vue @@ -0,0 +1,7 @@ + + + diff --git a/app/src/views/_core/fallback/forbidden.vue b/app/src/views/_core/fallback/forbidden.vue new file mode 100644 index 0000000..bd4adc0 --- /dev/null +++ b/app/src/views/_core/fallback/forbidden.vue @@ -0,0 +1,9 @@ + + + diff --git a/app/src/views/_core/fallback/internal-error.vue b/app/src/views/_core/fallback/internal-error.vue new file mode 100644 index 0000000..e288206 --- /dev/null +++ b/app/src/views/_core/fallback/internal-error.vue @@ -0,0 +1,9 @@ + + + diff --git a/app/src/views/_core/fallback/not-found.vue b/app/src/views/_core/fallback/not-found.vue new file mode 100644 index 0000000..1c631fc --- /dev/null +++ b/app/src/views/_core/fallback/not-found.vue @@ -0,0 +1,9 @@ + + + diff --git a/app/src/views/_core/fallback/offline.vue b/app/src/views/_core/fallback/offline.vue new file mode 100644 index 0000000..f34e62a --- /dev/null +++ b/app/src/views/_core/fallback/offline.vue @@ -0,0 +1,9 @@ + + + diff --git a/app/src/views/_core/profile/base-setting.vue b/app/src/views/_core/profile/base-setting.vue new file mode 100644 index 0000000..8883500 --- /dev/null +++ b/app/src/views/_core/profile/base-setting.vue @@ -0,0 +1,77 @@ + + diff --git a/app/src/views/_core/profile/index.vue b/app/src/views/_core/profile/index.vue new file mode 100644 index 0000000..1dc84ae --- /dev/null +++ b/app/src/views/_core/profile/index.vue @@ -0,0 +1,43 @@ + + diff --git a/app/src/views/_core/profile/notification-setting.vue b/app/src/views/_core/profile/notification-setting.vue new file mode 100644 index 0000000..790d399 --- /dev/null +++ b/app/src/views/_core/profile/notification-setting.vue @@ -0,0 +1,33 @@ + + diff --git a/app/src/views/_core/profile/password-setting.vue b/app/src/views/_core/profile/password-setting.vue new file mode 100644 index 0000000..cfda0fb --- /dev/null +++ b/app/src/views/_core/profile/password-setting.vue @@ -0,0 +1,78 @@ + + diff --git a/app/src/views/_core/profile/security-setting.vue b/app/src/views/_core/profile/security-setting.vue new file mode 100644 index 0000000..e8302b2 --- /dev/null +++ b/app/src/views/_core/profile/security-setting.vue @@ -0,0 +1,43 @@ + + diff --git a/app/src/views/ai/bots/index.vue b/app/src/views/ai/bots/index.vue new file mode 100644 index 0000000..c3d1fc9 --- /dev/null +++ b/app/src/views/ai/bots/index.vue @@ -0,0 +1,344 @@ + + + diff --git a/app/src/views/ai/bots/modal.vue b/app/src/views/ai/bots/modal.vue new file mode 100644 index 0000000..d5acad2 --- /dev/null +++ b/app/src/views/ai/bots/modal.vue @@ -0,0 +1,120 @@ + + + diff --git a/app/src/views/ai/bots/pages/Run.vue b/app/src/views/ai/bots/pages/Run.vue new file mode 100644 index 0000000..5edce7d --- /dev/null +++ b/app/src/views/ai/bots/pages/Run.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/app/src/views/ai/bots/pages/setting/PromptChoreChatModal.vue b/app/src/views/ai/bots/pages/setting/PromptChoreChatModal.vue new file mode 100644 index 0000000..df5c5bc --- /dev/null +++ b/app/src/views/ai/bots/pages/setting/PromptChoreChatModal.vue @@ -0,0 +1,91 @@ + + + diff --git a/app/src/views/ai/bots/pages/setting/config.vue b/app/src/views/ai/bots/pages/setting/config.vue new file mode 100644 index 0000000..6d361ad --- /dev/null +++ b/app/src/views/ai/bots/pages/setting/config.vue @@ -0,0 +1,1048 @@ + + + + + diff --git a/app/src/views/ai/bots/pages/setting/index.vue b/app/src/views/ai/bots/pages/setting/index.vue new file mode 100644 index 0000000..e28e4cd --- /dev/null +++ b/app/src/views/ai/bots/pages/setting/index.vue @@ -0,0 +1,66 @@ + + + + diff --git a/app/src/views/ai/bots/pages/setting/preview.vue b/app/src/views/ai/bots/pages/setting/preview.vue new file mode 100644 index 0000000..7e260e1 --- /dev/null +++ b/app/src/views/ai/bots/pages/setting/preview.vue @@ -0,0 +1,34 @@ + + + diff --git a/app/src/views/ai/bots/pages/setting/prompt.vue b/app/src/views/ai/bots/pages/setting/prompt.vue new file mode 100644 index 0000000..16b8006 --- /dev/null +++ b/app/src/views/ai/bots/pages/setting/prompt.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/app/src/views/ai/documentCollection/ChunkDocumentTable.vue b/app/src/views/ai/documentCollection/ChunkDocumentTable.vue new file mode 100644 index 0000000..72e0673 --- /dev/null +++ b/app/src/views/ai/documentCollection/ChunkDocumentTable.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/app/src/views/ai/documentCollection/ComfirmImportDocument.vue b/app/src/views/ai/documentCollection/ComfirmImportDocument.vue new file mode 100644 index 0000000..ebbe1cb --- /dev/null +++ b/app/src/views/ai/documentCollection/ComfirmImportDocument.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/app/src/views/ai/documentCollection/Document.vue b/app/src/views/ai/documentCollection/Document.vue new file mode 100644 index 0000000..96a4f35 --- /dev/null +++ b/app/src/views/ai/documentCollection/Document.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/app/src/views/ai/documentCollection/DocumentCollection.vue b/app/src/views/ai/documentCollection/DocumentCollection.vue new file mode 100644 index 0000000..73784cb --- /dev/null +++ b/app/src/views/ai/documentCollection/DocumentCollection.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/app/src/views/ai/documentCollection/DocumentCollectionModal.vue b/app/src/views/ai/documentCollection/DocumentCollectionModal.vue new file mode 100644 index 0000000..f69b7ea --- /dev/null +++ b/app/src/views/ai/documentCollection/DocumentCollectionModal.vue @@ -0,0 +1,284 @@ + + + + + diff --git a/app/src/views/ai/documentCollection/DocumentTable.vue b/app/src/views/ai/documentCollection/DocumentTable.vue new file mode 100644 index 0000000..f164c0e --- /dev/null +++ b/app/src/views/ai/documentCollection/DocumentTable.vue @@ -0,0 +1,155 @@ + + + + + diff --git a/app/src/views/ai/documentCollection/ImportKnowledgeDocFile.vue b/app/src/views/ai/documentCollection/ImportKnowledgeDocFile.vue new file mode 100644 index 0000000..0aa8f99 --- /dev/null +++ b/app/src/views/ai/documentCollection/ImportKnowledgeDocFile.vue @@ -0,0 +1,253 @@ + + + + + diff --git a/app/src/views/ai/documentCollection/ImportKnowledgeFileContainer.vue b/app/src/views/ai/documentCollection/ImportKnowledgeFileContainer.vue new file mode 100644 index 0000000..3ba43f4 --- /dev/null +++ b/app/src/views/ai/documentCollection/ImportKnowledgeFileContainer.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/app/src/views/ai/documentCollection/KnowledgeSearch.vue b/app/src/views/ai/documentCollection/KnowledgeSearch.vue new file mode 100644 index 0000000..e849d48 --- /dev/null +++ b/app/src/views/ai/documentCollection/KnowledgeSearch.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/app/src/views/ai/documentCollection/PreviewSearchKnowledge.vue b/app/src/views/ai/documentCollection/PreviewSearchKnowledge.vue new file mode 100644 index 0000000..1027580 --- /dev/null +++ b/app/src/views/ai/documentCollection/PreviewSearchKnowledge.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/app/src/views/ai/documentCollection/SegmenterDoc.vue b/app/src/views/ai/documentCollection/SegmenterDoc.vue new file mode 100644 index 0000000..08cc2a8 --- /dev/null +++ b/app/src/views/ai/documentCollection/SegmenterDoc.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/app/src/views/ai/documentCollection/SplitterDocPreview.vue b/app/src/views/ai/documentCollection/SplitterDocPreview.vue new file mode 100644 index 0000000..fda5a5b --- /dev/null +++ b/app/src/views/ai/documentCollection/SplitterDocPreview.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/app/src/views/ai/mcp/Mcp.vue b/app/src/views/ai/mcp/Mcp.vue new file mode 100644 index 0000000..1241d8d --- /dev/null +++ b/app/src/views/ai/mcp/Mcp.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/app/src/views/ai/mcp/McpModal.vue b/app/src/views/ai/mcp/McpModal.vue new file mode 100644 index 0000000..b6646ca --- /dev/null +++ b/app/src/views/ai/mcp/McpModal.vue @@ -0,0 +1,341 @@ + + + + + diff --git a/app/src/views/ai/model/AddModelModal.vue b/app/src/views/ai/model/AddModelModal.vue new file mode 100644 index 0000000..052b2dc --- /dev/null +++ b/app/src/views/ai/model/AddModelModal.vue @@ -0,0 +1,377 @@ + + + + + diff --git a/app/src/views/ai/model/AddModelProviderModal.vue b/app/src/views/ai/model/AddModelProviderModal.vue new file mode 100644 index 0000000..164f726 --- /dev/null +++ b/app/src/views/ai/model/AddModelProviderModal.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/app/src/views/ai/model/ManageModelModal.vue b/app/src/views/ai/model/ManageModelModal.vue new file mode 100644 index 0000000..4f0a616 --- /dev/null +++ b/app/src/views/ai/model/ManageModelModal.vue @@ -0,0 +1,350 @@ + + + + + diff --git a/app/src/views/ai/model/Model.vue b/app/src/views/ai/model/Model.vue new file mode 100644 index 0000000..55a651d --- /dev/null +++ b/app/src/views/ai/model/Model.vue @@ -0,0 +1,622 @@ + + + + + diff --git a/app/src/views/ai/model/ModelVerifyConfig.vue b/app/src/views/ai/model/ModelVerifyConfig.vue new file mode 100644 index 0000000..a4ca706 --- /dev/null +++ b/app/src/views/ai/model/ModelVerifyConfig.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/app/src/views/ai/model/ModelViewItemOperation.vue b/app/src/views/ai/model/ModelViewItemOperation.vue new file mode 100644 index 0000000..157f7c2 --- /dev/null +++ b/app/src/views/ai/model/ModelViewItemOperation.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/app/src/views/ai/model/modelUtils/defaultIcon.ts b/app/src/views/ai/model/modelUtils/defaultIcon.ts new file mode 100644 index 0000000..34b42ba --- /dev/null +++ b/app/src/views/ai/model/modelUtils/defaultIcon.ts @@ -0,0 +1,27 @@ +import { ref } from 'vue'; + +import providerList from './providerList.json'; + +const providerOptions = + ref>( + providerList, + ); + +/** + * 根据传入的value,返回对应的icon属性 + * @param targetValue 要匹配的value值 + * @returns 匹配到的icon字符串,未匹配到返回空字符串 + */ +export const getIconByValue = (targetValue: string): string => { + const matchItem = providerOptions.value.find( + (item) => item.value === targetValue, + ); + + return matchItem?.icon || ''; +}; + +export const isSvgString = (icon: any) => { + if (typeof icon !== 'string') return false; + // 简单判断:是否包含 SVG 根标签 + return icon.trim().startsWith(''); +}; diff --git a/app/src/views/ai/model/modelUtils/model-ability-utils.ts b/app/src/views/ai/model/modelUtils/model-ability-utils.ts new file mode 100644 index 0000000..c6bcee8 --- /dev/null +++ b/app/src/views/ai/model/modelUtils/model-ability-utils.ts @@ -0,0 +1,71 @@ +import type { BooleanField, ModelAbilityItem } from './model-ability'; + +import type { llmType } from '#/api'; + +/** + * 将 llm 数据转换为标签选中状态 + * @param llm LLM数据对象 + * @param modelAbility 模型能力数组 + * @returns 更新后的模型能力数组 + */ +export const mapLlmToModelAbility = ( + llm: llmType, + modelAbility: ModelAbilityItem[], +): ModelAbilityItem[] => { + return modelAbility.map((tag) => ({ + ...tag, + selected: Boolean(llm[tag.field as keyof llmType]), + })); +}; + +/** + * 从标签选中状态生成 features 对象 + * @param modelAbility 模型能力数组 + * @returns 包含所有字段的features对象 + */ +export const generateFeaturesFromModelAbility = ( + modelAbility: ModelAbilityItem[], +): Record => { + const features: Partial> = {}; + + modelAbility.forEach((tag) => { + features[tag.field] = tag.selected; + }); + + return features as Record; +}; + +/** + * 过滤显示选中的标签 + * @param modelAbility 模型能力数组 + * @returns 选中的标签数组 + */ +export const getSelectedModelAbility = ( + modelAbility: ModelAbilityItem[], +): ModelAbilityItem[] => { + return modelAbility.filter((tag) => tag.selected); +}; + +/** + * 重置所有标签为未选中状态 + * @param modelAbility 模型能力数组 + */ +export const resetModelAbility = (modelAbility: ModelAbilityItem[]): void => { + modelAbility.forEach((tag) => { + tag.selected = false; + }); +}; + +/** + * 根据标签选中状态更新表单数据 + * @param modelAbility 模型能力数组 + * @param formData 表单数据对象 + */ +export const updateFormDataFromModelAbility = ( + modelAbility: ModelAbilityItem[], + formData: Record, +): void => { + modelAbility.forEach((tag) => { + formData[tag.field] = tag.selected; + }); +}; diff --git a/app/src/views/ai/model/modelUtils/model-ability.ts b/app/src/views/ai/model/modelUtils/model-ability.ts new file mode 100644 index 0000000..abc4d62 --- /dev/null +++ b/app/src/views/ai/model/modelUtils/model-ability.ts @@ -0,0 +1,169 @@ +import { $t } from '#/locales'; + +export type BooleanField = + | 'supportAudio' + | 'supportFree' + | 'supportImage' + | 'supportImageB64Only' + | 'supportThinking' + | 'supportTool' + | 'supportToolMessage' + | 'supportVideo'; + +export interface ModelAbilityItem { + activeType: 'danger' | 'info' | 'primary' | 'success' | 'warning'; + defaultType: 'info'; + field: BooleanField; + label: string; + selected: boolean; + value: string; +} + +/** + * 获取模型能力标签的默认配置 + * @returns ModelAbilityItem[] 模型能力配置数组 + */ +export const getDefaultModelAbility = (): ModelAbilityItem[] => [ + { + label: $t('llm.modelAbility.supportThinking'), + value: 'thinking', + defaultType: 'info', + activeType: 'success', + selected: false, + field: 'supportThinking', + }, + { + label: $t('llm.modelAbility.supportTool'), + value: 'tool', + defaultType: 'info', + activeType: 'success', + selected: false, + field: 'supportTool', + }, + { + label: $t('llm.modelAbility.supportVideo'), + value: 'video', + defaultType: 'info', + activeType: 'success', + selected: false, + field: 'supportVideo', + }, + { + label: $t('llm.modelAbility.supportImage'), + value: 'image', + defaultType: 'info', + activeType: 'success', + selected: false, + field: 'supportImage', + }, + { + label: $t('llm.modelAbility.supportFree'), + value: 'free', + defaultType: 'info', + activeType: 'success', + selected: false, + field: 'supportFree', + }, + { + label: $t('llm.modelAbility.supportAudio'), + value: 'audio', + defaultType: 'info', + activeType: 'success', + selected: false, + field: 'supportAudio', + }, + { + label: $t('llm.modelAbility.supportImageB64Only'), + value: 'imageB64', + defaultType: 'info', + activeType: 'success', + selected: false, + field: 'supportImageB64Only', + }, + { + label: $t('llm.modelAbility.supportToolMessage'), + value: 'toolMessage', + defaultType: 'info', + activeType: 'success', + selected: true, + field: 'supportToolMessage', + }, +]; + +/** + * 根据字段数组获取对应的标签选中状态 + * @param modelAbility 模型能力数组 + * @param fields 需要获取的字段数组 + * @returns 以字段名为键、选中状态为值的对象 + */ +export const getTagsSelectedStatus = ( + modelAbility: ModelAbilityItem[], + fields: BooleanField[], +): Record => { + const result: Partial> = {}; + + fields.forEach((field) => { + const tagItem = modelAbility.find((tag) => tag.field === field); + result[field] = tagItem?.selected ?? false; + }); + + return result as Record; +}; + +/** + * 同步标签选中状态与formData中的布尔字段 + * @param modelAbility 模型能力数组 + * @param formData 表单数据对象 + */ +export const syncTagSelectedStatus = ( + modelAbility: ModelAbilityItem[], + formData: Record, +): void => { + modelAbility.forEach((tag) => { + tag.selected = formData[tag.field] ?? false; + }); +}; + +/** + * 处理标签点击事件 + * @param modelAbility 模型能力数组 + * @param item 被点击的标签项 + * @param formData 表单数据对象 + */ +export const handleTagClick = ( + // modelAbility: ModelAbilityItem[], + item: ModelAbilityItem, + formData: Record, +): void => { + // 切换标签选中状态 + item.selected = !item.selected; + + // 同步更新formData中的布尔字段 + formData[item.field] = item.selected; +}; + +/** + * 根据字段获取对应的标签项 + * @param modelAbility 模型能力数组 + * @param field 布尔字段名 + * @returns 标签项 | undefined + */ +export const getTagByField = ( + modelAbility: ModelAbilityItem[], + field: BooleanField, +): ModelAbilityItem | undefined => { + return modelAbility.find((tag) => tag.field === field); +}; + +/** + * 获取所有支持的BooleanField数组 + */ +export const getAllBooleanFields = (): BooleanField[] => [ + 'supportThinking', + 'supportTool', + 'supportImage', + 'supportImageB64Only', + 'supportVideo', + 'supportAudio', + 'supportFree', +]; diff --git a/app/src/views/ai/model/modelUtils/modelTypes.ts b/app/src/views/ai/model/modelUtils/modelTypes.ts new file mode 100644 index 0000000..c53826e --- /dev/null +++ b/app/src/views/ai/model/modelUtils/modelTypes.ts @@ -0,0 +1,16 @@ +import { $t } from '@aiflowy/locales'; + +export const modelTypes = [ + { + label: $t('llmProvider.chatModel'), + value: 'chatModel', + }, + { + label: $t('llmProvider.embeddingModel'), + value: 'embeddingModel', + }, + { + label: $t('llmProvider.rerankModel'), + value: 'rerankModel', + }, +]; diff --git a/app/src/views/ai/model/modelUtils/providerList.json b/app/src/views/ai/model/modelUtils/providerList.json new file mode 100644 index 0000000..1c7b213 --- /dev/null +++ b/app/src/views/ai/model/modelUtils/providerList.json @@ -0,0 +1,577 @@ +[ + { + "label": "DeepSeek", + "value": "deepseek", + "options":{ + "llmEndpoint":"https://api.deepseek.com", + "chatPath":"/chat/completions", + "modelList":[ + { + "llmModel":"deepseek-reasoner", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-R1-0528", + "description":"DeepSeek-R1-0528 是一款强化学习(RL)驱动的推理模型,解决了模型中的重复性和可读性问题。在 RL 之前,DeepSeek-R1 引入了冷启动数据,进一步优化了推理性能。它在数学、代码和推理任务中与 OpenAI-o1 表现相当,并且通过精心设计的训练方法,提升了整体效果" + }, + { + "llmModel":"deepseek-chat", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-V3-0324", + "description":"新版 DeepSeek-V3 (DeepSeek-V3-0324)与之前的 DeepSeek-V3-1226 使用同样的 base 模型,仅改进了后训练方法。新版 V3 模型借鉴 DeepSeek-R1 模型训练过程中所使用的强化学习技术,大幅提高了在推理类任务上的表现水平,在数学、代码类相关评测集上取得了超过 GPT-4.5 的得分成绩。此外该模型在工具调用、角色扮演、问答闲聊等方面也得到了一定幅度的能力提升。" + } + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n" + + }, + { + "label": "Open AI", + "value": "openai", + "options":{ + "llmEndpoint":"https://api.openai.com", + "chatPath":"/v1/chat/completions", + "embedPath":"/v1/embeddings", + + "modelList":[ + { + "llmModel":"o4-mini", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"o4-mini", + "description":"O4-mini 是OpenAi最新的小型 O 系列型号。它针对快速、有效的推理进行了优化,在编码和可视化任务中具有非常高效的性能。" + }, + { + "llmModel":"o3", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"o3", + "description":"O3 是一个全面而强大的跨领域模型。它为数学、科学、编码和视觉推理任务设定了新标准。它还擅长技术写作和指导遵循。使用它来思考涉及跨文本、代码和图像分析的多步骤问题。" + }, + { + "llmModel":"o3-pro", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"o3-pro", + "description":"o 系列模型经过强化学习训练,在回答和执行复杂推理之前先思考。o3-pro 模型使用更多的计算来更深入地思考并始终提供更好的答案。" + }, + { + "llmModel":"o3-mini", + "supportChat":true, + "supportFunctionCalling":true, + "label":"o3-mini", + "description":"O3-mini 是OpenAi最新的小型推理模型,以与 O1-mini 相同的成本和延迟目标提供高智能。o3-mini 支持关键的开发人员功能,如结构化输出、函数调用和批处理 API。" + }, + { + "llmModel":"GPT-4.1", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"GPT-4.1", + "description":"GPT-4.1 是OpenAi用于复杂任务的旗舰模型。它非常适合跨领域解决问题。" + }, + { + "llmModel":"GPT-4o", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"GPT-4o", + "description":"GPT-4o(“o”代表“omni”)是OpenAi多功能、高智能的旗舰模型。它接受文本和图像输入,并生成文本输出(包括结构化输出)。它是大多数任务的最佳模型,也是 o 系列模型之外功能最强大的模型" + }, + { + "llmModel":"text-embedding-3-small", + "supportEmbed":true, + "label":"text-embedding-3-small", + "description":"text-embedding-3-small 是 ADA 嵌入模型的改进版,性能更高。嵌入是文本的数字表示形式,可用于度量两段文本之间的相关性。嵌入对于搜索、聚类、推荐、异常检测和分类任务非常有用" + }, + { + "llmModel":"text-embedding-3-large", + "supportEmbed":true, + "label":"text-embedding-3-large", + "description":"text-embedding-3-large 是OpenAi最强大的嵌入模型,适用于英语和非英语任务。嵌入是文本的数字表示形式,可用于度量两段文本之间的相关性。嵌入对于搜索、聚类、推荐、异常检测和分类任务非常有用。" + }, + { + "llmModel":"text-embedding-ada-002", + "supportEmbed":true, + "label":"text-embedding-ada-002", + "description":"text-embedding-ada-002 是 ADA 嵌入模型的改进版,性能更高。嵌入是文本的数字表示形式,可用于度量两段文本之间的相关性。嵌入对于搜索、聚类、推荐、异常检测和分类任务非常有用。" + } + + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "label": "阿里百炼", + "value": "aliyun", + "options":{ + "llmEndpoint":"https://dashscope.aliyuncs.com", + "chatPath":"/compatible-mode/v1/chat/completions", + "embedPath":"/compatible-mode/v1/embeddings", + "rerankPath":"/api/v1/services/rerank/text-rerank/text-rerank", + "modelList":[ + { + "llmModel":"qwen-plus", + "supportChat":true, + "supportFunctionCalling":true, + "label":"通义千问-Plus", + "description":"Qwen3系列Plus模型,实现思考模式和非思考模式的有效融合,可在对话中切换模式。推理能力显著超过QwQ、通用能力显著超过Qwen2.5-Plus,达到同规模业界SOTA水平。" + + }, + { + "llmModel":"qwen-turbo", + "supportChat":true, + "supportFunctionCalling":true, + "label":"通义千问-Turbo", + "description":"Qwen3系列Turbo模型,实现思考模式和非思考模式的有效融合,可在对话中切换模式。推理能力以更小参数规模比肩QwQ-32B、通用能力显著超过Qwen2.5-Turbo,达到同规模业界SOTA水平。" + }, + { + "llmModel":"qwen-max", + "supportChat":true, + "supportFunctionCalling":true, + "label":"通义千问-Max", + "description":"通义千问2.5系列千亿级别超大规模语言模型,支持中文、英文等不同语言输入。随着模型的升级,qwen-max将滚动更新升级。" + }, + { + "llmModel":"deepseek-r1-distill-qwen-7b", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-R1-Distill-Qwen-7B", + "description":"DeepSeek-R1-Distill-Qwen-7B是一个基于Qwen2.5-Math-7B的蒸馏大型语言模型,使用了 DeepSeek R1 的输出。" + }, + { + "llmModel":"deepseek-r1-distill-qwen-14b", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-R1-Distill-Qwen-14B", + "description":"DeepSeek-R1-Distill-Qwen-14B是一个基于Qwen2.5-14B的蒸馏大型语言模型,使用了 DeepSeek R1 的输出。" + }, + { + "llmModel":"deepseek-r1-distill-qwen-32b", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-R1-Distill-Qwen-32B", + "description":"DeepSeek-R1-Distill-Qwen-32B是一个基于Qwen2.5-32B的蒸馏大型语言模型,使用了 DeepSeek R1 的输出。" + }, + { + "llmModel":"qwen-vl-max", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"通义千问VL-Max", + "description":"通义千问VL-Max(qwen-vl-max),即通义千问超大规模视觉语言模型。相比增强版,再次提升视觉推理能力和指令遵循能力,提供更高的视觉感知和认知水平。在更多复杂任务上提供最佳的性能。" + }, + { + "llmModel":"qwen-vl-plus", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"通义千问VL-Plus", + "description":"通义千问VL-Plus(qwen-vl-plus),即通义千问大规模视觉语言模型增强版。大幅提升细节识别能力和文字识别能力,支持超百万像素分辨率和任意长宽比规格的图像。在广泛的视觉任务上提供卓越的性能。" + }, + { + "llmModel":"qvq-max", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"通义千问-QVQ-Max", + "description":"通义千问QVQ视觉推理模型,支持视觉输入及思维链输出,在数学、编程、视觉分析、创作以及通用任务上都表现了更强的能力。" + }, + { + "llmModel":"qvq-plus", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"通义千问-QVQ-Plus", + "description":"通义千问QVQ视觉推理模型增强版,支持视觉输入及思维链输出,在数学、编程、视觉分析、创作以及通用任务上都表现了更强的能力。" + }, + { + "llmModel":"text-embedding-v4", + "supportEmbed":true, + "label":"通用文本向量-v4", + "description":"通义实验室基于Qwen3训练的多语言文本统一向量模型,相较V3版本在文本检索、聚类、分类性能大幅提升;在MTEB多语言、中英、Code检索等评测任务上效果提升15%~40%;支持64~2048维用户自定义向量维度。" + }, + { + "llmModel":"text-embedding-v3", + "supportEmbed":true, + "label":"通用文本向量-v3", + "description":"通用文本向量,是通义实验室基于LLM底座的多语言文本统一向量模型,面向全球多个主流语种,提供高水准的向量服务,帮助开发者将文本数据快速转换为高质量的向量数据。" + } + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "label": "火山引擎", + "value": "volcengine", + "options":{ + "llmEndpoint":"https://ark.cn-beijing.volces.com", + "chatPath":"/api/v3/chat/completions", + "embedPath":"/api/v3/embeddings", + "modelList":[ + { + "llmModel":"doubao-seed-1-6-250615", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"doubao-seed-1.6", + "description":"全新多模态深度思考模型,同时支持 thinking、non-thinking、auto三种思考模式。其中 non-thinking 模型对比 doubao-1-5-pro-32k-250115 模型大幅提升。" + + }, + { + "llmModel":"doubao-seed-1-6-flash-250615", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"doubao-seed-1.6-flash", + "description":"有极致推理速度的多模态深度思考模型;同时支持文本和视觉理解。文本理解能力超过上一代 Lite 系列模型,视觉理解比肩友商 Pro 系列模型。" + }, + { + "llmModel":"doubao-seed-1-6-thinking-250715", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"doubao-seed-1.6-thinking", + "description":"在思考能力上进行了大幅强化, 对比 doubao 1.5 代深度理解模型,在编程、数学、逻辑推理等基础能力上进一步提升, 支持视觉理解。" + }, + { + "llmModel":"deepseek-r1-250528", + "supportChat":true, + "supportFunctionCalling":true, + "label":"deepseek-r1", + "description":"deepseek-r1 在后训练阶段大规模使用了强化学习技术,在数学、代码、自然语言推理等任务上,能力比肩 OpenAI o1 正式版。" + } + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "label": "百度千帆", + "value": "baidu", + "options":{ + "llmEndpoint":"https://qianfan.baidubce.com", + "chatPath":"/v2/chat/completions", + "embedPath":"/v2/embeddings", + "rerankPath":"/v2/rerank", + "modelList":[ + { + "llmModel":"ernie-x1-turbo-32k", + "supportChat":true, + "supportFunctionCalling":true, + "label":"ERNIE X1 Turbo", + "description":"核心定位:深度思考模型,具备更强的理解、规划、反思、进化能力。适用场景: 在中文知识问答、文学创作、文稿写作、日常对话、逻辑推理、复杂计算及工具调用等方面表现尤为出色。" + + }, + { + "llmModel":"ernie-4.5-turbo-128k", + "supportChat":true, + "supportFunctionCalling":true, + "label":"ERNIE 4.5 Turbo", + "description":"​核心定位:更好的满足多轮长历史对话处理、长文档理解问答任务。适用场景:​1)复杂语义理解:支持中文知识问答、文学创作,尤其擅长文档理解(如DocVQA任务)。 ​2)数学推理:在中文数学问题(CMath基准)表现突出。" + }, + { + "llmModel":"ernie-4.5-turbo-vl-32k", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"ERNIE 4.5 Turbo VL", + "description":"​核心定位:多模态基础模型,支持文本、图像跨模态输入与生成。​适用场景:结合图文生成营销文案、视频脚本设计等。" + }, + { + "llmModel":"deepseek-r1-250528", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-R1", + "description":"核心定位:专业优化推理模型,聚焦数学与逻辑任务。 ​适用场景: ​复杂数学问题:如高等数学题求解、科学计算模拟。 ​逻辑拆解与规划:业务流程自动化、学术研究中的假设验证。 ​STEM领域应用:物理建模、金融量化分析等需高精度推理的场景。" + } + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "label": "星火大模型", + "value": "spark", + "options":{ + "llmEndpoint":"https://spark-api-open.xf-yun.com", + "chatPath":"/v1/chat/completions", + "modelList":[ + { + "llmModel":"generalv3.5", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Spark Max", + "description":"旗舰级大语言模型,具有千亿级参数,核心能力全面升级,具备更强的数学、中文、代码和多模态能力。适用数理计算、逻辑推理等对效果有更高要求的业务场景。" + }, + { + "llmModel":"4.0Ultra", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Spark4.0 Ultra", + "description":"最强大的大语言模型版本,文本生成、语言理解、知识问答、逻辑推理、数学能力等方面实现超越GPT4 Turbo,优化联网搜索链路,提供更精准回答。" + } + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "label": "Gitee", + "value": "gitee", + "options":{ + "llmEndpoint":"https://ai.gitee.com", + "chatPath":"/v1/chat/completions", + "embedPath":"/v1/embeddings", + "rerankPath":"/v1/rerank", + "modelList":[ + { + "llmModel":"kimi-k2-instruct", + "supportChat":true, + "supportFunctionCalling":true, + "label":"kimi-k2-instruct", + "description":"Kimi K2 是一个最先进的混合专家 (MoE) 语言模型,激活参数为 320 亿,总参数为 1 万亿。通过 Muon 优化器进行训练,Kimi K2 在前沿知识、推理和编码任务上表现出色,同时在智能体能力方面进行了精心优化" + + }, + { + "llmModel":"ERNIE-4.5-Turbo", + "supportChat":true, + "supportFunctionCalling":true, + "label":"ERNIE-4.5-Turbo", + "description":"文心4.5 Turbo在去幻觉、逻辑推理和代码能力等方面也有着明显增强。对比文心4.5,速度更快、价格更低。" + + }, + { + "llmModel":"ERNIE-X1-Turbo", + "supportChat":true, + "supportFunctionCalling":true, + "label":"ERNIE-X1-Turbo", + "description":"文心ERNIE X1 Turbo具备更长的思维链,更强的深度思考能力,进一步增强了多模态和工具调用能力,擅长文学创作、逻辑推理等" + + }, + { + "llmModel":"DeepSeek-R1", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-R1", + "description":"DeepSeek-R1 是一款采用强化学习技术的推理模型,凭借少量标注数据大幅提升推理能力,性能媲美 OpenAI o1。" + + }, + { + "llmModel":"DeepSeek-V3", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-V3", + "description":"DeepSeek-V3 是 685B 参数的高效 MoE 语言模型,性能优越,训练稳定,超越开源模型,并接近顶级闭源模型。" + + }, + { + "llmModel":"Qwen3-235B-A22B", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Qwen3-235B-A22B", + "description":"Qwen3 是 Qwen 系列中的最新一代大型语言模型,提供了一系列密集型和混合专家(MoE)模型。基于在训练数据、模型架构和优化技术方面的广泛进步。" + + }, + { + "llmModel":"Qwen3-30B-A3B", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Qwen3-30B-A3B", + "description":"Qwen3 是 Qwen 系列中的最新一代大型语言模型,提供了一系列密集型和混合专家(MoE)模型。基于在训练数据、模型架构和优化技术方面的广泛进步。" + + }, + { + "llmModel":"Qwen3-32B", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Qwen3-32B", + "description":"Qwen3 是 Qwen 系列中的最新一代大型语言模型,提供了一系列密集型和混合专家(MoE)模型。基于在训练数据、模型架构和优化技术方面的广泛进步。" + + }, + { + "llmModel":"ERNIE-4.5-Turbo-VL", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"ERNIE-4.5-Turbo-VL", + "description":"文心一言大模型全新版本,图片理解、创作、翻译、代码等能力显著提升,首次支持32K上下文长度,首Token时延显著降低。" + + }, + { + "llmModel":"Qwen2.5-VL-32B-Instruct", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"Qwen2.5-VL-32B-Instruct", + "description":"Qwen2.5-VL-32B-Instruct 是一款拥有 320 亿参数、支持多图输入与复杂图文推理的大规模多模态指令微调模型。" + + }, + { + "llmModel":"InternVL3-78B", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"InternVL3-78B", + "description":"InternVL3-78B 是一款支持中英文、多图多轮对话的大规模多模态模型,具备超强图文理解、推理与生成能力,广泛适用于复杂 AI 应用场景。" + + }, + { + "llmModel":"InternVL3-38B", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"InternVL3-38B", + "description":"InternVL3-38B 是一款支持中英双语、多模态对话与图像理解的大规模视觉语言模型,具备强大的跨模态推理与视觉问答能力。" + }, + { + "llmModel":"Qwen3-Embedding-8B", + "supportEmbed":true, + "label":"Qwen3-Embedding-8B", + "description":"Qwen3‑Embedding‑8B 是 Qwen 系列推出的大规模嵌入模型,专注于生成高质量、多语言及代码向量,支持多种下游任务中的语义匹配与信息检索需求。" + }, + { + "llmModel":"Qwen3-Embedding-4B", + "supportEmbed":true, + "label":"Qwen3-Embedding-4B", + "description":"Qwen3-Embedding-4B 是由 Qwen 团队开发的一款高性能文本和代码嵌入模型,专为多语言、多模态任务设计,能够将文本和代码内容转换为语义丰富的向量表示。它广泛适用于语义搜索、跨语言检索、信息匹配、文本相似度分析等多种自然语言处理和代码理解场景。" + } + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "label": "硅基流动", + "value": "siliconlow", + "options":{ + "llmEndpoint":"https://api.siliconflow.cn", + "chatPath":"/v1/chat/completions", + "embedPath":"/v1/embeddings", + "rerankPath":"/v1/rerank", + "modelList":[ + { + "llmModel":"deepseek-ai/DeepSeek-R1", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-R1", + "description":"DeepSeek-R1-0528 是一款强化学习(RL)驱动的推理模型,解决了模型中的重复性和可读性问题。在 RL 之前,DeepSeek-R1 引入了冷启动数据,进一步优化了推理性能。它在数学、代码和推理任务中与 OpenAI-o1 表现相当,并且通过精心设计的训练方法,提升了整体效果。" + + }, + { + "llmModel":"deepseek-ai/DeepSeek-V3", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-V3", + "description":"新版 DeepSeek-V3 (DeepSeek-V3-0324)与之前的 DeepSeek-V3-1226 使用同样的 base 模型,仅改进了后训练方法。新版 V3 模型借鉴 DeepSeek-R1 模型训练过程中所使用的强化学习技术,大幅提高了在推理类任务上的表现水平,在数学、代码类相关评测集上取得了超过 GPT-4.5 的得分成绩。此外该模型在工具调用、角色扮演、问答闲聊等方面也得到了一定幅度的能力提升。" + + }, + { + "llmModel":"moonshotai/Kimi-K2-Instruct", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Kimi-K2-Instruct", + "description":"Kimi K2 是一款具备超强代码和 Agent 能力的 MoE 架构基础模型,总参数 1T,激活参数 32B。在通用知识推理、编程、数学、Agent 等主要类别的基准性能测试中,K2 模型的性能超过其他主流开源模型" + + }, + { + "llmModel":"Tongyi-Zhiwen/QwenLong-L1-32B", + "supportChat":true, + "supportFunctionCalling":true, + "label":"QwenLong-L1-32B", + "description":"QwenLong-L1-32B 是首个使用强化学习训练的长上下文大型推理模型(LRM),专门针对长文本推理任务进行优化。该模型通过渐进式上下文扩展的强化学习框架,实现了从短上下文到长上下文的稳定迁移。在七个长上下文文档问答基准测试中,QwenLong-L1-32B 超越了 OpenAI-o3-mini 和 Qwen3-235B-A22B 等旗舰模型,性能可媲美 Claude-3.7-Sonnet-Thinking。该模型特别擅长数学推理、逻辑推理和多跳推理等复杂任务" + + }, + { + "llmModel":"Qwen/Qwen3-30B-A3B", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Qwen3-30B-A3B", + "description":"Qwen3-30B-A3B 是通义千问系列的最新大语言模型,采用混合专家(MoE)架构,拥有 30.5B 总参数量和 3.3B 激活参数量。该模型独特地支持在思考模式(适用于复杂逻辑推理、数学和编程)和非思考模式(适用于高效的通用对话)之间无缝切换,显著增强了推理能力。模型在数学、代码生成和常识逻辑推理上表现优异,并在创意写作、角色扮演和多轮对话等方面展现出卓越的人类偏好对齐能力。此外,该模型支持 100 多种语言和方言,具备出色的多语言指令遵循和翻译能力" + + }, + { + "llmModel":"Qwen/Qwen3-32B", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Qwen3-32B", + "description":"Qwen3-32B 是通义千问系列的最新大语言模型,拥有 32.8B 参数量。该模型独特地支持在思考模式(适用于复杂逻辑推理、数学和编程)和非思考模式(适用于高效的通用对话)之间无缝切换,显著增强了推理能力。模型在数学、代码生成和常识逻辑推理上表现优异,并在创意写作、角色扮演和多轮对话等方面展现出卓越的人类偏好对齐能力。此外,该模型支持 100 多种语言和方言,具备出色的多语言指令遵循和翻译能力" + + }, + { + "llmModel":"MiniMaxAI/MiniMax-M1-80k", + "supportChat":true, + "supportFunctionCalling":true, + "label":"MiniMax-M1-80k", + "description":"MiniMax-M1 是开源权重的大规模混合注意力推理模型,拥有 4560 亿参数,每个 Token 可激活约 459 亿参数。模型原生支持 100 万 Token 的超长上下文,并通过闪电注意力机制,在 10 万 Token 的生成任务中相比 DeepSeek R1 节省 75% 的浮点运算量。同时,MiniMax-M1 采用 MoE(混合专家)架构,结合 CISPO 算法与混合注意力设计的高效强化学习训练,在长输入推理与真实软件工程场景中实现了业界领先的性能。" + + }, + { + "llmModel":"THUDM/GLM-4.1V-9B-Thinking", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"GLM-4.1V-9B-Thinking", + "description":"GLM-4.1V-9B-Thinking 是由智谱 AI 和清华大学 KEG 实验室联合发布的一款开源视觉语言模型(VLM),专为处理复杂的多模态认知任务而设计。该模型基于 GLM-4-9B-0414 基础模型,通过引入“思维链”(Chain-of-Thought)推理机制和采用强化学习策略,显著提升了其跨模态的推理能力和稳定性。作为一个 9B 参数规模的轻量级模型,它在部署效率和性能之间取得了平衡,在 28 项权威评测基准中,有 18 项的表现持平甚至超越了 72B 参数规模的 Qwen-2.5-VL-72B。该模型不仅在图文理解、数学科学推理、视频理解等任务上表现卓越,还支持高达 4K 分辨率的图像和任意宽高比输入" + }, + { + "llmModel":"Qwen/Qwen2.5-VL-32B-Instruct", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"Qwen2.5-VL-32B-Instruct", + "description":"Qwen2.5-VL-32B-Instruct 是通义千问团队推出的多模态大模型,是 Qwen2.5-VL 系列的一部分。该模型不仅精通识别常见物体,还能分析图像中的文本、图表、图标、图形和布局。它可作为视觉智能体,能够推理并动态操控工具,具备使用电脑和手机的能力。此外,这个模型可以精确定位图像中的对象,并为发票、表格等生成结构化输出。相比前代模型 Qwen2-VL,该版本在数学和问题解决能力方面通过强化学习得到了进一步提升,响应风格也更符合人类偏好" + }, + { + "llmModel":"Qwen/Qwen2.5-VL-72B-Instruct", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"Qwen2.5-VL-72B-Instruct", + "description":"Qwen2.5-VL 是 Qwen2.5 系列中的视觉语言模型。该模型在多方面有显著提升:具备更强的视觉理解能力,能够识别常见物体、分析文本、图表和布局;作为视觉代理能够推理并动态指导工具使用;支持理解超过 1 小时的长视频并捕捉关键事件;能够通过生成边界框或点准确定位图像中的物体;支持生成结构化输出,尤其适用于发票、表格等扫描数据。模型在多项基准测试中表现出色,包括图像、视频和代理任务评测" + }, + { + "llmModel":"deepseek-ai/deepseek-vl2", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"deepseek-vl2", + "description":"DeepSeek-VL2 是一个基于 DeepSeekMoE-27B 开发的混合专家(MoE)视觉语言模型,采用稀疏激活的 MoE 架构,在仅激活 4.5B 参数的情况下实现了卓越性能。该模型在视觉问答、光学字符识别、文档/表格/图表理解和视觉定位等多个任务中表现优异,与现有的开源稠密模型和基于 MoE 的模型相比,在使用相同或更少的激活参数的情况下,实现了具有竞争力的或最先进的性能表现" + }, + { + "llmModel":"Qwen/Qwen3-Embedding-8B", + "supportEmbed":true, + "label":"Qwen3-Embedding-8B", + "description":"Qwen3-Embedding-8B 是 Qwen3 嵌入模型系列的最新专有模型,专为文本嵌入和排序任务设计。该模型基于 Qwen3 系列的密集基础模型,具有 80 亿参数规模,支持长达 32K 的上下文长度,可生成最高 4096 维的嵌入向量。该模型继承了基础模型卓越的多语言能力,支持超过 100 种语言,具备长文本理解和推理能力。在 MTEB 多语言排行榜上排名第一(截至 2025 年 6 月 5 日,得分 70.58),在文本检索、代码检索、文本分类、文本聚类和双语挖掘等多项任务中表现出色。模型支持用户自定义输出维度(32 到 4096)和指令感知功能,可根据特定任务、语言或场景进行优化" + }, + { + "llmModel":"Qwen/Qwen3-Embedding-4B", + "supportEmbed":true, + "label":"Qwen3-Embedding-4B", + "description":"Qwen3-Embedding-4B 是 Qwen3 嵌入模型系列的最新专有模型,专为文本嵌入和排序任务设计。该模型基于 Qwen3 系列的密集基础模型,具有 40 亿参数规模,支持长达 32K 的上下文长度,可生成最高 2560 维的嵌入向量。模型继承了基础模型卓越的多语言能力,支持超过 100 种语言,具备长文本理解和推理能力。在 MTEB 多语言排行榜上表现卓越(得分 69.45),在文本检索、代码检索、文本分类、文本聚类和双语挖掘等多项任务中表现出色。模型支持用户自定义输出维度(32 到 2560)和指令感知功能,可根据特定任务、语言或场景进行优化,在效率和效果之间达到良好平衡" + }, + { + "llmModel":"BAAI/bge-m3", + "supportEmbed":true, + "label":"bge-m3", + "description":"BGE-M3 是一个多功能、多语言、多粒度的文本嵌入模型。它支持三种常见的检索功能:密集检索、多向量检索和稀疏检索。该模型可以处理超过100种语言,并且能够处理从短句到长达8192个词元的长文档等不同粒度的输入。BGE-M3在多语言和跨语言检索任务中表现出色,在 MIRACL 和 MKQA 等基准测试中取得了领先结果。它还具有处理长文档检索的能力,在 MLDR 和 NarritiveQA 等数据集上展现了优秀性能" + }, + { + "llmModel":"netease-youdao/bce-embedding-base_v1", + "supportEmbed":true, + "label":"bce-embedding-base_v1", + "description":"bce-embedding-base_v1 是由网易有道开发的双语和跨语言嵌入模型。该模型在中英文语义表示和检索任务中表现出色,尤其擅长跨语言场景。它是为检索增强生成(RAG)系统优化的,可以直接应用于教育、医疗、法律等多个领域。该模型不需要特定指令即可使用,能够高效地生成语义向量,为语义搜索和问答系统提供关键支持" + } + + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "label": "Ollama", + "value": "ollama", + "options":{ + + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + } +] diff --git a/app/src/views/ai/plugin/AddPluginModal.vue b/app/src/views/ai/plugin/AddPluginModal.vue new file mode 100644 index 0000000..ea1915a --- /dev/null +++ b/app/src/views/ai/plugin/AddPluginModal.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/app/src/views/ai/plugin/AiPluginToolModal.vue b/app/src/views/ai/plugin/AiPluginToolModal.vue new file mode 100644 index 0000000..4984421 --- /dev/null +++ b/app/src/views/ai/plugin/AiPluginToolModal.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/app/src/views/ai/plugin/CategoryPluginModal.vue b/app/src/views/ai/plugin/CategoryPluginModal.vue new file mode 100644 index 0000000..33f60e0 --- /dev/null +++ b/app/src/views/ai/plugin/CategoryPluginModal.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/app/src/views/ai/plugin/Plugin.vue b/app/src/views/ai/plugin/Plugin.vue new file mode 100644 index 0000000..4db952f --- /dev/null +++ b/app/src/views/ai/plugin/Plugin.vue @@ -0,0 +1,308 @@ + + + + + diff --git a/app/src/views/ai/plugin/PluginInputAndOutParams.vue b/app/src/views/ai/plugin/PluginInputAndOutParams.vue new file mode 100644 index 0000000..420b1aa --- /dev/null +++ b/app/src/views/ai/plugin/PluginInputAndOutParams.vue @@ -0,0 +1,703 @@ + + + + + diff --git a/app/src/views/ai/plugin/PluginRunParams.vue b/app/src/views/ai/plugin/PluginRunParams.vue new file mode 100644 index 0000000..1a97a97 --- /dev/null +++ b/app/src/views/ai/plugin/PluginRunParams.vue @@ -0,0 +1,259 @@ + + + + + diff --git a/app/src/views/ai/plugin/PluginRunTestModal.vue b/app/src/views/ai/plugin/PluginRunTestModal.vue new file mode 100644 index 0000000..5e6672b --- /dev/null +++ b/app/src/views/ai/plugin/PluginRunTestModal.vue @@ -0,0 +1,215 @@ + + + + + diff --git a/app/src/views/ai/plugin/PluginToolCollapse.vue b/app/src/views/ai/plugin/PluginToolCollapse.vue new file mode 100644 index 0000000..d879ed0 --- /dev/null +++ b/app/src/views/ai/plugin/PluginToolCollapse.vue @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/views/ai/plugin/PluginToolEdit.vue b/app/src/views/ai/plugin/PluginToolEdit.vue new file mode 100644 index 0000000..80c3dd6 --- /dev/null +++ b/app/src/views/ai/plugin/PluginToolEdit.vue @@ -0,0 +1,763 @@ + + + + + diff --git a/app/src/views/ai/plugin/PluginToolTable.vue b/app/src/views/ai/plugin/PluginToolTable.vue new file mode 100644 index 0000000..918f2a0 --- /dev/null +++ b/app/src/views/ai/plugin/PluginToolTable.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/app/src/views/ai/plugin/PluginTools.vue b/app/src/views/ai/plugin/PluginTools.vue new file mode 100644 index 0000000..fba61a8 --- /dev/null +++ b/app/src/views/ai/plugin/PluginTools.vue @@ -0,0 +1,62 @@ + + + diff --git a/app/src/views/ai/resource/ChooseResource.vue b/app/src/views/ai/resource/ChooseResource.vue new file mode 100644 index 0000000..110ae80 --- /dev/null +++ b/app/src/views/ai/resource/ChooseResource.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/app/src/views/ai/resource/PreviewModal.vue b/app/src/views/ai/resource/PreviewModal.vue new file mode 100644 index 0000000..e8c09f7 --- /dev/null +++ b/app/src/views/ai/resource/PreviewModal.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/app/src/views/ai/resource/ResourceCardList.vue b/app/src/views/ai/resource/ResourceCardList.vue new file mode 100644 index 0000000..f7da0c7 --- /dev/null +++ b/app/src/views/ai/resource/ResourceCardList.vue @@ -0,0 +1,155 @@ + + + + + diff --git a/app/src/views/ai/resource/ResourceList.vue b/app/src/views/ai/resource/ResourceList.vue new file mode 100644 index 0000000..6716a2f --- /dev/null +++ b/app/src/views/ai/resource/ResourceList.vue @@ -0,0 +1,467 @@ + + + diff --git a/app/src/views/ai/resource/ResourceModal.vue b/app/src/views/ai/resource/ResourceModal.vue new file mode 100644 index 0000000..f6e5c38 --- /dev/null +++ b/app/src/views/ai/resource/ResourceModal.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/app/src/views/ai/workflow/RunPage.vue b/app/src/views/ai/workflow/RunPage.vue new file mode 100644 index 0000000..dd77341 --- /dev/null +++ b/app/src/views/ai/workflow/RunPage.vue @@ -0,0 +1,133 @@ + + + diff --git a/app/src/views/ai/workflow/WorkflowDesign.vue b/app/src/views/ai/workflow/WorkflowDesign.vue new file mode 100644 index 0000000..b8e96f4 --- /dev/null +++ b/app/src/views/ai/workflow/WorkflowDesign.vue @@ -0,0 +1,312 @@ + + + + + diff --git a/app/src/views/ai/workflow/WorkflowList.vue b/app/src/views/ai/workflow/WorkflowList.vue new file mode 100644 index 0000000..e673e23 --- /dev/null +++ b/app/src/views/ai/workflow/WorkflowList.vue @@ -0,0 +1,435 @@ + + + + + diff --git a/app/src/views/ai/workflow/WorkflowModal.vue b/app/src/views/ai/workflow/WorkflowModal.vue new file mode 100644 index 0000000..e06ee9f --- /dev/null +++ b/app/src/views/ai/workflow/WorkflowModal.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/app/src/views/ai/workflow/components/ConfirmItem.vue b/app/src/views/ai/workflow/components/ConfirmItem.vue new file mode 100644 index 0000000..a62db12 --- /dev/null +++ b/app/src/views/ai/workflow/components/ConfirmItem.vue @@ -0,0 +1,211 @@ + + + + + diff --git a/app/src/views/ai/workflow/components/ConfirmItemMulti.vue b/app/src/views/ai/workflow/components/ConfirmItemMulti.vue new file mode 100644 index 0000000..42789f3 --- /dev/null +++ b/app/src/views/ai/workflow/components/ConfirmItemMulti.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/app/src/views/ai/workflow/components/ExecResult.vue b/app/src/views/ai/workflow/components/ExecResult.vue new file mode 100644 index 0000000..f43db67 --- /dev/null +++ b/app/src/views/ai/workflow/components/ExecResult.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/app/src/views/ai/workflow/components/ExecResultItem.vue b/app/src/views/ai/workflow/components/ExecResultItem.vue new file mode 100644 index 0000000..2ac3563 --- /dev/null +++ b/app/src/views/ai/workflow/components/ExecResultItem.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/app/src/views/ai/workflow/components/SingleRun.vue b/app/src/views/ai/workflow/components/SingleRun.vue new file mode 100644 index 0000000..0a1283b --- /dev/null +++ b/app/src/views/ai/workflow/components/SingleRun.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/app/src/views/ai/workflow/components/WorkflowForm.vue b/app/src/views/ai/workflow/components/WorkflowForm.vue new file mode 100644 index 0000000..388e5bd --- /dev/null +++ b/app/src/views/ai/workflow/components/WorkflowForm.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/app/src/views/ai/workflow/components/WorkflowFormItem.vue b/app/src/views/ai/workflow/components/WorkflowFormItem.vue new file mode 100644 index 0000000..7249d44 --- /dev/null +++ b/app/src/views/ai/workflow/components/WorkflowFormItem.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/app/src/views/ai/workflow/components/WorkflowSteps.vue b/app/src/views/ai/workflow/components/WorkflowSteps.vue new file mode 100644 index 0000000..e12ae9f --- /dev/null +++ b/app/src/views/ai/workflow/components/WorkflowSteps.vue @@ -0,0 +1,242 @@ + + + + + diff --git a/app/src/views/ai/workflow/customNode/documentNode.ts b/app/src/views/ai/workflow/customNode/documentNode.ts new file mode 100644 index 0000000..7529f1a --- /dev/null +++ b/app/src/views/ai/workflow/customNode/documentNode.ts @@ -0,0 +1,37 @@ +import { $t } from '#/locales'; + +import nodeNames from './nodeNames'; + +export default { + [nodeNames.documentNode]: { + title: $t('aiWorkflow.fileContentExtraction'), + group: 'base', + description: $t('aiWorkflow.descriptions.fileContentExtraction'), + icon: '', + sortNo: 801, + parametersAddEnable: false, + outputDefsAddEnable: false, + parameters: [ + { + name: 'fileUrl', + nameDisabled: true, + title: $t('aiWorkflow.documentAddress'), + dataType: 'File', + required: true, + description: $t('aiWorkflow.descriptions.documentAddress'), + }, + ], + outputDefs: [ + { + name: 'content', + title: $t('aiWorkflow.parsedText'), + dataType: 'String', + dataTypeDisabled: true, + required: true, + parametersAddEnable: false, + description: $t('aiWorkflow.descriptions.parsedText'), + deleteDisabled: true, + }, + ], + }, +}; diff --git a/app/src/views/ai/workflow/customNode/downloadNode.ts b/app/src/views/ai/workflow/customNode/downloadNode.ts new file mode 100644 index 0000000..0fa7182 --- /dev/null +++ b/app/src/views/ai/workflow/customNode/downloadNode.ts @@ -0,0 +1,90 @@ +import { $t } from '#/locales'; + +import nodeNames from './nodeNames'; + +export default { + [nodeNames.downloadNode]: { + title: $t('aiWorkflow.resourceSync'), + group: 'base', + description: $t('aiWorkflow.descriptions.resourceSync'), + icon: '', + sortNo: 811, + parametersAddEnable: false, + outputDefsAddEnable: false, + parameters: [ + { + name: 'originUrl', + nameDisabled: true, + title: $t('aiWorkflow.originUrl'), + dataType: 'String', + required: true, + description: $t('aiWorkflow.descriptions.originUrl'), + }, + ], + outputDefs: [ + { + name: 'resourceUrl', + title: $t('aiWorkflow.savedUrl'), + dataType: 'String', + dataTypeDisabled: true, + required: true, + parametersAddEnable: false, + description: $t('aiWorkflow.savedUrl'), + deleteDisabled: true, + }, + ], + forms: [ + // 节点表单 + { + // 'input' | 'textarea' | 'select' | 'slider' | 'heading' | 'chosen' + type: 'heading', + label: $t('aiWorkflow.saveOptions'), + }, + { + type: 'select', + label: $t('aiResource.resourceType'), + description: $t('aiWorkflow.descriptions.resourceType'), + name: 'resourceType', // 属性名称 + defaultValue: '99', + options: [ + { + label: $t('aiWorkflow.image'), + value: '0', + }, + { + label: $t('aiWorkflow.video'), + value: '1', + }, + { + label: $t('aiWorkflow.audio'), + value: '2', + }, + { + label: $t('aiWorkflow.document'), + value: '3', + }, + { + label: $t('aiWorkflow.other'), + value: '99', + }, + ], + }, + // { + // // 用法可参考插件节点的代码 + // type: 'chosen', + // label: '插件选择', + // chosen: { + // // 节点自定义属性 + // labelDataKey: 'pluginName', + // valueDataKey: 'pluginId', + // // updateNodeData 可动态更新节点属性 + // // value 为选中的 value + // // label 为选中的 label + // onChosen: ((updateNodeData: (data: Record) => void, value?: string, label?: string, event?: Event) => { + // console.warn('No onChosen handler provided for plugin-node'); + // }) + // } + // } + ], + }, +}; diff --git a/app/src/views/ai/workflow/customNode/index.ts b/app/src/views/ai/workflow/customNode/index.ts new file mode 100644 index 0000000..6356070 --- /dev/null +++ b/app/src/views/ai/workflow/customNode/index.ts @@ -0,0 +1,29 @@ +import docNode from './documentNode'; +import downloadNode from './downloadNode'; +import makeFileNode from './makeFileNode'; +import nodeNames from './nodeNames'; +import { PluginNode } from './pluginNode'; +import { SaveToDatacenterNode } from './saveToDatacenter'; +import { SearchDatacenterNode } from './searchDatacenter'; +import sqlNode from './sqlNode'; +import { WorkflowNode } from './workflowNode'; + +export interface CustomNodeOptions { + handleChosen?: (nodeType: string, updateNodeData: any, value: string) => void; +} +export const getCustomNode = async (options: CustomNodeOptions) => { + const pluginNode = PluginNode({ onChosen: options.handleChosen }); + const workflowNode = WorkflowNode({ onChosen: options.handleChosen }); + const searchDatacenterNode = await SearchDatacenterNode(); + const saveToDatacenterNode = await SaveToDatacenterNode(); + return { + ...docNode, + ...makeFileNode, + ...downloadNode, + ...sqlNode, + [nodeNames.pluginNode]: pluginNode, + [nodeNames.workflowNode]: workflowNode, + [nodeNames.searchDatacenterNode]: searchDatacenterNode, + [nodeNames.saveToDatacenterNode]: saveToDatacenterNode, + }; +}; diff --git a/app/src/views/ai/workflow/customNode/makeFileNode.ts b/app/src/views/ai/workflow/customNode/makeFileNode.ts new file mode 100644 index 0000000..9fc484d --- /dev/null +++ b/app/src/views/ai/workflow/customNode/makeFileNode.ts @@ -0,0 +1,58 @@ +import { $t } from '#/locales'; + +import nodeNames from './nodeNames'; + +export default { + [nodeNames.makeFileNode]: { + title: $t('aiWorkflow.fileGeneration'), + group: 'base', + description: $t('aiWorkflow.descriptions.fileGeneration'), + icon: '', + sortNo: 802, + parametersAddEnable: true, + outputDefsAddEnable: true, + forms: [ + { + type: 'heading', + label: $t('aiWorkflow.fileSettings'), + }, + { + type: 'select', + label: $t('documentCollection.splitterDoc.fileType'), + description: $t('aiWorkflow.descriptions.fileType'), + name: 'suffix', + defaultValue: 'docx', + options: [ + { + label: 'docx', + value: 'docx', + }, + ], + }, + ], + parameters: [ + { + name: 'content', + nameDisabled: true, + title: $t('preferences.content'), + dataType: 'String', + required: true, + description: $t('preferences.content'), + deleteDisabled: true, + }, + ], + outputDefs: [ + { + name: 'url', + nameDisabled: true, + title: $t('aiWorkflow.fileDownloadURL'), + dataType: 'String', + dataTypeDisabled: true, + required: true, + parametersAddEnable: false, + description: $t('aiWorkflow.descriptions.fileDownloadURL'), + deleteDisabled: true, + }, + ], + }, +}; diff --git a/app/src/views/ai/workflow/customNode/nodeNames.ts b/app/src/views/ai/workflow/customNode/nodeNames.ts new file mode 100644 index 0000000..d727f0c --- /dev/null +++ b/app/src/views/ai/workflow/customNode/nodeNames.ts @@ -0,0 +1,10 @@ +export default { + documentNode: 'document-node', + makeFileNode: 'make-file', + downloadNode: 'download-node', + sqlNode: 'sql-node', + pluginNode: 'plugin-node', + workflowNode: 'workflow-node', + searchDatacenterNode: 'search-datacenter-node', + saveToDatacenterNode: 'save-to-datacenter-node', +}; diff --git a/app/src/views/ai/workflow/customNode/pluginNode.ts b/app/src/views/ai/workflow/customNode/pluginNode.ts new file mode 100644 index 0000000..acf58a6 --- /dev/null +++ b/app/src/views/ai/workflow/customNode/pluginNode.ts @@ -0,0 +1,30 @@ +import { $t } from '#/locales'; + +import nodeNames from './nodeNames'; + +export interface PluginNodeOptions { + onChosen?: (nodeType: string, updateNodeData: any, value: string) => void; +} + +export const PluginNode = (options: PluginNodeOptions = {}) => ({ + title: $t('menus.ai.plugin'), + group: 'base', + description: $t('aiWorkflow.descriptions.plugin'), + icon: '', + sortNo: 810, + parametersAddEnable: false, + outputDefsAddEnable: false, + forms: [ + { + type: 'chosen', + label: $t('aiWorkflow.pluginSelect'), + chosen: { + labelDataKey: 'pluginName', + valueDataKey: 'pluginId', + onChosen: (updateNodeData: any, value: any) => { + options.onChosen?.(nodeNames.pluginNode, updateNodeData, value); + }, + }, + }, + ], +}); diff --git a/app/src/views/ai/workflow/customNode/saveToDatacenter.ts b/app/src/views/ai/workflow/customNode/saveToDatacenter.ts new file mode 100644 index 0000000..71cba45 --- /dev/null +++ b/app/src/views/ai/workflow/customNode/saveToDatacenter.ts @@ -0,0 +1,58 @@ +import { getOptions } from '@aiflowy/utils'; + +import { api } from '#/api/request'; +import { $t } from '#/locales'; + +export const SaveToDatacenterNode = async () => { + const res = await api.get('/api/v1/datacenterTable/list'); + + return { + title: $t('aiWorkflow.saveData'), + group: 'base', + description: $t('aiWorkflow.descriptions.saveData'), + icon: '', + sortNo: 812, + parametersAddEnable: false, + outputDefsAddEnable: false, + parameters: [ + { + name: 'saveList', + title: $t('aiWorkflow.dataToBeSaved'), + dataType: 'Array', + dataTypeDisabled: true, + required: true, + parametersAddEnable: false, + description: $t('aiWorkflow.descriptions.dataToBeSaved'), + deleteDisabled: true, + nameDisabled: true, + }, + ], + outputDefs: [ + { + name: 'successRows', + title: $t('aiWorkflow.successInsertedRecords'), + dataType: 'Number', + dataTypeDisabled: true, + required: true, + parametersAddEnable: false, + description: $t('aiWorkflow.successInsertedRecords'), + deleteDisabled: true, + nameDisabled: true, + }, + ], + forms: [ + { + type: 'heading', + label: $t('aiWorkflow.dataTable'), + }, + { + type: 'select', + label: '', + description: $t('aiWorkflow.descriptions.dataTable'), + name: 'tableId', + defaultValue: '', + options: getOptions('tableName', 'id', res.data), + }, + ], + }; +}; diff --git a/app/src/views/ai/workflow/customNode/searchDatacenter.ts b/app/src/views/ai/workflow/customNode/searchDatacenter.ts new file mode 100644 index 0000000..48e070c --- /dev/null +++ b/app/src/views/ai/workflow/customNode/searchDatacenter.ts @@ -0,0 +1,68 @@ +import { getOptions } from '@aiflowy/utils'; + +import { api } from '#/api/request'; +import { $t } from '#/locales'; + +export const SearchDatacenterNode = async () => { + const res = await api.get('/api/v1/datacenterTable/list'); + + return { + title: $t('aiWorkflow.queryData'), + group: 'base', + description: $t('aiWorkflow.descriptions.queryData'), + icon: '', + sortNo: 813, + parametersAddEnable: true, + outputDefsAddEnable: false, + parameters: [], + outputDefs: [ + { + name: 'rows', + title: $t('aiWorkflow.queryResult'), + dataType: 'Array', + dataTypeDisabled: true, + required: true, + parametersAddEnable: false, + description: $t('aiWorkflow.queryResult'), + deleteDisabled: true, + nameDisabled: false, + }, + ], + forms: [ + { + type: 'heading', + label: $t('aiWorkflow.dataTable'), + }, + { + type: 'select', + label: '', + description: $t('aiWorkflow.descriptions.dataTable'), + name: 'tableId', + defaultValue: '', + options: getOptions('tableName', 'id', res.data), + }, + { + type: 'heading', + label: $t('aiWorkflow.filterConditions'), + }, + { + type: 'textarea', + label: $t('aiWorkflow.descriptions.filterConditions'), + description: '', + name: 'where', + defaultValue: '', + }, + { + type: 'heading', + label: $t('aiWorkflow.limit'), + }, + { + type: 'input', + label: '', + description: '', + name: 'limit', + defaultValue: '10', + }, + ], + }; +}; diff --git a/app/src/views/ai/workflow/customNode/sqlNode.ts b/app/src/views/ai/workflow/customNode/sqlNode.ts new file mode 100644 index 0000000..7806bd6 --- /dev/null +++ b/app/src/views/ai/workflow/customNode/sqlNode.ts @@ -0,0 +1,37 @@ +import { $t } from '#/locales'; + +import nodeNames from './nodeNames'; + +export default { + [nodeNames.sqlNode]: { + title: $t('aiWorkflow.sqlQuery'), + group: 'base', + description: $t('aiWorkflow.descriptions.sqlQuery'), + icon: '', + sortNo: 803, + parametersAddEnable: true, + outputDefsAddEnable: true, + parameters: [], + forms: [ + { + name: 'sql', + type: 'textarea', + label: 'SQL', + placeholder: $t('aiWorkflow.descriptions.enterSQL'), + }, + ], + outputDefs: [ + { + name: 'queryData', + title: $t('aiWorkflow.queryResult'), + dataType: 'Array', + dataTypeDisabled: true, + required: true, + parametersAddEnable: false, + description: $t('aiWorkflow.descriptions.queryResultJson'), + deleteDisabled: true, + nameDisabled: true, + }, + ], + }, +}; diff --git a/app/src/views/ai/workflow/customNode/workflowNode.ts b/app/src/views/ai/workflow/customNode/workflowNode.ts new file mode 100644 index 0000000..486e38d --- /dev/null +++ b/app/src/views/ai/workflow/customNode/workflowNode.ts @@ -0,0 +1,30 @@ +import { $t } from '#/locales'; + +import nodeNames from './nodeNames'; + +export interface WorkflowNodeOptions { + onChosen?: (nodeType: string, updateNodeData: any, value: string) => void; +} + +export const WorkflowNode = (options: WorkflowNodeOptions = {}) => ({ + title: $t('aiWorkflow.subProcess'), + group: 'base', + description: $t('aiWorkflow.descriptions.subProcess'), + icon: '', + sortNo: 815, + parametersAddEnable: false, + outputDefsAddEnable: false, + forms: [ + { + type: 'chosen', + label: $t('aiWorkflow.workflowSelect'), + chosen: { + labelDataKey: 'workflowName', + valueDataKey: 'workflowId', + onChosen: (updateNodeData: any, value: any) => { + options.onChosen?.(nodeNames.workflowNode, updateNodeData, value); + }, + }, + }, + ], +}); diff --git a/app/src/views/ai/workflow/execute/WorkflowExecResultList.vue b/app/src/views/ai/workflow/execute/WorkflowExecResultList.vue new file mode 100644 index 0000000..8b925e2 --- /dev/null +++ b/app/src/views/ai/workflow/execute/WorkflowExecResultList.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/app/src/views/ai/workflow/execute/WorkflowExecStepList.vue b/app/src/views/ai/workflow/execute/WorkflowExecStepList.vue new file mode 100644 index 0000000..4d24c6e --- /dev/null +++ b/app/src/views/ai/workflow/execute/WorkflowExecStepList.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/app/src/views/config/apikey/SysApiKey.vue b/app/src/views/config/apikey/SysApiKey.vue new file mode 100644 index 0000000..a97256e --- /dev/null +++ b/app/src/views/config/apikey/SysApiKey.vue @@ -0,0 +1,30 @@ + + + + diff --git a/app/src/views/config/apikey/SysApiKeyList.vue b/app/src/views/config/apikey/SysApiKeyList.vue new file mode 100644 index 0000000..7291847 --- /dev/null +++ b/app/src/views/config/apikey/SysApiKeyList.vue @@ -0,0 +1,209 @@ + + + diff --git a/app/src/views/config/apikey/SysApiKeyModal.vue b/app/src/views/config/apikey/SysApiKeyModal.vue new file mode 100644 index 0000000..2a837fd --- /dev/null +++ b/app/src/views/config/apikey/SysApiKeyModal.vue @@ -0,0 +1,259 @@ + + + + + diff --git a/app/src/views/config/apikey/SysApiKeyResourcePermissionList.vue b/app/src/views/config/apikey/SysApiKeyResourcePermissionList.vue new file mode 100644 index 0000000..9285e6c --- /dev/null +++ b/app/src/views/config/apikey/SysApiKeyResourcePermissionList.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/app/src/views/config/apikey/SysApiKeyResourcePermissionModal.vue b/app/src/views/config/apikey/SysApiKeyResourcePermissionModal.vue new file mode 100644 index 0000000..ff1a365 --- /dev/null +++ b/app/src/views/config/apikey/SysApiKeyResourcePermissionModal.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/app/src/views/config/apikey/SysApiKeyResourcePermissionSelectModal.vue b/app/src/views/config/apikey/SysApiKeyResourcePermissionSelectModal.vue new file mode 100644 index 0000000..350b479 --- /dev/null +++ b/app/src/views/config/apikey/SysApiKeyResourcePermissionSelectModal.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/app/src/views/config/settings/Settings.vue b/app/src/views/config/settings/Settings.vue new file mode 100644 index 0000000..25c6e3f --- /dev/null +++ b/app/src/views/config/settings/Settings.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/app/src/views/dashboard/analytics/analytics-trends.vue b/app/src/views/dashboard/analytics/analytics-trends.vue new file mode 100644 index 0000000..3e0812e --- /dev/null +++ b/app/src/views/dashboard/analytics/analytics-trends.vue @@ -0,0 +1,98 @@ + + + diff --git a/app/src/views/dashboard/analytics/analytics-visits-data.vue b/app/src/views/dashboard/analytics/analytics-visits-data.vue new file mode 100644 index 0000000..31bc6b7 --- /dev/null +++ b/app/src/views/dashboard/analytics/analytics-visits-data.vue @@ -0,0 +1,82 @@ + + + diff --git a/app/src/views/dashboard/analytics/analytics-visits-sales.vue b/app/src/views/dashboard/analytics/analytics-visits-sales.vue new file mode 100644 index 0000000..5605594 --- /dev/null +++ b/app/src/views/dashboard/analytics/analytics-visits-sales.vue @@ -0,0 +1,46 @@ + + + diff --git a/app/src/views/dashboard/analytics/analytics-visits-source.vue b/app/src/views/dashboard/analytics/analytics-visits-source.vue new file mode 100644 index 0000000..8734d86 --- /dev/null +++ b/app/src/views/dashboard/analytics/analytics-visits-source.vue @@ -0,0 +1,65 @@ + + + diff --git a/app/src/views/dashboard/analytics/analytics-visits.vue b/app/src/views/dashboard/analytics/analytics-visits.vue new file mode 100644 index 0000000..aa57c10 --- /dev/null +++ b/app/src/views/dashboard/analytics/analytics-visits.vue @@ -0,0 +1,55 @@ + + + diff --git a/app/src/views/dashboard/analytics/index.vue b/app/src/views/dashboard/analytics/index.vue new file mode 100644 index 0000000..8957816 --- /dev/null +++ b/app/src/views/dashboard/analytics/index.vue @@ -0,0 +1,90 @@ + + + diff --git a/app/src/views/dashboard/workspace/index.vue b/app/src/views/dashboard/workspace/index.vue new file mode 100644 index 0000000..9fc4fb5 --- /dev/null +++ b/app/src/views/dashboard/workspace/index.vue @@ -0,0 +1,266 @@ + + + diff --git a/app/src/views/datacenter/BatchImportModal.vue b/app/src/views/datacenter/BatchImportModal.vue new file mode 100644 index 0000000..8508e06 --- /dev/null +++ b/app/src/views/datacenter/BatchImportModal.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/app/src/views/datacenter/DatacenterTableDetail.vue b/app/src/views/datacenter/DatacenterTableDetail.vue new file mode 100644 index 0000000..c8535ff --- /dev/null +++ b/app/src/views/datacenter/DatacenterTableDetail.vue @@ -0,0 +1,272 @@ + + + + + diff --git a/app/src/views/datacenter/DatacenterTableList.vue b/app/src/views/datacenter/DatacenterTableList.vue new file mode 100644 index 0000000..03890ba --- /dev/null +++ b/app/src/views/datacenter/DatacenterTableList.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/app/src/views/datacenter/DatacenterTableModal.vue b/app/src/views/datacenter/DatacenterTableModal.vue new file mode 100644 index 0000000..5d70d0c --- /dev/null +++ b/app/src/views/datacenter/DatacenterTableModal.vue @@ -0,0 +1,280 @@ + + + + + diff --git a/app/src/views/datacenter/RecordModal.vue b/app/src/views/datacenter/RecordModal.vue new file mode 100644 index 0000000..62e696f --- /dev/null +++ b/app/src/views/datacenter/RecordModal.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/app/src/views/demos/cardTest/index.vue b/app/src/views/demos/cardTest/index.vue new file mode 100644 index 0000000..c68984c --- /dev/null +++ b/app/src/views/demos/cardTest/index.vue @@ -0,0 +1,177 @@ + + + + + + diff --git a/app/src/views/demos/categoryPanel/index.vue b/app/src/views/demos/categoryPanel/index.vue new file mode 100644 index 0000000..a569480 --- /dev/null +++ b/app/src/views/demos/categoryPanel/index.vue @@ -0,0 +1,34 @@ + + + diff --git a/app/src/views/demos/element/index.vue b/app/src/views/demos/element/index.vue new file mode 100644 index 0000000..c255d25 --- /dev/null +++ b/app/src/views/demos/element/index.vue @@ -0,0 +1,117 @@ + + + diff --git a/app/src/views/demos/form/basic.vue b/app/src/views/demos/form/basic.vue new file mode 100644 index 0000000..2a1eb79 --- /dev/null +++ b/app/src/views/demos/form/basic.vue @@ -0,0 +1,191 @@ + + diff --git a/app/src/views/system/sysAccount/SysAccountList.vue b/app/src/views/system/sysAccount/SysAccountList.vue new file mode 100644 index 0000000..bfec70d --- /dev/null +++ b/app/src/views/system/sysAccount/SysAccountList.vue @@ -0,0 +1,214 @@ + + + + + diff --git a/app/src/views/system/sysAccount/SysAccountModal.vue b/app/src/views/system/sysAccount/SysAccountModal.vue new file mode 100644 index 0000000..52994b0 --- /dev/null +++ b/app/src/views/system/sysAccount/SysAccountModal.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/app/src/views/system/sysDept/SysDeptList.vue b/app/src/views/system/sysDept/SysDeptList.vue new file mode 100644 index 0000000..2b9c689 --- /dev/null +++ b/app/src/views/system/sysDept/SysDeptList.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/app/src/views/system/sysDept/SysDeptModal.vue b/app/src/views/system/sysDept/SysDeptModal.vue new file mode 100644 index 0000000..bdd440e --- /dev/null +++ b/app/src/views/system/sysDept/SysDeptModal.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/app/src/views/system/sysFeedback/sysFeedbackDetail.vue b/app/src/views/system/sysFeedback/sysFeedbackDetail.vue new file mode 100644 index 0000000..aa1d269 --- /dev/null +++ b/app/src/views/system/sysFeedback/sysFeedbackDetail.vue @@ -0,0 +1,187 @@ + + + diff --git a/app/src/views/system/sysFeedback/sysFeedbackList.vue b/app/src/views/system/sysFeedback/sysFeedbackList.vue new file mode 100644 index 0000000..4d18866 --- /dev/null +++ b/app/src/views/system/sysFeedback/sysFeedbackList.vue @@ -0,0 +1,241 @@ + + + diff --git a/app/src/views/system/sysJob/SysJobList.vue b/app/src/views/system/sysJob/SysJobList.vue new file mode 100644 index 0000000..3d2272a --- /dev/null +++ b/app/src/views/system/sysJob/SysJobList.vue @@ -0,0 +1,278 @@ + + + + + diff --git a/app/src/views/system/sysJob/SysJobLogList.vue b/app/src/views/system/sysJob/SysJobLogList.vue new file mode 100644 index 0000000..97d710e --- /dev/null +++ b/app/src/views/system/sysJob/SysJobLogList.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/app/src/views/system/sysJob/SysJobModal.vue b/app/src/views/system/sysJob/SysJobModal.vue new file mode 100644 index 0000000..cee98d9 --- /dev/null +++ b/app/src/views/system/sysJob/SysJobModal.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/app/src/views/system/sysLog/SysLogList.vue b/app/src/views/system/sysLog/SysLogList.vue new file mode 100644 index 0000000..e777506 --- /dev/null +++ b/app/src/views/system/sysLog/SysLogList.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/app/src/views/system/sysLog/SysLogModal.vue b/app/src/views/system/sysLog/SysLogModal.vue new file mode 100644 index 0000000..a216b32 --- /dev/null +++ b/app/src/views/system/sysLog/SysLogModal.vue @@ -0,0 +1,143 @@ + + + + + diff --git a/app/src/views/system/sysMenu/SysMenuList.vue b/app/src/views/system/sysMenu/SysMenuList.vue new file mode 100644 index 0000000..610c28d --- /dev/null +++ b/app/src/views/system/sysMenu/SysMenuList.vue @@ -0,0 +1,182 @@ + + + + + diff --git a/app/src/views/system/sysMenu/SysMenuModal.vue b/app/src/views/system/sysMenu/SysMenuModal.vue new file mode 100644 index 0000000..3255aaf --- /dev/null +++ b/app/src/views/system/sysMenu/SysMenuModal.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/app/src/views/system/sysPosition/SysPositionList.vue b/app/src/views/system/sysPosition/SysPositionList.vue new file mode 100644 index 0000000..c4fdcb3 --- /dev/null +++ b/app/src/views/system/sysPosition/SysPositionList.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/app/src/views/system/sysPosition/SysPositionModal.vue b/app/src/views/system/sysPosition/SysPositionModal.vue new file mode 100644 index 0000000..7a8acc6 --- /dev/null +++ b/app/src/views/system/sysPosition/SysPositionModal.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/app/src/views/system/sysRole/SysRoleList.vue b/app/src/views/system/sysRole/SysRoleList.vue new file mode 100644 index 0000000..de1257d --- /dev/null +++ b/app/src/views/system/sysRole/SysRoleList.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/app/src/views/system/sysRole/SysRoleModal.vue b/app/src/views/system/sysRole/SysRoleModal.vue new file mode 100644 index 0000000..b85e69d --- /dev/null +++ b/app/src/views/system/sysRole/SysRoleModal.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/app/tailwind.config.mjs b/app/tailwind.config.mjs new file mode 100644 index 0000000..12c65e9 --- /dev/null +++ b/app/tailwind.config.mjs @@ -0,0 +1 @@ +export { default } from '@aiflowy/tailwind-config'; diff --git a/app/tsconfig.json b/app/tsconfig.json new file mode 100644 index 0000000..0be4b12 --- /dev/null +++ b/app/tsconfig.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@aiflowy/tsconfig/web-app.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "#/*": ["./src/*"] + } + }, + "references": [{ "path": "./tsconfig.node.json" }], + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/app/tsconfig.node.json b/app/tsconfig.node.json new file mode 100644 index 0000000..671db23 --- /dev/null +++ b/app/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@aiflowy/tsconfig/node.json", + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "noEmit": false + }, + "include": ["vite.config.mts"] +} diff --git a/app/vite.config.mts b/app/vite.config.mts new file mode 100644 index 0000000..3c352ca --- /dev/null +++ b/app/vite.config.mts @@ -0,0 +1,27 @@ +import { defineConfig } from '@aiflowy/vite-config'; + +import ElementPlus from 'unplugin-element-plus/vite'; + +export default defineConfig(async () => { + return { + application: {}, + vite: { + plugins: [ + ElementPlus({ + format: 'esm', + }), + ], + server: { + proxy: { + '/api': { + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ''), + // mock代理目标地址 + target: 'http://localhost:5320/api', + ws: true, + }, + }, + }, + }, + }; +}); diff --git a/cspell.json b/cspell.json new file mode 100644 index 0000000..0fcf900 --- /dev/null +++ b/cspell.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", + "version": "0.2", + "language": "en,en-US", + "allowCompoundWords": true, + "words": [ + "acmr", + "antd", + "antdv", + "astro", + "brotli", + "clsx", + "defu", + "demi", + "echarts", + "ependencies", + "esno", + "etag", + "execa", + "iconify", + "iconoir", + "intlify", + "lockb", + "lucide", + "minh", + "minw", + "mkdist", + "mockjs", + "naiveui", + "nocheck", + "noopener", + "noreferrer", + "nprogress", + "nuxt", + "pinia", + "prefixs", + "publint", + "qrcode", + "reka", + "shadcn", + "sonner", + "sortablejs", + "styl", + "taze", + "ui-kit", + "uicons", + "unplugin", + "unref", + "aiflowy", + "aiflowy", + "vite", + "vitejs", + "vitepress", + "vnode", + "vueuse", + "yxxx" + ], + "ignorePaths": [ + "**/node_modules/**", + "**/dist/**", + "**/*-dist/**", + "**/icons/**", + "pnpm-lock.yaml", + "**/*.log", + "**/*.test.ts", + "**/*.spec.ts", + "**/__tests__/**" + ] +} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..1c972b6 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,5 @@ +// @ts-check + +import { defineConfig } from '@aiflowy/eslint-config'; + +export default defineConfig(); diff --git a/internal/lint-configs/commitlint-config/index.mjs b/internal/lint-configs/commitlint-config/index.mjs new file mode 100644 index 0000000..5a74f48 --- /dev/null +++ b/internal/lint-configs/commitlint-config/index.mjs @@ -0,0 +1,153 @@ +import { execSync } from 'node:child_process'; + +import { getPackagesSync } from '@aiflowy/node-utils'; + +const { packages } = getPackagesSync(); + +const allowedScopes = [ + ...packages.map((pkg) => pkg.packageJson.name), + 'project', + 'style', + 'lint', + 'ci', + 'dev', + 'deploy', + 'other', +]; + +// precomputed scope +const scopeComplete = execSync('git status --porcelain || true') + .toString() + .trim() + .split('\n') + .find((r) => ~r.indexOf('M src')) + ?.replace(/(\/)/g, '%%') + ?.match(/src%%((\w|-)*)/)?.[1] + ?.replace(/s$/, ''); + +/** + * @type {import('cz-git').UserConfig} + */ +const userConfig = { + extends: ['@commitlint/config-conventional'], + plugins: ['commitlint-plugin-function-rules'], + prompt: { + /** @use `pnpm commit :f` */ + alias: { + b: 'build: bump dependencies', + c: 'chore: update config', + f: 'docs: fix typos', + r: 'docs: update README', + s: 'style: update code format', + }, + allowCustomIssuePrefixs: false, + // scopes: [...scopes, 'mock'], + allowEmptyIssuePrefixs: false, + customScopesAlign: scopeComplete ? 'bottom' : 'top', + defaultScope: scopeComplete, + // English + typesAppend: [ + { name: 'workflow: workflow improvements', value: 'workflow' }, + { name: 'types: type definition file changes', value: 'types' }, + ], + + // 中英文对照版 + // messages: { + // type: '选择你要提交的类型 :', + // scope: '选择一个提交范围 (可选):', + // customScope: '请输入自定义的提交范围 :', + // subject: '填写简短精炼的变更描述 :\n', + // body: '填写更加详细的变更描述 (可选)。使用 "|" 换行 :\n', + // breaking: '列举非兼容性重大的变更 (可选)。使用 "|" 换行 :\n', + // footerPrefixsSelect: '选择关联issue前缀 (可选):', + // customFooterPrefixs: '输入自定义issue前缀 :', + // footer: '列举关联issue (可选) 例如: #31, #I3244 :\n', + // confirmCommit: '是否提交或修改commit ?', + // }, + // types: [ + // { value: 'feat', name: 'feat: 新增功能' }, + // { value: 'fix', name: 'fix: 修复缺陷' }, + // { value: 'docs', name: 'docs: 文档变更' }, + // { value: 'style', name: 'style: 代码格式' }, + // { value: 'refactor', name: 'refactor: 代码重构' }, + // { value: 'perf', name: 'perf: 性能优化' }, + // { value: 'test', name: 'test: 添加疏漏测试或已有测试改动' }, + // { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' }, + // { value: 'ci', name: 'ci: 修改 CI 配置、脚本' }, + // { value: 'revert', name: 'revert: 回滚 commit' }, + // { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' }, + // { value: 'wip', name: 'wip: 正在开发中' }, + // { value: 'workflow', name: 'workflow: 工作流程改进' }, + // { value: 'types', name: 'types: 类型定义文件修改' }, + // ], + // emptyScopesAlias: 'empty: 不填写', + // customScopesAlias: 'custom: 自定义', + }, + rules: { + /** + * type[scope]: [function] description + * + * ^^^^^^^^^^^^^^ empty line. + * - Something here + */ + 'body-leading-blank': [2, 'always'], + /** + * type[scope]: [function] description + * + * - something here + * + * ^^^^^^^^^^^^^^ + */ + 'footer-leading-blank': [1, 'always'], + /** + * type[scope]: [function] description + * ^^^^^ + */ + 'function-rules/scope-enum': [ + 2, // level: error + 'always', + (parsed) => { + if (!parsed.scope || allowedScopes.includes(parsed.scope)) { + return [true]; + } + + return [false, `scope must be one of ${allowedScopes.join(', ')}`]; + }, + ], + /** + * type[scope]: [function] description [No more than 108 characters] + * ^^^^^ + */ + 'header-max-length': [2, 'always', 108], + + 'scope-enum': [0], + 'subject-case': [0], + 'subject-empty': [2, 'never'], + 'type-empty': [2, 'never'], + /** + * type[scope]: [function] description + * ^^^^ + */ + 'type-enum': [ + 2, + 'always', + [ + 'feat', + 'fix', + 'perf', + 'style', + 'docs', + 'test', + 'refactor', + 'build', + 'ci', + 'chore', + 'revert', + 'types', + 'release', + ], + ], + }, +}; + +export default userConfig; diff --git a/internal/lint-configs/commitlint-config/package.json b/internal/lint-configs/commitlint-config/package.json new file mode 100644 index 0000000..04dce39 --- /dev/null +++ b/internal/lint-configs/commitlint-config/package.json @@ -0,0 +1,33 @@ +{ + "name": "@aiflowy/commitlint-config", + "version": "1.0.0", + "private": true, + "homepage": "https://github.com/aiflowy/aiflowy", + "bugs": "https://github.com/aiflowy/aiflowy/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/aiflowy/aiflowy.git", + "directory": "internal/lint-configs/commitlint-config" + }, + "license": "MIT", + "type": "module", + "files": [ + "dist" + ], + "main": "./index.mjs", + "module": "./index.mjs", + "exports": { + ".": { + "import": "./index.mjs", + "default": "./index.mjs" + } + }, + "dependencies": { + "@commitlint/cli": "catalog:", + "@commitlint/config-conventional": "catalog:", + "@aiflowy/node-utils": "workspace:*", + "commitlint-plugin-function-rules": "catalog:", + "cz-git": "catalog:", + "czg": "catalog:" + } +} diff --git a/internal/lint-configs/eslint-config/build.config.ts b/internal/lint-configs/eslint-config/build.config.ts new file mode 100644 index 0000000..97e572c --- /dev/null +++ b/internal/lint-configs/eslint-config/build.config.ts @@ -0,0 +1,7 @@ +import { defineBuildConfig } from 'unbuild'; + +export default defineBuildConfig({ + clean: true, + declaration: true, + entries: ['src/index'], +}); diff --git a/internal/lint-configs/eslint-config/package.json b/internal/lint-configs/eslint-config/package.json new file mode 100644 index 0000000..14f4850 --- /dev/null +++ b/internal/lint-configs/eslint-config/package.json @@ -0,0 +1,56 @@ +{ + "name": "@aiflowy/eslint-config", + "version": "5.0.0", + "private": true, + "homepage": "https://github.com/aiflowy/aiflowy", + "bugs": "https://github.com/aiflowy/aiflowy/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/aiflowy/aiflowy.git", + "directory": "internal/lint-configs/eslint-config" + }, + "license": "MIT", + "type": "module", + "scripts": { + "stub": "pnpm unbuild --stub" + }, + "files": [ + "dist" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs" + } + }, + "dependencies": { + "eslint-config-turbo": "catalog:", + "eslint-plugin-command": "catalog:", + "eslint-plugin-import-x": "catalog:" + }, + "devDependencies": { + "@eslint/js": "catalog:", + "@types/eslint": "catalog:", + "@typescript-eslint/eslint-plugin": "catalog:", + "@typescript-eslint/parser": "catalog:", + "eslint": "catalog:", + "eslint-plugin-eslint-comments": "catalog:", + "eslint-plugin-jsdoc": "catalog:", + "eslint-plugin-jsonc": "catalog:", + "eslint-plugin-n": "catalog:", + "eslint-plugin-no-only-tests": "catalog:", + "eslint-plugin-perfectionist": "catalog:", + "eslint-plugin-prettier": "catalog:", + "eslint-plugin-regexp": "catalog:", + "eslint-plugin-unicorn": "catalog:", + "eslint-plugin-unused-imports": "catalog:", + "eslint-plugin-vitest": "catalog:", + "eslint-plugin-vue": "catalog:", + "globals": "catalog:", + "jsonc-eslint-parser": "catalog:", + "vue-eslint-parser": "catalog:" + } +} diff --git a/internal/lint-configs/eslint-config/src/configs/command.ts b/internal/lint-configs/eslint-config/src/configs/command.ts new file mode 100644 index 0000000..67651b2 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/command.ts @@ -0,0 +1,10 @@ +import createCommand from 'eslint-plugin-command/config'; + +export async function command() { + return [ + { + // @ts-expect-error - no types + ...createCommand(), + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/comments.ts b/internal/lint-configs/eslint-config/src/configs/comments.ts new file mode 100644 index 0000000..77ccd5d --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/comments.ts @@ -0,0 +1,24 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function comments(): Promise { + const [pluginComments] = await Promise.all([ + // @ts-expect-error - no types + interopDefault(import('eslint-plugin-eslint-comments')), + ] as const); + + return [ + { + plugins: { + 'eslint-comments': pluginComments, + }, + rules: { + 'eslint-comments/no-aggregating-enable': 'error', + 'eslint-comments/no-duplicate-disable': 'error', + 'eslint-comments/no-unlimited-disable': 'error', + 'eslint-comments/no-unused-enable': 'error', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/disableds.ts b/internal/lint-configs/eslint-config/src/configs/disableds.ts new file mode 100644 index 0000000..152b84c --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/disableds.ts @@ -0,0 +1,28 @@ +import type { Linter } from 'eslint'; + +export async function disableds(): Promise { + return [ + { + files: ['**/__tests__/**/*.?([cm])[jt]s?(x)'], + name: 'disables/test', + rules: { + '@typescript-eslint/ban-ts-comment': 'off', + 'no-console': 'off', + }, + }, + { + files: ['**/*.d.ts'], + name: 'disables/dts', + rules: { + '@typescript-eslint/triple-slash-reference': 'off', + }, + }, + { + files: ['**/*.js', '**/*.mjs', '**/*.cjs'], + name: 'disables/js', + rules: { + '@typescript-eslint/explicit-module-boundary-types': 'off', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/ignores.ts b/internal/lint-configs/eslint-config/src/configs/ignores.ts new file mode 100644 index 0000000..136c956 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/ignores.ts @@ -0,0 +1,52 @@ +import type { Linter } from 'eslint'; + +export async function ignores(): Promise { + return [ + { + ignores: [ + '**/node_modules', + '**/dist', + '**/dist-*', + '**/*-dist', + '**/.husky', + '**/.nitro', + '**/.output', + '**/Dockerfile', + '**/package-lock.json', + '**/yarn.lock', + '**/pnpm-lock.yaml', + '**/bun.lockb', + '**/output', + '**/coverage', + '**/temp', + '**/.temp', + '**/tmp', + '**/.tmp', + '**/.history', + '**/.turbo', + '**/.nuxt', + '**/.next', + '**/.vercel', + '**/.changeset', + '**/.idea', + '**/.cache', + '**/.output', + '**/.vite-inspect', + + '**/CHANGELOG*.md', + '**/*.min.*', + '**/LICENSE*', + '**/__snapshots__', + '**/*.snap', + '**/fixtures/**', + '**/.vitepress/cache/**', + '**/auto-import?(s).d.ts', + '**/components.d.ts', + '**/vite.config.mts.*', + '**/*.sh', + '**/*.ttf', + '**/*.woff', + ], + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/import.ts b/internal/lint-configs/eslint-config/src/configs/import.ts new file mode 100644 index 0000000..ce6cf65 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/import.ts @@ -0,0 +1,25 @@ +import type { Linter } from 'eslint'; + +import * as pluginImport from 'eslint-plugin-import-x'; + +export async function importPluginConfig(): Promise { + return [ + { + plugins: { + // @ts-expect-error - This is a dynamic import + import: pluginImport, + }, + rules: { + 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], + 'import/first': 'error', + 'import/newline-after-import': 'error', + 'import/no-duplicates': 'error', + 'import/no-mutable-exports': 'error', + 'import/no-named-default': 'error', + 'import/no-self-import': 'error', + 'import/no-unresolved': 'off', + 'import/no-webpack-loader-syntax': 'error', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/index.ts b/internal/lint-configs/eslint-config/src/configs/index.ts new file mode 100644 index 0000000..c0284ef --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/index.ts @@ -0,0 +1,17 @@ +export * from './command'; +export * from './comments'; +export * from './disableds'; +export * from './ignores'; +export * from './import'; +export * from './javascript'; +export * from './jsdoc'; +export * from './jsonc'; +export * from './node'; +export * from './perfectionist'; +export * from './prettier'; +export * from './regexp'; +export * from './test'; +export * from './turbo'; +export * from './typescript'; +export * from './unicorn'; +export * from './vue'; diff --git a/internal/lint-configs/eslint-config/src/configs/javascript.ts b/internal/lint-configs/eslint-config/src/configs/javascript.ts new file mode 100644 index 0000000..44cf5b6 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/javascript.ts @@ -0,0 +1,241 @@ +import type { Linter } from 'eslint'; + +import js from '@eslint/js'; +import pluginUnusedImports from 'eslint-plugin-unused-imports'; +import globals from 'globals'; + +export async function javascript(): Promise { + return [ + { + languageOptions: { + ecmaVersion: 'latest', + globals: { + ...globals.browser, + ...globals.es2021, + ...globals.node, + document: 'readonly', + navigator: 'readonly', + window: 'readonly', + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 'latest', + sourceType: 'module', + }, + sourceType: 'module', + }, + linterOptions: { + reportUnusedDisableDirectives: true, + }, + plugins: { + 'unused-imports': pluginUnusedImports, + }, + rules: { + ...js.configs.recommended.rules, + 'accessor-pairs': [ + 'error', + { enforceForClassMembers: true, setWithoutGet: true }, + ], + 'array-callback-return': 'error', + 'block-scoped-var': 'error', + 'constructor-super': 'error', + 'default-case-last': 'error', + 'dot-notation': ['error', { allowKeywords: true }], + eqeqeq: ['error', 'always'], + 'keyword-spacing': 'off', + + 'new-cap': [ + 'error', + { capIsNew: false, newIsCap: true, properties: true }, + ], + 'no-alert': 'error', + 'no-array-constructor': 'error', + 'no-async-promise-executor': 'error', + 'no-caller': 'error', + 'no-case-declarations': 'error', + 'no-class-assign': 'error', + 'no-compare-neg-zero': 'error', + 'no-cond-assign': ['error', 'always'], + 'no-console': ['error', { allow: ['warn', 'error'] }], + 'no-const-assign': 'error', + 'no-control-regex': 'error', + 'no-debugger': 'error', + 'no-delete-var': 'error', + 'no-dupe-args': 'error', + 'no-dupe-class-members': 'error', + 'no-dupe-keys': 'error', + 'no-duplicate-case': 'error', + 'no-empty': ['error', { allowEmptyCatch: true }], + 'no-empty-character-class': 'error', + 'no-empty-function': 'off', + 'no-empty-pattern': 'error', + 'no-eval': 'error', + 'no-ex-assign': 'error', + 'no-extend-native': 'error', + 'no-extra-bind': 'error', + 'no-extra-boolean-cast': 'error', + 'no-fallthrough': 'error', + 'no-func-assign': 'error', + 'no-global-assign': 'error', + 'no-implied-eval': 'error', + 'no-import-assign': 'error', + 'no-invalid-regexp': 'error', + 'no-irregular-whitespace': 'error', + 'no-iterator': 'error', + 'no-labels': ['error', { allowLoop: false, allowSwitch: false }], + 'no-lone-blocks': 'error', + 'no-loss-of-precision': 'error', + 'no-misleading-character-class': 'error', + 'no-multi-str': 'error', + 'no-new': 'error', + 'no-new-func': 'error', + 'no-new-object': 'error', + 'no-new-symbol': 'error', + 'no-new-wrappers': 'error', + 'no-obj-calls': 'error', + 'no-octal': 'error', + 'no-octal-escape': 'error', + 'no-proto': 'error', + 'no-prototype-builtins': 'error', + 'no-redeclare': ['error', { builtinGlobals: false }], + 'no-regex-spaces': 'error', + 'no-restricted-globals': [ + 'error', + { message: 'Use `globalThis` instead.', name: 'global' }, + { message: 'Use `globalThis` instead.', name: 'self' }, + ], + 'no-restricted-properties': [ + 'error', + { + message: + 'Use `Object.getPrototypeOf` or `Object.setPrototypeOf` instead.', + property: '__proto__', + }, + { + message: 'Use `Object.defineProperty` instead.', + property: '__defineGetter__', + }, + { + message: 'Use `Object.defineProperty` instead.', + property: '__defineSetter__', + }, + { + message: 'Use `Object.getOwnPropertyDescriptor` instead.', + property: '__lookupGetter__', + }, + { + message: 'Use `Object.getOwnPropertyDescriptor` instead.', + property: '__lookupSetter__', + }, + ], + 'no-restricted-syntax': [ + 'error', + 'DebuggerStatement', + 'LabeledStatement', + 'WithStatement', + 'TSEnumDeclaration[const=true]', + 'TSExportAssignment', + ], + 'no-self-assign': ['error', { props: true }], + 'no-self-compare': 'error', + 'no-sequences': 'error', + 'no-shadow-restricted-names': 'error', + 'no-sparse-arrays': 'error', + 'no-template-curly-in-string': 'error', + 'no-this-before-super': 'error', + 'no-throw-literal': 'error', + 'no-undef': 'off', + 'no-undef-init': 'error', + 'no-unexpected-multiline': 'error', + 'no-unmodified-loop-condition': 'error', + 'no-unneeded-ternary': ['error', { defaultAssignment: false }], + 'no-unreachable': 'error', + 'no-unreachable-loop': 'error', + 'no-unsafe-finally': 'error', + 'no-unsafe-negation': 'error', + 'no-unused-expressions': [ + 'error', + { + allowShortCircuit: true, + allowTaggedTemplates: true, + allowTernary: true, + }, + ], + 'no-unused-vars': [ + 'error', + { + args: 'none', + caughtErrors: 'none', + ignoreRestSiblings: true, + vars: 'all', + }, + ], + 'no-use-before-define': [ + 'error', + { classes: false, functions: false, variables: false }, + ], + 'no-useless-backreference': 'error', + 'no-useless-call': 'error', + 'no-useless-catch': 'error', + 'no-useless-computed-key': 'error', + 'no-useless-constructor': 'error', + 'no-useless-rename': 'error', + 'no-useless-return': 'error', + 'no-var': 'error', + 'no-with': 'error', + 'object-shorthand': [ + 'error', + 'always', + { avoidQuotes: true, ignoreConstructors: false }, + ], + 'one-var': ['error', { initialized: 'never' }], + 'prefer-arrow-callback': [ + 'error', + { + allowNamedFunctions: false, + allowUnboundThis: true, + }, + ], + 'prefer-const': [ + 'error', + { + destructuring: 'all', + ignoreReadBeforeAssign: true, + }, + ], + 'prefer-exponentiation-operator': 'error', + + 'prefer-promise-reject-errors': 'error', + 'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }], + 'prefer-rest-params': 'error', + 'prefer-spread': 'error', + 'prefer-template': 'error', + 'space-before-function-paren': 'off', + 'spaced-comment': 'error', + 'symbol-description': 'error', + 'unicode-bom': ['error', 'never'], + + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': [ + 'error', + { + args: 'after-used', + argsIgnorePattern: '^_', + vars: 'all', + varsIgnorePattern: '^_', + }, + ], + 'use-isnan': [ + 'error', + { enforceForIndexOf: true, enforceForSwitchCase: true }, + ], + 'valid-typeof': ['error', { requireStringLiterals: true }], + + 'vars-on-top': 'error', + yoda: ['error', 'never'], + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/jsdoc.ts b/internal/lint-configs/eslint-config/src/configs/jsdoc.ts new file mode 100644 index 0000000..1368197 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/jsdoc.ts @@ -0,0 +1,34 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function jsdoc(): Promise { + const [pluginJsdoc] = await Promise.all([ + interopDefault(import('eslint-plugin-jsdoc')), + ] as const); + + return [ + { + plugins: { + jsdoc: pluginJsdoc, + }, + rules: { + 'jsdoc/check-access': 'warn', + 'jsdoc/check-param-names': 'warn', + 'jsdoc/check-property-names': 'warn', + 'jsdoc/check-types': 'warn', + 'jsdoc/empty-tags': 'warn', + 'jsdoc/implements-on-classes': 'warn', + 'jsdoc/no-defaults': 'warn', + 'jsdoc/no-multi-asterisks': 'warn', + 'jsdoc/require-param-name': 'warn', + 'jsdoc/require-property': 'warn', + 'jsdoc/require-property-description': 'warn', + 'jsdoc/require-property-name': 'warn', + 'jsdoc/require-returns-check': 'warn', + 'jsdoc/require-returns-description': 'warn', + 'jsdoc/require-yields-check': 'warn', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/jsonc.ts b/internal/lint-configs/eslint-config/src/configs/jsonc.ts new file mode 100644 index 0000000..4072e4c --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/jsonc.ts @@ -0,0 +1,258 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function jsonc(): Promise { + const [pluginJsonc, parserJsonc] = await Promise.all([ + interopDefault(import('eslint-plugin-jsonc')), + interopDefault(import('jsonc-eslint-parser')), + ] as const); + + return [ + { + files: ['**/*.json', '**/*.json5', '**/*.jsonc', '*.code-workspace'], + languageOptions: { + parser: parserJsonc as any, + }, + plugins: { + jsonc: pluginJsonc as any, + }, + rules: { + 'jsonc/no-bigint-literals': 'error', + 'jsonc/no-binary-expression': 'error', + 'jsonc/no-binary-numeric-literals': 'error', + 'jsonc/no-dupe-keys': 'error', + 'jsonc/no-escape-sequence-in-identifier': 'error', + 'jsonc/no-floating-decimal': 'error', + 'jsonc/no-hexadecimal-numeric-literals': 'error', + 'jsonc/no-infinity': 'error', + 'jsonc/no-multi-str': 'error', + 'jsonc/no-nan': 'error', + 'jsonc/no-number-props': 'error', + 'jsonc/no-numeric-separators': 'error', + 'jsonc/no-octal': 'error', + 'jsonc/no-octal-escape': 'error', + 'jsonc/no-octal-numeric-literals': 'error', + 'jsonc/no-parenthesized': 'error', + 'jsonc/no-plus-sign': 'error', + 'jsonc/no-regexp-literals': 'error', + 'jsonc/no-sparse-arrays': 'error', + 'jsonc/no-template-literals': 'error', + 'jsonc/no-undefined-value': 'error', + 'jsonc/no-unicode-codepoint-escapes': 'error', + 'jsonc/no-useless-escape': 'error', + 'jsonc/space-unary-ops': 'error', + 'jsonc/valid-json-number': 'error', + 'jsonc/vue-custom-block/no-parsing-error': 'error', + }, + }, + sortTsconfig(), + sortPackageJson(), + ]; +} + +function sortPackageJson(): Linter.Config { + return { + files: ['**/package.json'], + rules: { + 'jsonc/sort-array-values': [ + 'error', + { + order: { type: 'asc' }, + pathPattern: '^files$|^pnpm.neverBuiltDependencies$', + }, + ], + 'jsonc/sort-keys': [ + 'error', + { + order: [ + 'name', + 'version', + 'description', + 'private', + 'keywords', + 'homepage', + 'bugs', + 'repository', + 'license', + 'author', + 'contributors', + 'categories', + 'funding', + 'type', + 'scripts', + 'files', + 'sideEffects', + 'bin', + 'main', + 'module', + 'unpkg', + 'jsdelivr', + 'types', + 'typesVersions', + 'imports', + 'exports', + 'publishConfig', + 'icon', + 'activationEvents', + 'contributes', + 'peerDependencies', + 'peerDependenciesMeta', + 'dependencies', + 'optionalDependencies', + 'devDependencies', + 'engines', + 'packageManager', + 'pnpm', + 'overrides', + 'resolutions', + 'husky', + 'simple-git-hooks', + 'lint-staged', + 'eslintConfig', + ], + pathPattern: '^$', + }, + { + order: { type: 'asc' }, + pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies(Meta)?$', + }, + { + order: { type: 'asc' }, + pathPattern: '^(?:resolutions|overrides|pnpm.overrides)$', + }, + { + order: ['types', 'import', 'require', 'default'], + pathPattern: '^exports.*$', + }, + ], + }, + }; +} + +function sortTsconfig(): Linter.Config { + return { + files: [ + '**/tsconfig.json', + '**/tsconfig.*.json', + 'internal/tsconfig/*.json', + ], + rules: { + 'jsonc/sort-keys': [ + 'error', + { + order: [ + 'extends', + 'compilerOptions', + 'references', + 'files', + 'include', + 'exclude', + ], + pathPattern: '^$', + }, + { + order: [ + /* Projects */ + 'incremental', + 'composite', + 'tsBuildInfoFile', + 'disableSourceOfProjectReferenceRedirect', + 'disableSolutionSearching', + 'disableReferencedProjectLoad', + /* Language and Environment */ + 'target', + 'jsx', + 'jsxFactory', + 'jsxFragmentFactory', + 'jsxImportSource', + 'lib', + 'moduleDetection', + 'noLib', + 'reactNamespace', + 'useDefineForClassFields', + 'emitDecoratorMetadata', + 'experimentalDecorators', + /* Modules */ + 'baseUrl', + 'rootDir', + 'rootDirs', + 'customConditions', + 'module', + 'moduleResolution', + 'moduleSuffixes', + 'noResolve', + 'paths', + 'resolveJsonModule', + 'resolvePackageJsonExports', + 'resolvePackageJsonImports', + 'typeRoots', + 'types', + 'allowArbitraryExtensions', + 'allowImportingTsExtensions', + 'allowUmdGlobalAccess', + /* JavaScript Support */ + 'allowJs', + 'checkJs', + 'maxNodeModuleJsDepth', + /* Type Checking */ + 'strict', + 'strictBindCallApply', + 'strictFunctionTypes', + 'strictNullChecks', + 'strictPropertyInitialization', + 'allowUnreachableCode', + 'allowUnusedLabels', + 'alwaysStrict', + 'exactOptionalPropertyTypes', + 'noFallthroughCasesInSwitch', + 'noImplicitAny', + 'noImplicitOverride', + 'noImplicitReturns', + 'noImplicitThis', + 'noPropertyAccessFromIndexSignature', + 'noUncheckedIndexedAccess', + 'noUnusedLocals', + 'noUnusedParameters', + 'useUnknownInCatchVariables', + /* Emit */ + 'declaration', + 'declarationDir', + 'declarationMap', + 'downlevelIteration', + 'emitBOM', + 'emitDeclarationOnly', + 'importHelpers', + 'importsNotUsedAsValues', + 'inlineSourceMap', + 'inlineSources', + 'mapRoot', + 'newLine', + 'noEmit', + 'noEmitHelpers', + 'noEmitOnError', + 'outDir', + 'outFile', + 'preserveConstEnums', + 'preserveValueImports', + 'removeComments', + 'sourceMap', + 'sourceRoot', + 'stripInternal', + /* Interop Constraints */ + 'allowSyntheticDefaultImports', + 'esModuleInterop', + 'forceConsistentCasingInFileNames', + 'isolatedModules', + 'preserveSymlinks', + 'verbatimModuleSyntax', + /* Completeness */ + 'skipDefaultLibCheck', + 'skipLibCheck', + ], + pathPattern: '^compilerOptions$', + }, + ], + }, + }; +} diff --git a/internal/lint-configs/eslint-config/src/configs/node.ts b/internal/lint-configs/eslint-config/src/configs/node.ts new file mode 100644 index 0000000..a670d4a --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/node.ts @@ -0,0 +1,57 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function node(): Promise { + const pluginNode = await interopDefault(import('eslint-plugin-n')); + + return [ + { + plugins: { + n: pluginNode, + }, + rules: { + 'n/handle-callback-err': ['error', '^(err|error)$'], + 'n/no-deprecated-api': 'error', + 'n/no-exports-assign': 'error', + 'n/no-extraneous-import': [ + 'error', + { + allowModules: [ + 'unbuild', + '@aiflowy/vite-config', + 'vitest', + 'vite', + '@vue/test-utils', + '@aiflowy/tailwind-config', + '@playwright/test', + ], + }, + ], + 'n/no-new-require': 'error', + 'n/no-path-concat': 'error', + // 'n/no-unpublished-import': 'off', + 'n/no-unsupported-features/es-syntax': [ + 'error', + { + ignores: [], + version: '>=18.0.0', + }, + ], + 'n/prefer-global/buffer': ['error', 'never'], + // 'n/no-missing-import': 'off', + 'n/prefer-global/process': ['error', 'never'], + 'n/process-exit-as-throw': 'error', + }, + }, + { + files: [ + 'scripts/**/*.?([cm])[jt]s?(x)', + 'internal/**/*.?([cm])[jt]s?(x)', + ], + rules: { + 'n/prefer-global/process': 'off', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/perfectionist.ts b/internal/lint-configs/eslint-config/src/configs/perfectionist.ts new file mode 100644 index 0000000..9e9ac5a --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/perfectionist.ts @@ -0,0 +1,89 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function perfectionist(): Promise { + const perfectionistPlugin = await interopDefault( + // @ts-expect-error - no types + import('eslint-plugin-perfectionist'), + ); + + return [ + perfectionistPlugin.configs['recommended-natural'], + { + rules: { + 'perfectionist/sort-exports': [ + 'error', + { + order: 'asc', + type: 'natural', + }, + ], + 'perfectionist/sort-imports': [ + 'error', + { + customGroups: { + type: { + 'aiflowy-core-type': ['^@aiflowy-core/.+'], + 'aiflowy-type': ['^@aiflowy/.+'], + 'vue-type': ['^vue$', '^vue-.+', '^@vue/.+'], + }, + value: { + aiflowy: ['^@aiflowy/.+'], + 'aiflowy-core': ['^@aiflowy-core/.+'], + vue: ['^vue$', '^vue-.+', '^@vue/.+'], + }, + }, + environment: 'node', + groups: [ + ['external-type', 'builtin-type', 'type'], + 'vue-type', + 'aiflowy-type', + 'aiflowy-core-type', + ['parent-type', 'sibling-type', 'index-type'], + ['internal-type'], + 'builtin', + 'vue', + 'aiflowy', + 'aiflowy-core', + 'external', + 'internal', + ['parent', 'sibling', 'index'], + 'side-effect', + 'side-effect-style', + 'style', + 'object', + 'unknown', + ], + internalPattern: ['^#/.+'], + newlinesBetween: 'always', + order: 'asc', + type: 'natural', + }, + ], + 'perfectionist/sort-modules': 'off', + 'perfectionist/sort-named-exports': [ + 'error', + { + order: 'asc', + type: 'natural', + }, + ], + 'perfectionist/sort-objects': [ + 'off', + { + customGroups: { + items: 'items', + list: 'list', + children: 'children', + }, + groups: ['unknown', 'items', 'list', 'children'], + ignorePattern: ['children'], + order: 'asc', + type: 'natural', + }, + ], + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/prettier.ts b/internal/lint-configs/eslint-config/src/configs/prettier.ts new file mode 100644 index 0000000..3cd7af4 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/prettier.ts @@ -0,0 +1,19 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function prettier(): Promise { + const [pluginPrettier] = await Promise.all([ + interopDefault(import('eslint-plugin-prettier')), + ] as const); + return [ + { + plugins: { + prettier: pluginPrettier, + }, + rules: { + 'prettier/prettier': 'error', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/regexp.ts b/internal/lint-configs/eslint-config/src/configs/regexp.ts new file mode 100644 index 0000000..c0f4c9f --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/regexp.ts @@ -0,0 +1,20 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function regexp(): Promise { + const [pluginRegexp] = await Promise.all([ + interopDefault(import('eslint-plugin-regexp')), + ] as const); + + return [ + { + plugins: { + regexp: pluginRegexp, + }, + rules: { + ...pluginRegexp.configs.recommended.rules, + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/test.ts b/internal/lint-configs/eslint-config/src/configs/test.ts new file mode 100644 index 0000000..ddfde2b --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/test.ts @@ -0,0 +1,45 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function test(): Promise { + const [pluginTest, pluginNoOnlyTests] = await Promise.all([ + interopDefault(import('eslint-plugin-vitest')), + // @ts-expect-error - no types + interopDefault(import('eslint-plugin-no-only-tests')), + ] as const); + + return [ + { + files: [ + `**/__tests__/**/*.?([cm])[jt]s?(x)`, + `**/*.spec.?([cm])[jt]s?(x)`, + `**/*.test.?([cm])[jt]s?(x)`, + `**/*.bench.?([cm])[jt]s?(x)`, + `**/*.benchmark.?([cm])[jt]s?(x)`, + ], + plugins: { + test: { + ...pluginTest, + rules: { + ...pluginTest.rules, + ...pluginNoOnlyTests.rules, + }, + }, + }, + rules: { + 'no-console': 'off', + 'node/prefer-global/process': 'off', + 'test/consistent-test-it': [ + 'error', + { fn: 'it', withinDescribe: 'it' }, + ], + 'test/no-identical-title': 'error', + 'test/no-import-node-test': 'error', + 'test/no-only-tests': 'error', + 'test/prefer-hooks-in-order': 'error', + 'test/prefer-lowercase-title': 'error', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/turbo.ts b/internal/lint-configs/eslint-config/src/configs/turbo.ts new file mode 100644 index 0000000..9f6bf75 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/turbo.ts @@ -0,0 +1,18 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function turbo(): Promise { + const [pluginTurbo] = await Promise.all([ + // @ts-expect-error - no types + interopDefault(import('eslint-config-turbo')), + ] as const); + + return [ + { + plugins: { + turbo: pluginTurbo, + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/typescript.ts b/internal/lint-configs/eslint-config/src/configs/typescript.ts new file mode 100644 index 0000000..cff9aa4 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/typescript.ts @@ -0,0 +1,72 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function typescript(): Promise { + const [pluginTs, parserTs] = await Promise.all([ + interopDefault(import('@typescript-eslint/eslint-plugin')), + // @ts-expect-error missing types + interopDefault(import('@typescript-eslint/parser')), + ] as const); + + return [ + { + files: ['**/*.?([cm])[jt]s?(x)'], + languageOptions: { + parser: parserTs, + parserOptions: { + createDefaultProgram: false, + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 'latest', + extraFileExtensions: ['.vue'], + jsxPragma: 'React', + project: './tsconfig.*.json', + sourceType: 'module', + }, + }, + plugins: { + '@typescript-eslint': pluginTs, + }, + rules: { + ...pluginTs.configs['eslint-recommended'].overrides?.[0].rules, + ...pluginTs.configs.strict.rules, + '@typescript-eslint/ban-ts-comment': [ + 'error', + { + 'ts-check': false, + 'ts-expect-error': 'allow-with-description', + 'ts-ignore': 'allow-with-description', + 'ts-nocheck': 'allow-with-description', + }, + ], + + // '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'], + '@typescript-eslint/consistent-type-definitions': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-empty-function': [ + 'error', + { + allow: ['arrowFunctions', 'functions', 'methods'], + }, + ], + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-non-null-assertion': 'error', + '@typescript-eslint/no-unused-expressions': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + }, + ], + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/no-var-requires': 'error', + 'unused-imports/no-unused-vars': 'off', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/unicorn.ts b/internal/lint-configs/eslint-config/src/configs/unicorn.ts new file mode 100644 index 0000000..21b1902 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/unicorn.ts @@ -0,0 +1,45 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function unicorn(): Promise { + const [pluginUnicorn] = await Promise.all([ + interopDefault(import('eslint-plugin-unicorn')), + ] as const); + + return [ + { + plugins: { + unicorn: pluginUnicorn, + }, + rules: { + ...pluginUnicorn.configs.recommended.rules, + + 'unicorn/better-regex': 'off', + 'unicorn/consistent-destructuring': 'off', + 'unicorn/consistent-function-scoping': 'off', + 'unicorn/expiring-todo-comments': 'off', + 'unicorn/filename-case': 'off', + 'unicorn/import-style': 'off', + 'unicorn/no-array-for-each': 'off', + 'unicorn/no-null': 'off', + 'unicorn/no-useless-undefined': 'off', + 'unicorn/prefer-at': 'off', + 'unicorn/prefer-dom-node-text-content': 'off', + 'unicorn/prefer-export-from': ['error', { ignoreUsedVariables: true }], + 'unicorn/prefer-global-this': 'off', + 'unicorn/prefer-top-level-await': 'off', + 'unicorn/prevent-abbreviations': 'off', + }, + }, + { + files: [ + 'scripts/**/*.?([cm])[jt]s?(x)', + 'internal/**/*.?([cm])[jt]s?(x)', + ], + rules: { + 'unicorn/no-process-exit': 'off', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/vue.ts b/internal/lint-configs/eslint-config/src/configs/vue.ts new file mode 100644 index 0000000..a64c55a --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/vue.ts @@ -0,0 +1,153 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function vue(): Promise { + const [pluginVue, parserVue, parserTs] = await Promise.all([ + interopDefault(import('eslint-plugin-vue')), + interopDefault(import('vue-eslint-parser')), + // @ts-expect-error missing types + interopDefault(import('@typescript-eslint/parser')), + ] as const); + + const flatEssential = pluginVue.configs?.['flat/essential'] || []; + const flatStronglyRecommended = + pluginVue.configs?.['flat/strongly-recommended'] || []; + const flatRecommended = pluginVue.configs?.['flat/recommended'] || []; + + return [ + ...flatEssential, + ...flatStronglyRecommended, + ...flatRecommended, + { + files: ['**/*.vue'], + languageOptions: { + // globals: { + // computed: 'readonly', + // defineEmits: 'readonly', + // defineExpose: 'readonly', + // defineProps: 'readonly', + // onMounted: 'readonly', + // onUnmounted: 'readonly', + // reactive: 'readonly', + // ref: 'readonly', + // shallowReactive: 'readonly', + // shallowRef: 'readonly', + // toRef: 'readonly', + // toRefs: 'readonly', + // watch: 'readonly', + // watchEffect: 'readonly', + // }, + parser: parserVue, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + extraFileExtensions: ['.vue'], + parser: parserTs, + sourceType: 'module', + }, + }, + plugins: { + vue: pluginVue, + }, + processor: pluginVue.processors?.['.vue'], + rules: { + ...pluginVue.configs?.base?.rules, + + 'vue/attribute-hyphenation': [ + 'error', + 'always', + { + ignore: [], + }, + ], + 'vue/attributes-order': 'off', + 'vue/block-order': [ + 'error', + { + order: ['script', 'template', 'style'], + }, + ], + 'vue/component-name-in-template-casing': ['error', 'PascalCase'], + 'vue/component-options-name-casing': ['error', 'PascalCase'], + 'vue/custom-event-name-casing': ['error', 'camelCase'], + 'vue/define-macros-order': [ + 'error', + { + order: [ + 'defineOptions', + 'defineProps', + 'defineEmits', + 'defineSlots', + ], + }, + ], + 'vue/dot-location': ['error', 'property'], + 'vue/dot-notation': ['error', { allowKeywords: true }], + 'vue/eqeqeq': ['error', 'smart'], + 'vue/html-closing-bracket-newline': 'error', + 'vue/html-indent': 'off', + // 'vue/html-indent': ['error', 2], + 'vue/html-quotes': ['error', 'double'], + 'vue/html-self-closing': [ + 'error', + { + html: { + component: 'always', + normal: 'never', + void: 'always', + }, + math: 'always', + svg: 'always', + }, + ], + 'vue/max-attributes-per-line': 'off', + 'vue/multi-word-component-names': 'off', + 'vue/multiline-html-element-content-newline': 'error', + 'vue/no-empty-pattern': 'error', + 'vue/no-extra-parens': ['error', 'functions'], + 'vue/no-irregular-whitespace': 'error', + 'vue/no-loss-of-precision': 'error', + 'vue/no-reserved-component-names': 'off', + 'vue/no-restricted-syntax': [ + 'error', + 'DebuggerStatement', + 'LabeledStatement', + 'WithStatement', + ], + 'vue/no-restricted-v-bind': ['error', '/^v-/'], + 'vue/no-sparse-arrays': 'error', + 'vue/no-unused-refs': 'error', + 'vue/no-useless-v-bind': 'error', + 'vue/object-shorthand': [ + 'error', + 'always', + { + avoidQuotes: true, + ignoreConstructors: false, + }, + ], + 'vue/one-component-per-file': 'error', + 'vue/prefer-import-from-vue': 'error', + 'vue/prefer-separate-static-class': 'error', + 'vue/prefer-template': 'error', + 'vue/prop-name-casing': ['error', 'camelCase'], + 'vue/require-default-prop': 'error', + 'vue/require-explicit-emits': 'error', + 'vue/require-prop-types': 'off', + 'vue/singleline-html-element-content-newline': 'off', + 'vue/space-infix-ops': 'error', + 'vue/space-unary-ops': ['error', { nonwords: false, words: true }], + 'vue/v-on-event-hyphenation': [ + 'error', + 'always', + { + autofix: true, + ignore: [], + }, + ], + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/custom-config.ts b/internal/lint-configs/eslint-config/src/custom-config.ts new file mode 100644 index 0000000..c5fb58b --- /dev/null +++ b/internal/lint-configs/eslint-config/src/custom-config.ts @@ -0,0 +1,156 @@ +import type { Linter } from 'eslint'; + +const restrictedImportIgnores = [ + '**/vite.config.mts', + '**/tailwind.config.mjs', + '**/postcss.config.mjs', +]; + +const customConfig: Linter.Config[] = [ + // shadcn-ui 内部组件是自动生成的,不做太多限制 + { + files: ['packages/@core/ui-kit/shadcn-ui/**/**'], + rules: { + 'vue/require-default-prop': 'off', + }, + }, + { + files: [ + 'app/**', + 'packages/effects/**/**', + 'packages/utils/**/**', + 'packages/types/**/**', + 'packages/locales/**/**', + ], + ignores: restrictedImportIgnores, + rules: { + 'perfectionist/sort-interfaces': 'off', + 'perfectionist/sort-objects': 'off', + }, + }, + { + files: ['**/**.vue'], + ignores: restrictedImportIgnores, + rules: { + 'perfectionist/sort-objects': 'off', + }, + }, + { + // apps内部的一些基础规则 + files: ['app/**'], + ignores: restrictedImportIgnores, + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['#/api/*'], + message: + 'The #/api package cannot be imported, please use the @core package itself', + }, + { + group: ['#/layouts/*'], + message: + 'The #/layouts package cannot be imported, please use the @core package itself', + }, + { + group: ['#/locales/*'], + message: + 'The #/locales package cannot be imported, please use the @core package itself', + }, + { + group: ['#/stores/*'], + message: + 'The #/stores package cannot be imported, please use the @core package itself', + }, + ], + }, + ], + 'perfectionist/sort-interfaces': 'off', + }, + }, + { + // @core内部组件,不能引入@aiflowy/* 里面的包 + files: ['packages/@core/**/**'], + ignores: restrictedImportIgnores, + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['@aiflowy/*'], + message: + 'The @core package cannot import the @aiflowy package, please use the @core package itself', + }, + ], + }, + ], + }, + }, + { + // @core/shared内部组件,不能引入@aiflowy/* 或者 @aiflowy-core/* 里面的包 + files: ['packages/@core/base/**/**'], + ignores: restrictedImportIgnores, + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['@aiflowy/*', '@aiflowy-core/*'], + message: + 'The @aiflowy-core/shared package cannot import the @aiflowy package, please use the @core/shared package itself', + }, + ], + }, + ], + }, + }, + + { + // 不能引入@aiflowy/*里面的包 + files: [ + 'packages/types/**/**', + 'packages/utils/**/**', + 'packages/icons/**/**', + 'packages/constants/**/**', + 'packages/styles/**/**', + 'packages/stores/**/**', + 'packages/preferences/**/**', + 'packages/locales/**/**', + ], + ignores: restrictedImportIgnores, + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['@aiflowy/*'], + message: + 'The @aiflowy package cannot be imported, please use the @core package itself', + }, + ], + }, + ], + }, + }, + { + files: ['**/**/playwright.config.ts'], + rules: { + 'n/prefer-global/buffer': 'off', + 'n/prefer-global/process': 'off', + 'no-console': 'off', + }, + }, + { + files: ['internal/**/**', 'scripts/**/**'], + rules: { + 'no-console': 'off', + }, + }, +]; + +export { customConfig }; diff --git a/internal/lint-configs/eslint-config/src/index.ts b/internal/lint-configs/eslint-config/src/index.ts new file mode 100644 index 0000000..c9f08bd --- /dev/null +++ b/internal/lint-configs/eslint-config/src/index.ts @@ -0,0 +1,60 @@ +import type { Linter } from 'eslint'; + +import { + command, + comments, + disableds, + ignores, + importPluginConfig, + javascript, + jsdoc, + jsonc, + node, + perfectionist, + prettier, + regexp, + test, + turbo, + typescript, + unicorn, + vue, +} from './configs'; +import { customConfig } from './custom-config'; + +type FlatConfig = Linter.Config; + +type FlatConfigPromise = + | FlatConfig + | FlatConfig[] + | Promise + | Promise; + +async function defineConfig(config: FlatConfig[] = []) { + const configs: FlatConfigPromise[] = [ + vue(), + javascript(), + ignores(), + prettier(), + typescript(), + jsonc(), + disableds(), + importPluginConfig(), + node(), + perfectionist(), + comments(), + jsdoc(), + unicorn(), + test(), + regexp(), + command(), + turbo(), + ...customConfig, + ...config, + ]; + + const resolved = await Promise.all(configs); + + return resolved.flat(); +} + +export { defineConfig }; diff --git a/internal/lint-configs/eslint-config/src/util.ts b/internal/lint-configs/eslint-config/src/util.ts new file mode 100644 index 0000000..d1a10ad --- /dev/null +++ b/internal/lint-configs/eslint-config/src/util.ts @@ -0,0 +1,8 @@ +export type Awaitable = Promise | T; + +export async function interopDefault( + m: Awaitable, +): Promise { + const resolved = await m; + return (resolved as any).default || resolved; +} diff --git a/internal/lint-configs/eslint-config/tsconfig.json b/internal/lint-configs/eslint-config/tsconfig.json new file mode 100644 index 0000000..c12c7ab --- /dev/null +++ b/internal/lint-configs/eslint-config/tsconfig.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@aiflowy/tsconfig/node.json", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/internal/lint-configs/prettier-config/index.mjs b/internal/lint-configs/prettier-config/index.mjs new file mode 100644 index 0000000..f6a20c8 --- /dev/null +++ b/internal/lint-configs/prettier-config/index.mjs @@ -0,0 +1,18 @@ +export default { + endOfLine: 'auto', + overrides: [ + { + files: ['*.json5'], + options: { + quoteProps: 'preserve', + singleQuote: false, + }, + }, + ], + plugins: ['prettier-plugin-tailwindcss'], + printWidth: 80, + proseWrap: 'never', + semi: true, + singleQuote: true, + trailingComma: 'all', +}; diff --git a/internal/lint-configs/prettier-config/package.json b/internal/lint-configs/prettier-config/package.json new file mode 100644 index 0000000..1ec2d67 --- /dev/null +++ b/internal/lint-configs/prettier-config/package.json @@ -0,0 +1,28 @@ +{ + "name": "@aiflowy/prettier-config", + "version": "5.0.0", + "private": true, + "homepage": "https://github.com/aiflowy/aiflowy", + "bugs": "https://github.com/aiflowy/aiflowy/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/aiflowy/aiflowy.git", + "directory": "internal/lint-configs/prettier-config" + }, + "license": "MIT", + "type": "module", + "files": [ + "dist" + ], + "main": "./index.mjs", + "module": "./index.mjs", + "exports": { + ".": { + "default": "./index.mjs" + } + }, + "dependencies": { + "prettier": "catalog:", + "prettier-plugin-tailwindcss": "catalog:" + } +} diff --git a/internal/lint-configs/stylelint-config/index.mjs b/internal/lint-configs/stylelint-config/index.mjs new file mode 100644 index 0000000..08ac823 --- /dev/null +++ b/internal/lint-configs/stylelint-config/index.mjs @@ -0,0 +1,141 @@ +export default { + extends: ['stylelint-config-standard', 'stylelint-config-recess-order'], + ignoreFiles: [ + '**/*.js', + '**/*.jsx', + '**/*.tsx', + '**/*.ts', + '**/*.json', + '**/*.md', + ], + overrides: [ + { + customSyntax: 'postcss-html', + files: ['*.(html|vue)', '**/*.(html|vue)'], + rules: { + 'selector-pseudo-class-no-unknown': [ + true, + { + ignorePseudoClasses: ['global', 'deep'], + }, + ], + 'selector-pseudo-element-no-unknown': [ + true, + { + ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'], + }, + ], + }, + }, + { + customSyntax: 'postcss-scss', + extends: [ + 'stylelint-config-recommended-scss', + 'stylelint-config-recommended-vue/scss', + ], + files: ['*.scss', '**/*.scss'], + }, + ], + plugins: [ + 'stylelint-order', + '@stylistic/stylelint-plugin', + 'stylelint-prettier', + 'stylelint-scss', + ], + rules: { + 'at-rule-no-deprecated': null, + 'at-rule-no-unknown': [ + true, + { + ignoreAtRules: [ + 'extends', + 'ignores', + 'include', + 'mixin', + 'if', + 'else', + 'media', + 'for', + 'at-root', + 'tailwind', + 'apply', + 'variants', + 'responsive', + 'screen', + 'function', + 'each', + 'use', + 'forward', + 'return', + ], + }, + ], + 'font-family-no-missing-generic-family-keyword': null, + 'function-no-unknown': null, + 'import-notation': null, + 'media-feature-range-notation': null, + 'named-grid-areas-no-invalid': null, + 'no-descending-specificity': null, + 'no-empty-source': null, + 'order/order': [ + [ + 'dollar-variables', + 'custom-properties', + 'at-rules', + 'declarations', + { + name: 'supports', + type: 'at-rule', + }, + { + name: 'media', + type: 'at-rule', + }, + { + name: 'include', + type: 'at-rule', + }, + 'rules', + ], + { severity: 'error' }, + ], + 'prettier/prettier': true, + 'rule-empty-line-before': [ + 'always', + { + ignore: ['after-comment', 'first-nested'], + }, + ], + 'scss/at-rule-no-unknown': [ + true, + { + ignoreAtRules: [ + 'extends', + 'ignores', + 'include', + 'mixin', + 'if', + 'else', + 'media', + 'for', + 'at-root', + 'tailwind', + 'apply', + 'variants', + 'responsive', + 'screen', + 'function', + 'each', + 'use', + 'forward', + 'return', + ], + }, + ], + 'scss/operator-no-newline-after': null, + 'selector-class-pattern': + '^(?:(?:o|c|u|t|s|is|has|_|js|qa)-)?[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*(?:__[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:--[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:[.+])?$', + + 'selector-not-notation': null, + }, +}; diff --git a/internal/lint-configs/stylelint-config/package.json b/internal/lint-configs/stylelint-config/package.json new file mode 100644 index 0000000..eee0f64 --- /dev/null +++ b/internal/lint-configs/stylelint-config/package.json @@ -0,0 +1,43 @@ +{ + "name": "@aiflowy/stylelint-config", + "version": "1.0.0", + "private": true, + "homepage": "https://github.com/aiflowy/aiflowy", + "bugs": "https://github.com/aiflowy/aiflowy/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/aiflowy/aiflowy.git", + "directory": "internal/lint-configs/stylelint-config" + }, + "license": "MIT", + "type": "module", + "files": [ + "dist" + ], + "main": "./index.mjs", + "module": "./index.mjs", + "exports": { + ".": { + "import": "./index.mjs", + "default": "./index.mjs" + } + }, + "dependencies": { + "@stylistic/stylelint-plugin": "catalog:", + "stylelint-config-recess-order": "catalog:", + "stylelint-scss": "catalog:" + }, + "devDependencies": { + "postcss": "catalog:", + "postcss-html": "catalog:", + "postcss-scss": "catalog:", + "prettier": "catalog:", + "stylelint": "catalog:", + "stylelint-config-recommended": "catalog:", + "stylelint-config-recommended-scss": "catalog:", + "stylelint-config-recommended-vue": "catalog:", + "stylelint-config-standard": "catalog:", + "stylelint-order": "catalog:", + "stylelint-prettier": "catalog:" + } +} diff --git a/internal/node-utils/build.config.ts b/internal/node-utils/build.config.ts new file mode 100644 index 0000000..97e572c --- /dev/null +++ b/internal/node-utils/build.config.ts @@ -0,0 +1,7 @@ +import { defineBuildConfig } from 'unbuild'; + +export default defineBuildConfig({ + clean: true, + declaration: true, + entries: ['src/index'], +}); diff --git a/internal/node-utils/package.json b/internal/node-utils/package.json new file mode 100644 index 0000000..303bd01 --- /dev/null +++ b/internal/node-utils/package.json @@ -0,0 +1,43 @@ +{ + "name": "@aiflowy/node-utils", + "version": "1.0.0", + "private": true, + "homepage": "https://github.com/aiflowy/aiflowy", + "bugs": "https://github.com/aiflowy/aiflowy/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/aiflowy/aiflowy.git", + "directory": "internal/node-utils" + }, + "license": "MIT", + "type": "module", + "scripts": { + "stub": "pnpm unbuild --stub" + }, + "files": [ + "dist" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./dist/index.mjs", + "default": "./dist/index.mjs" + } + }, + "dependencies": { + "@changesets/git": "catalog:", + "@manypkg/get-packages": "catalog:", + "chalk": "catalog:", + "consola": "catalog:", + "dayjs": "catalog:", + "execa": "catalog:", + "find-up": "catalog:", + "ora": "catalog:", + "pkg-types": "catalog:", + "prettier": "catalog:", + "rimraf": "catalog:" + } +} diff --git a/internal/node-utils/src/__tests__/hash.test.ts b/internal/node-utils/src/__tests__/hash.test.ts new file mode 100644 index 0000000..3851306 --- /dev/null +++ b/internal/node-utils/src/__tests__/hash.test.ts @@ -0,0 +1,52 @@ +import { createHash } from 'node:crypto'; + +import { describe, expect, it } from 'vitest'; + +import { generatorContentHash } from '../hash'; + +describe('generatorContentHash', () => { + it('should generate an MD5 hash for the content', () => { + const content = 'example content'; + const expectedHash = createHash('md5') + .update(content, 'utf8') + .digest('hex'); + const actualHash = generatorContentHash(content); + expect(actualHash).toBe(expectedHash); + }); + + it('should generate an MD5 hash with specified length', () => { + const content = 'example content'; + const hashLength = 10; + const generatedHash = generatorContentHash(content, hashLength); + expect(generatedHash).toHaveLength(hashLength); + }); + + it('should correctly generate the hash with specified length', () => { + const content = 'example content'; + const hashLength = 8; + const expectedHash = createHash('md5') + .update(content, 'utf8') + .digest('hex') + .slice(0, hashLength); + const generatedHash = generatorContentHash(content, hashLength); + expect(generatedHash).toBe(expectedHash); + }); + + it('should return full hash if hash length parameter is not provided', () => { + const content = 'example content'; + const expectedHash = createHash('md5') + .update(content, 'utf8') + .digest('hex'); + const actualHash = generatorContentHash(content); + expect(actualHash).toBe(expectedHash); + }); + + it('should handle empty content', () => { + const content = ''; + const expectedHash = createHash('md5') + .update(content, 'utf8') + .digest('hex'); + const actualHash = generatorContentHash(content); + expect(actualHash).toBe(expectedHash); + }); +}); diff --git a/internal/node-utils/src/__tests__/path.test.ts b/internal/node-utils/src/__tests__/path.test.ts new file mode 100644 index 0000000..3bab5a1 --- /dev/null +++ b/internal/node-utils/src/__tests__/path.test.ts @@ -0,0 +1,67 @@ +// pathUtils.test.ts + +import { describe, expect, it } from 'vitest'; + +import { toPosixPath } from '../path'; + +describe('toPosixPath', () => { + // 测试 Windows 风格路径到 POSIX 风格路径的转换 + it('converts Windows-style paths to POSIX paths', () => { + const windowsPath = String.raw`C:\Users\Example\file.txt`; + const expectedPosixPath = 'C:/Users/Example/file.txt'; + expect(toPosixPath(windowsPath)).toBe(expectedPosixPath); + }); + + // 确认 POSIX 风格路径不会被改变 + it('leaves POSIX-style paths unchanged', () => { + const posixPath = '/home/user/file.txt'; + expect(toPosixPath(posixPath)).toBe(posixPath); + }); + + // 测试带有多个分隔符的路径 + it('converts paths with mixed separators', () => { + const mixedPath = String.raw`C:/Users\Example\file.txt`; + const expectedPosixPath = 'C:/Users/Example/file.txt'; + expect(toPosixPath(mixedPath)).toBe(expectedPosixPath); + }); + + // 测试空字符串 + it('handles empty strings', () => { + const emptyPath = ''; + expect(toPosixPath(emptyPath)).toBe(''); + }); + + // 测试仅包含分隔符的路径 + it('handles path with only separators', () => { + const separatorsPath = '\\\\\\'; + const expectedPosixPath = '///'; + expect(toPosixPath(separatorsPath)).toBe(expectedPosixPath); + }); + + // 测试不包含任何分隔符的路径 + it('handles path without separators', () => { + const noSeparatorPath = 'file.txt'; + expect(toPosixPath(noSeparatorPath)).toBe('file.txt'); + }); + + // 测试以分隔符结尾的路径 + it('handles path ending with a separator', () => { + const endingSeparatorPath = 'C:\\Users\\Example\\'; + const expectedPosixPath = 'C:/Users/Example/'; + expect(toPosixPath(endingSeparatorPath)).toBe(expectedPosixPath); + }); + + // 测试以分隔符开头的路径 + it('handles path starting with a separator', () => { + const startingSeparatorPath = String.raw`\Users\Example`; + const expectedPosixPath = '/Users/Example'; + expect(toPosixPath(startingSeparatorPath)).toBe(expectedPosixPath); + }); + + // 测试包含非法字符的路径 + it('handles path with invalid characters', () => { + const invalidCharsPath = String.raw`C:\Us*?ers\Ex|file.txt`; + const expectedPosixPath = 'C:/Us*?ers/Ex|file.txt'; + expect(toPosixPath(invalidCharsPath)).toBe(expectedPosixPath); + }); +}); diff --git a/internal/node-utils/src/constants.ts b/internal/node-utils/src/constants.ts new file mode 100644 index 0000000..71d8a6c --- /dev/null +++ b/internal/node-utils/src/constants.ts @@ -0,0 +1,6 @@ +enum UNICODE { + FAILURE = '\u2716', // ✖ + SUCCESS = '\u2714', // ✔ +} + +export { UNICODE }; diff --git a/internal/node-utils/src/date.ts b/internal/node-utils/src/date.ts new file mode 100644 index 0000000..d36572d --- /dev/null +++ b/internal/node-utils/src/date.ts @@ -0,0 +1,12 @@ +import dayjs from 'dayjs'; +import timezone from 'dayjs/plugin/timezone'; +import utc from 'dayjs/plugin/utc'; + +dayjs.extend(utc); +dayjs.extend(timezone); + +dayjs.tz.setDefault('Asia/Shanghai'); + +const dateUtil = dayjs; + +export { dateUtil }; diff --git a/internal/node-utils/src/fs.ts b/internal/node-utils/src/fs.ts new file mode 100644 index 0000000..8eec357 --- /dev/null +++ b/internal/node-utils/src/fs.ts @@ -0,0 +1,39 @@ +import { promises as fs } from 'node:fs'; +import { dirname } from 'node:path'; + +export async function outputJSON( + filePath: string, + data: any, + spaces: number = 2, +) { + try { + const dir = dirname(filePath); + await fs.mkdir(dir, { recursive: true }); + const jsonData = JSON.stringify(data, null, spaces); + await fs.writeFile(filePath, jsonData, 'utf8'); + } catch (error) { + console.error('Error writing JSON file:', error); + throw error; + } +} + +export async function ensureFile(filePath: string) { + try { + const dir = dirname(filePath); + await fs.mkdir(dir, { recursive: true }); + await fs.writeFile(filePath, '', { flag: 'a' }); + } catch (error) { + console.error('Error ensuring file:', error); + throw error; + } +} + +export async function readJSON(filePath: string) { + try { + const data = await fs.readFile(filePath, 'utf8'); + return JSON.parse(data); + } catch (error) { + console.error('Error reading JSON file:', error); + throw error; + } +} diff --git a/internal/node-utils/src/git.ts b/internal/node-utils/src/git.ts new file mode 100644 index 0000000..88f159c --- /dev/null +++ b/internal/node-utils/src/git.ts @@ -0,0 +1,34 @@ +import path from 'node:path'; + +import { execa } from 'execa'; + +export * from '@changesets/git'; + +/** + * 获取暂存区文件 + */ +async function getStagedFiles(): Promise { + try { + const { stdout } = await execa('git', [ + '-c', + 'submodule.recurse=false', + 'diff', + '--staged', + '--diff-filter=ACMR', + '--name-only', + '--ignore-submodules', + '-z', + ]); + + let changedList = stdout ? stdout.replace(/\0$/, '').split('\0') : []; + changedList = changedList.map((item) => path.resolve(process.cwd(), item)); + const changedSet = new Set(changedList); + changedSet.delete(''); + return [...changedSet]; + } catch (error) { + console.error('Failed to get staged files:', error); + return []; + } +} + +export { getStagedFiles }; diff --git a/internal/node-utils/src/hash.ts b/internal/node-utils/src/hash.ts new file mode 100644 index 0000000..81f6b05 --- /dev/null +++ b/internal/node-utils/src/hash.ts @@ -0,0 +1,18 @@ +import { createHash } from 'node:crypto'; + +/** + * 生产基于内容的 hash,可自定义长度 + * @param content + * @param hashLSize + */ +function generatorContentHash(content: string, hashLSize?: number) { + const hash = createHash('md5').update(content, 'utf8').digest('hex'); + + if (hashLSize) { + return hash.slice(0, hashLSize); + } + + return hash; +} + +export { generatorContentHash }; diff --git a/internal/node-utils/src/index.ts b/internal/node-utils/src/index.ts new file mode 100644 index 0000000..963cb87 --- /dev/null +++ b/internal/node-utils/src/index.ts @@ -0,0 +1,19 @@ +export * from './constants'; +export * from './date'; +export * from './fs'; +export * from './git'; +export { getStagedFiles, add as gitAdd } from './git'; +export { generatorContentHash } from './hash'; +export * from './monorepo'; +export { toPosixPath } from './path'; +export { prettierFormat } from './prettier'; +export * from './spinner'; +export type { Package } from '@manypkg/get-packages'; +export { default as colors } from 'chalk'; +export { consola } from 'consola'; +export * from 'execa'; + +export { default as fs } from 'node:fs/promises'; + +export { type PackageJson, readPackageJSON } from 'pkg-types'; +export { rimraf } from 'rimraf'; diff --git a/internal/node-utils/src/monorepo.ts b/internal/node-utils/src/monorepo.ts new file mode 100644 index 0000000..b6373e7 --- /dev/null +++ b/internal/node-utils/src/monorepo.ts @@ -0,0 +1,46 @@ +import { dirname } from 'node:path'; + +import { + getPackages as getPackagesFunc, + getPackagesSync as getPackagesSyncFunc, +} from '@manypkg/get-packages'; +import { findUpSync } from 'find-up'; + +/** + * 查找大仓的根目录 + * @param cwd + */ +function findMonorepoRoot(cwd: string = process.cwd()) { + const lockFile = findUpSync('pnpm-lock.yaml', { + cwd, + type: 'file', + }); + return dirname(lockFile || ''); +} + +/** + * 获取大仓的所有包 + */ +function getPackagesSync() { + const root = findMonorepoRoot(); + return getPackagesSyncFunc(root); +} + +/** + * 获取大仓的所有包 + */ +async function getPackages() { + const root = findMonorepoRoot(); + + return await getPackagesFunc(root); +} + +/** + * 获取大仓指定的包 + */ +async function getPackage(pkgName: string) { + const { packages } = await getPackages(); + return packages.find((pkg) => pkg.packageJson.name === pkgName); +} + +export { findMonorepoRoot, getPackage, getPackages, getPackagesSync }; diff --git a/internal/node-utils/src/path.ts b/internal/node-utils/src/path.ts new file mode 100644 index 0000000..e625fd2 --- /dev/null +++ b/internal/node-utils/src/path.ts @@ -0,0 +1,11 @@ +import { posix } from 'node:path'; + +/** + * 将给定的文件路径转换为 POSIX 风格。 + * @param {string} pathname - 原始文件路径。 + */ +function toPosixPath(pathname: string) { + return pathname.split(`\\`).join(posix.sep); +} + +export { toPosixPath }; diff --git a/internal/node-utils/src/prettier.ts b/internal/node-utils/src/prettier.ts new file mode 100644 index 0000000..1e1525d --- /dev/null +++ b/internal/node-utils/src/prettier.ts @@ -0,0 +1,21 @@ +import fs from 'node:fs/promises'; + +import { format, getFileInfo, resolveConfig } from 'prettier'; + +async function prettierFormat(filepath: string) { + const prettierOptions = await resolveConfig(filepath, {}); + + const fileInfo = await getFileInfo(filepath); + + const input = await fs.readFile(filepath, 'utf8'); + const output = await format(input, { + ...prettierOptions, + parser: fileInfo.inferredParser as any, + }); + if (output !== input) { + await fs.writeFile(filepath, output, 'utf8'); + } + return output; +} + +export { prettierFormat }; diff --git a/internal/node-utils/src/spinner.ts b/internal/node-utils/src/spinner.ts new file mode 100644 index 0000000..13ad6a4 --- /dev/null +++ b/internal/node-utils/src/spinner.ts @@ -0,0 +1,26 @@ +import type { Ora } from 'ora'; + +import ora from 'ora'; + +interface SpinnerOptions { + failedText?: string; + successText?: string; + title: string; +} +export async function spinner( + { failedText, successText, title }: SpinnerOptions, + callback: () => Promise, +): Promise { + const loading: Ora = ora(title).start(); + + try { + const result = await callback(); + loading.succeed(successText || 'Success!'); + return result; + } catch (error) { + loading.fail(failedText || 'Failed!'); + throw error; + } finally { + loading.stop(); + } +} diff --git a/internal/node-utils/tsconfig.json b/internal/node-utils/tsconfig.json new file mode 100644 index 0000000..c12c7ab --- /dev/null +++ b/internal/node-utils/tsconfig.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@aiflowy/tsconfig/node.json", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/internal/tailwind-config/build.config.ts b/internal/tailwind-config/build.config.ts new file mode 100644 index 0000000..1f3c3c2 --- /dev/null +++ b/internal/tailwind-config/build.config.ts @@ -0,0 +1,10 @@ +import { defineBuildConfig } from 'unbuild'; + +export default defineBuildConfig({ + clean: true, + declaration: true, + entries: ['src/index', './src/postcss.config'], + rollup: { + emitCJS: true, + }, +}); diff --git a/internal/tailwind-config/package.json b/internal/tailwind-config/package.json new file mode 100644 index 0000000..df09976 --- /dev/null +++ b/internal/tailwind-config/package.json @@ -0,0 +1,66 @@ +{ + "name": "@aiflowy/tailwind-config", + "version": "1.0.0", + "private": true, + "homepage": "https://github.com/aiflowy/aiflowy", + "bugs": "https://github.com/aiflowy/aiflowy/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/aiflowy/aiflowy.git", + "directory": "internal/tailwind-config" + }, + "license": "MIT", + "type": "module", + "scripts": { + "stub": "pnpm unbuild --stub" + }, + "files": [ + "dist" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "./dist/*", + "./*" + ] + } + }, + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + }, + "./postcss": { + "types": "./src/postcss.config.ts", + "import": "./dist/postcss.config.mjs", + "require": "./dist/postcss.config.cjs", + "default": "./dist/postcss.config.mjs" + }, + "./*": "./*" + }, + "peerDependencies": { + "tailwindcss": "^3.4.3" + }, + "dependencies": { + "@iconify/json": "catalog:", + "@iconify/tailwind": "catalog:", + "@manypkg/get-packages": "catalog:", + "@tailwindcss/nesting": "catalog:", + "@tailwindcss/typography": "catalog:", + "autoprefixer": "catalog:", + "cssnano": "catalog:", + "postcss": "catalog:", + "postcss-antd-fixes": "catalog:", + "postcss-import": "catalog:", + "postcss-preset-env": "catalog:", + "tailwindcss": "catalog:", + "tailwindcss-animate": "catalog:" + }, + "devDependencies": { + "@types/postcss-import": "catalog:" + } +} diff --git a/internal/tailwind-config/src/index.ts b/internal/tailwind-config/src/index.ts new file mode 100644 index 0000000..c554ffd --- /dev/null +++ b/internal/tailwind-config/src/index.ts @@ -0,0 +1,266 @@ +import type { Config } from 'tailwindcss'; + +import path from 'node:path'; + +import { addDynamicIconSelectors } from '@iconify/tailwind'; +import { getPackagesSync } from '@manypkg/get-packages'; +import typographyPlugin from '@tailwindcss/typography'; +import animate from 'tailwindcss-animate'; + +import { enterAnimationPlugin } from './plugins/entry'; + +// import defaultTheme from 'tailwindcss/defaultTheme'; + +const { packages } = getPackagesSync(process.cwd()); + +const tailwindPackages: string[] = []; + +packages.forEach((pkg) => { + // apps目录下和 @aiflowy-core/tailwind-ui 包需要使用到 tailwindcss ui + // if (fs.existsSync(path.join(pkg.dir, 'tailwind.config.mjs'))) { + tailwindPackages.push(pkg.dir); + // } +}); + +const shadcnUiColors = { + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))', + hover: 'hsl(var(--accent-hover))', + lighter: 'has(val(--accent-lighter))', + }, + background: { + deep: 'hsl(var(--background-deep))', + DEFAULT: 'hsl(var(--background))', + }, + border: { + DEFAULT: 'hsl(var(--border))', + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))', + }, + destructive: { + ...createColorsPalette('destructive'), + DEFAULT: 'hsl(var(--destructive))', + }, + + foreground: { + DEFAULT: 'hsl(var(--foreground))', + }, + + input: { + background: 'hsl(var(--input-background))', + DEFAULT: 'hsl(var(--input))', + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))', + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))', + }, + primary: { + ...createColorsPalette('primary'), + DEFAULT: 'hsl(var(--primary))', + }, + + ring: 'hsl(var(--ring))', + secondary: { + DEFAULT: 'hsl(var(--secondary))', + desc: 'hsl(var(--secondary-desc))', + foreground: 'hsl(var(--secondary-foreground))', + }, +}; + +const customColors = { + green: { + ...createColorsPalette('green'), + foreground: 'hsl(var(--success-foreground))', + }, + header: { + DEFAULT: 'hsl(var(--header))', + }, + heavy: { + DEFAULT: 'hsl(var(--heavy))', + foreground: 'hsl(var(--heavy-foreground))', + }, + main: { + DEFAULT: 'hsl(var(--main))', + }, + overlay: { + content: 'hsl(var(--overlay-content))', + DEFAULT: 'hsl(var(--overlay))', + }, + red: { + ...createColorsPalette('red'), + foreground: 'hsl(var(--destructive-foreground))', + }, + sidebar: { + deep: 'hsl(var(--sidebar-deep))', + DEFAULT: 'hsl(var(--sidebar))', + }, + success: { + ...createColorsPalette('success'), + DEFAULT: 'hsl(var(--success))', + }, + warning: { + ...createColorsPalette('warning'), + DEFAULT: 'hsl(var(--warning))', + }, + yellow: { + ...createColorsPalette('yellow'), + foreground: 'hsl(var(--warning-foreground))', + }, +}; + +export default { + content: [ + './index.html', + ...tailwindPackages.map((item) => + path.join(item, 'src/**/*.{vue,js,ts,jsx,tsx,svelte,astro,html}'), + ), + ], + darkMode: 'selector', + plugins: [ + animate, + typographyPlugin, + addDynamicIconSelectors(), + enterAnimationPlugin, + ], + prefix: '', + theme: { + container: { + center: true, + padding: '2rem', + screens: { + '2xl': '1400px', + }, + }, + extend: { + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out', + 'collapsible-down': 'collapsible-down 0.2s ease-in-out', + 'collapsible-up': 'collapsible-up 0.2s ease-in-out', + float: 'float 5s linear 0ms infinite', + }, + + animationDuration: { + '2000': '2000ms', + '3000': '3000ms', + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)', + xl: 'calc(var(--radius) + 4px)', + }, + boxShadow: { + float: `0 6px 16px 0 rgb(0 0 0 / 8%), + 0 3px 6px -4px rgb(0 0 0 / 12%), + 0 9px 28px 8px rgb(0 0 0 / 5%)`, + }, + colors: { + ...customColors, + ...shadcnUiColors, + }, + fontFamily: { + sans: [ + 'var(--font-family)', + // ...defaultTheme.fontFamily.sans + ], + }, + keyframes: { + 'accordion-down': { + from: { height: '0' }, + to: { height: 'var(--reka-accordion-content-height)' }, + }, + 'accordion-up': { + from: { height: 'var(--reka-accordion-content-height)' }, + to: { height: '0' }, + }, + 'collapsible-down': { + from: { height: '0' }, + to: { height: 'var(--reka-collapsible-content-height)' }, + }, + 'collapsible-up': { + from: { height: 'var(--reka-collapsible-content-height)' }, + to: { height: '0' }, + }, + float: { + '0%': { transform: 'translateY(0)' }, + '50%': { transform: 'translateY(-20px)' }, + '100%': { transform: 'translateY(0)' }, + }, + }, + zIndex: { + '100': '100', + '1000': '1000', + }, + }, + }, + safelist: ['dark'], +} as Config; + +function createColorsPalette(name: string) { + // backgroundLightest: '#EFF6FF', // Tailwind CSS 默认的 `blue-50` + // backgroundLighter: '#DBEAFE', // Tailwind CSS 默认的 `blue-100` + // backgroundLight: '#BFDBFE', // Tailwind CSS 默认的 `blue-200` + // borderLight: '#93C5FD', // Tailwind CSS 默认的 `blue-300` + // border: '#60A5FA', // Tailwind CSS 默认的 `blue-400` + // main: '#3B82F6', // Tailwind CSS 默认的 `blue-500` + // hover: '#2563EB', // Tailwind CSS 默认的 `blue-600` + // active: '#1D4ED8', // Tailwind CSS 默认的 `blue-700` + // backgroundDark: '#1E40AF', // Tailwind CSS 默认的 `blue-800` + // backgroundDarker: '#1E3A8A', // Tailwind CSS 默认的 `blue-900` + // backgroundDarkest: '#172554', // Tailwind CSS 默认的 `blue-950` + + // • backgroundLightest (#EFF6FF): 适用于最浅的背景色,可能用于非常轻微的阴影或卡片的背景。 + // • backgroundLighter (#DBEAFE): 适用于略浅的背景色,通常用于次要背景或略浅的区域。 + // • backgroundLight (#BFDBFE): 适用于浅色背景,可能用于输入框或表单区域的背景。 + // • borderLight (#93C5FD): 适用于浅色边框,可能用于输入框或卡片的边框。 + // • border (#60A5FA): 适用于普通边框,可能用于按钮或卡片的边框。 + // • main (#3B82F6): 适用于主要的主题色,通常用于按钮、链接或主要的强调色。 + // • hover (#2563EB): 适用于鼠标悬停状态下的颜色,例如按钮悬停时的背景色或边框色。 + // • active (#1D4ED8): 适用于激活状态下的颜色,例如按钮按下时的背景色或边框色。 + // • backgroundDark (#1E40AF): 适用于深色背景,可能用于主要按钮或深色卡片背景。 + // • backgroundDarker (#1E3A8A): 适用于更深的背景,通常用于头部导航栏或页脚。 + // • backgroundDarkest (#172554): 适用于最深的背景,可能用于非常深色的区域或极端对比色。 + + return { + 50: `hsl(var(--${name}-50))`, + 100: `hsl(var(--${name}-100))`, + 200: `hsl(var(--${name}-200))`, + 300: `hsl(var(--${name}-300))`, + 400: `hsl(var(--${name}-400))`, + 500: `hsl(var(--${name}-500))`, + 600: `hsl(var(--${name}-600))`, + 700: `hsl(var(--${name}-700))`, + // 800: `hsl(var(--${name}-800))`, + // 900: `hsl(var(--${name}-900))`, + // 950: `hsl(var(--${name}-950))`, + // 激活状态下的颜色,适用于按钮按下时的背景色或边框色。 + active: `hsl(var(--${name}-700))`, + // 浅色背景,适用于输入框或表单区域的背景。 + 'background-light': `hsl(var(--${name}-200))`, + // 适用于略浅的背景色,通常用于次要背景或略浅的区域。 + 'background-lighter': `hsl(var(--${name}-100))`, + // 最浅的背景色,适用于非常轻微的阴影或卡片的背景。 + 'background-lightest': `hsl(var(--${name}-50))`, + // 适用于普通边框,可能用于按钮或卡片的边框。 + border: `hsl(var(--${name}-400))`, + // 浅色边框,适用于输入框或卡片的边框。 + 'border-light': `hsl(var(--${name}-300))`, + foreground: `hsl(var(--${name}-foreground))`, + // 鼠标悬停状态下的颜色,适用于按钮悬停时的背景色或边框色。 + hover: `hsl(var(--${name}-600))`, + // 主色文本 + text: `hsl(var(--${name}-500))`, + // 主色文本激活态 + 'text-active': `hsl(var(--${name}-700))`, + // 主色文本悬浮态 + 'text-hover': `hsl(var(--${name}-600))`, + }; +} diff --git a/internal/tailwind-config/src/module.d.ts b/internal/tailwind-config/src/module.d.ts new file mode 100644 index 0000000..a399653 --- /dev/null +++ b/internal/tailwind-config/src/module.d.ts @@ -0,0 +1,3 @@ +declare module '@tailwindcss/nesting' { + export default any; +} diff --git a/internal/tailwind-config/src/plugins/entry.ts b/internal/tailwind-config/src/plugins/entry.ts new file mode 100644 index 0000000..0d8e8ec --- /dev/null +++ b/internal/tailwind-config/src/plugins/entry.ts @@ -0,0 +1,53 @@ +import plugin from 'tailwindcss/plugin.js'; + +const enterAnimationPlugin = plugin(({ addUtilities }) => { + const maxChild = 5; + const utilities: Record = {}; + for (let i = 1; i <= maxChild; i++) { + const baseDelay = 0.1; + const delay = `${baseDelay * i}s`; + + utilities[`.enter-x:nth-child(${i})`] = { + animation: `enter-x-animation 0.3s ease-in-out ${delay} forwards`, + opacity: '0', + transform: `translateX(50px)`, + }; + + utilities[`.enter-y:nth-child(${i})`] = { + animation: `enter-y-animation 0.3s ease-in-out ${delay} forwards`, + opacity: '0', + transform: `translateY(50px)`, + }; + + utilities[`.-enter-x:nth-child(${i})`] = { + animation: `enter-x-animation 0.3s ease-in-out ${delay} forwards`, + opacity: '0', + transform: `translateX(-50px)`, + }; + + utilities[`.-enter-y:nth-child(${i})`] = { + animation: `enter-y-animation 0.3s ease-in-out ${delay} forwards`, + opacity: '0', + transform: `translateY(-50px)`, + }; + } + + // 添加动画关键帧 + addUtilities(utilities); + addUtilities({ + '@keyframes enter-x-animation': { + to: { + opacity: '1', + transform: 'translateX(0)', + }, + }, + '@keyframes enter-y-animation': { + to: { + opacity: '1', + transform: 'translateY(0)', + }, + }, + }); +}); + +export { enterAnimationPlugin }; diff --git a/internal/tailwind-config/src/postcss.config.ts b/internal/tailwind-config/src/postcss.config.ts new file mode 100644 index 0000000..43b30b3 --- /dev/null +++ b/internal/tailwind-config/src/postcss.config.ts @@ -0,0 +1,15 @@ +import config from '.'; + +export default { + plugins: { + ...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}), + // Specifying the config is not necessary in most cases, but it is included + autoprefixer: {}, + // 修复 element-plus 和 ant-design-vue 的样式和tailwindcss冲突问题 + 'postcss-antd-fixes': { prefixes: ['ant', 'el'] }, + 'postcss-import': {}, + 'postcss-preset-env': {}, + tailwindcss: { config }, + 'tailwindcss/nesting': {}, + }, +}; diff --git a/internal/tailwind-config/tsconfig.json b/internal/tailwind-config/tsconfig.json new file mode 100644 index 0000000..9b3ffb9 --- /dev/null +++ b/internal/tailwind-config/tsconfig.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@aiflowy/tsconfig/node.json", + "compilerOptions": { + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/internal/tsconfig/base.json b/internal/tsconfig/base.json new file mode 100644 index 0000000..1e45a78 --- /dev/null +++ b/internal/tsconfig/base.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Base", + "compilerOptions": { + "composite": false, + "target": "ESNext", + + "moduleDetection": "force", + "experimentalDecorators": true, + + "baseUrl": ".", + "module": "ESNext", + + "moduleResolution": "node", + "resolveJsonModule": true, + + "strict": true, + "strictNullChecks": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitOverride": true, + "noImplicitThis": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + + "inlineSources": false, + "noEmit": true, + "removeComments": true, + "sourceMap": false, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true, + "preserveWatchOutput": true + }, + "exclude": ["**/node_modules/**", "**/dist/**", "**/.turbo/**"] +} diff --git a/internal/tsconfig/library.json b/internal/tsconfig/library.json new file mode 100644 index 0000000..7a976f0 --- /dev/null +++ b/internal/tsconfig/library.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Web Application", + "extends": "./base.json", + "compilerOptions": { + "jsx": "preserve", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "useDefineForClassFields": true, + "moduleResolution": "bundler", + "declaration": true, + "noEmit": false + } +} diff --git a/internal/tsconfig/node.json b/internal/tsconfig/node.json new file mode 100644 index 0000000..31ce8f1 --- /dev/null +++ b/internal/tsconfig/node.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Node Config", + "extends": "./base.json", + "compilerOptions": { + "composite": false, + "lib": ["ESNext"], + "baseUrl": "./", + "types": ["node"], + "noImplicitAny": true + } +} diff --git a/internal/tsconfig/package.json b/internal/tsconfig/package.json new file mode 100644 index 0000000..76a538a --- /dev/null +++ b/internal/tsconfig/package.json @@ -0,0 +1,25 @@ +{ + "name": "@aiflowy/tsconfig", + "version": "1.0.0", + "private": true, + "homepage": "https://github.com/aiflowy/aiflowy", + "bugs": "https://github.com/aiflowy/aiflowy/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/aiflowy/aiflowy.git", + "directory": "internal/tsconfig" + }, + "license": "MIT", + "type": "module", + "files": [ + "base.json", + "library.json", + "node.json", + "web-app.json", + "web.json" + ], + "dependencies": { + "@aiflowy/types": "workspace:*", + "vite": "catalog:" + } +} diff --git a/internal/tsconfig/web-app.json b/internal/tsconfig/web-app.json new file mode 100644 index 0000000..e65e65e --- /dev/null +++ b/internal/tsconfig/web-app.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Web Application", + "extends": "./web.json", + "compilerOptions": { + "types": ["vite/client", "@aiflowy/types/global"] + } +} diff --git a/internal/tsconfig/web.json b/internal/tsconfig/web.json new file mode 100644 index 0000000..a4b60ce --- /dev/null +++ b/internal/tsconfig/web.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Web Package", + "extends": "./base.json", + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "vue", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "useDefineForClassFields": true, + "moduleResolution": "bundler", + "types": ["vite/client"], + "declaration": false + } +} diff --git a/internal/vite-config/build.config.ts b/internal/vite-config/build.config.ts new file mode 100644 index 0000000..97e572c --- /dev/null +++ b/internal/vite-config/build.config.ts @@ -0,0 +1,7 @@ +import { defineBuildConfig } from 'unbuild'; + +export default defineBuildConfig({ + clean: true, + declaration: true, + entries: ['src/index'], +}); diff --git a/internal/vite-config/package.json b/internal/vite-config/package.json new file mode 100644 index 0000000..5771731 --- /dev/null +++ b/internal/vite-config/package.json @@ -0,0 +1,59 @@ +{ + "name": "@aiflowy/vite-config", + "version": "1.0.0", + "private": true, + "homepage": "https://github.com/aiflowy/aiflowy", + "bugs": "https://github.com/aiflowy/aiflowy/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/aiflowy/aiflowy.git", + "directory": "internal/vite-config" + }, + "license": "MIT", + "type": "module", + "scripts": { + "stub": "pnpm unbuild --stub" + }, + "files": [ + "dist" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./dist/index.mjs" + } + }, + "dependencies": { + "@intlify/unplugin-vue-i18n": "catalog:", + "@jspm/generator": "catalog:", + "archiver": "catalog:", + "cheerio": "catalog:", + "get-port": "catalog:", + "html-minifier-terser": "catalog:", + "nitropack": "catalog:", + "resolve.exports": "catalog:", + "vite-plugin-pwa": "catalog:", + "vite-plugin-vue-devtools": "catalog:" + }, + "devDependencies": { + "@pnpm/workspace.read-manifest": "catalog:", + "@types/archiver": "catalog:", + "@types/html-minifier-terser": "catalog:", + "@aiflowy/node-utils": "workspace:*", + "@vitejs/plugin-vue": "catalog:", + "@vitejs/plugin-vue-jsx": "catalog:", + "dayjs": "catalog:", + "dotenv": "catalog:", + "rollup": "catalog:", + "rollup-plugin-visualizer": "catalog:", + "sass": "catalog:", + "vite": "catalog:", + "vite-plugin-compression": "catalog:", + "vite-plugin-dts": "catalog:", + "vite-plugin-html": "catalog:", + "vite-plugin-lazy-import": "catalog:" + } +} diff --git a/internal/vite-config/src/config/application.ts b/internal/vite-config/src/config/application.ts new file mode 100644 index 0000000..2a861d6 --- /dev/null +++ b/internal/vite-config/src/config/application.ts @@ -0,0 +1,125 @@ +import type { CSSOptions, UserConfig } from 'vite'; + +import type { DefineApplicationOptions } from '../typing'; + +import path, { relative } from 'node:path'; + +import { findMonorepoRoot } from '@aiflowy/node-utils'; + +import { NodePackageImporter } from 'sass'; +import { defineConfig, loadEnv, mergeConfig } from 'vite'; + +import { defaultImportmapOptions, getDefaultPwaOptions } from '../options'; +import { loadApplicationPlugins } from '../plugins'; +import { loadAndConvertEnv } from '../utils/env'; +import { getCommonConfig } from './common'; + +function defineApplicationConfig(userConfigPromise?: DefineApplicationOptions) { + return defineConfig(async (config) => { + const options = await userConfigPromise?.(config); + const { appTitle, base, port, ...envConfig } = await loadAndConvertEnv(); + const { command, mode } = config; + const { application = {}, vite = {} } = options || {}; + const root = process.cwd(); + const isBuild = command === 'build'; + const env = loadEnv(mode, root); + + const plugins = await loadApplicationPlugins({ + archiver: true, + archiverPluginOptions: {}, + compress: false, + compressTypes: ['brotli', 'gzip'], + devtools: true, + env, + extraAppConfig: true, + html: true, + i18n: true, + importmapOptions: defaultImportmapOptions, + injectAppLoading: true, + injectMetadata: true, + isBuild, + license: true, + mode, + nitroMock: !isBuild, + nitroMockOptions: {}, + print: !isBuild, + printInfoMap: { + 'AIFlowy Docs': 'https://aiflowy.tech', + }, + pwa: true, + pwaOptions: getDefaultPwaOptions(appTitle), + vxeTableLazyImport: true, + ...envConfig, + ...application, + }); + + const { injectGlobalScss = true } = application; + + const applicationConfig: UserConfig = { + base, + build: { + rollupOptions: { + output: { + assetFileNames: '[ext]/[name]-[hash].[ext]', + chunkFileNames: 'js/[name]-[hash].js', + entryFileNames: 'jse/index-[name]-[hash].js', + }, + }, + target: 'es2015', + }, + css: createCssOptions(injectGlobalScss), + esbuild: { + drop: isBuild + ? [ + // 'console', + 'debugger', + ] + : [], + legalComments: 'none', + }, + plugins, + server: { + host: true, + port, + warmup: { + // 预热文件 + clientFiles: [ + './index.html', + './src/bootstrap.ts', + './src/{views,layouts,router,store,api,adapter}/*', + ], + }, + }, + }; + + const mergedCommonConfig = mergeConfig( + await getCommonConfig(), + applicationConfig, + ); + return mergeConfig(mergedCommonConfig, vite); + }); +} + +function createCssOptions(injectGlobalScss = true): CSSOptions { + const root = findMonorepoRoot(); + return { + preprocessorOptions: injectGlobalScss + ? { + scss: { + additionalData: (content: string, filepath: string) => { + const relativePath = relative(root, filepath); + // apps下的包注入全局样式 + if (relativePath.startsWith(`apps${path.sep}`)) { + return `@use "@aiflowy/styles/global" as *;\n${content}`; + } + return content; + }, + api: 'modern', + importers: [new NodePackageImporter()], + }, + } + : {}, + }; +} + +export { defineApplicationConfig }; diff --git a/internal/vite-config/src/config/common.ts b/internal/vite-config/src/config/common.ts new file mode 100644 index 0000000..653f210 --- /dev/null +++ b/internal/vite-config/src/config/common.ts @@ -0,0 +1,13 @@ +import type { UserConfig } from 'vite'; + +async function getCommonConfig(): Promise { + return { + build: { + chunkSizeWarningLimit: 2000, + reportCompressedSize: false, + sourcemap: false, + }, + }; +} + +export { getCommonConfig }; diff --git a/internal/vite-config/src/config/index.ts b/internal/vite-config/src/config/index.ts new file mode 100644 index 0000000..d04a84a --- /dev/null +++ b/internal/vite-config/src/config/index.ts @@ -0,0 +1,37 @@ +import type { DefineConfig } from '../typing'; + +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; + +import { defineApplicationConfig } from './application'; +import { defineLibraryConfig } from './library'; + +export * from './application'; +export * from './library'; + +function defineConfig( + userConfigPromise?: DefineConfig, + type: 'application' | 'auto' | 'library' = 'auto', +) { + let projectType = type; + + // 根据包是否存在 index.html,自动判断类型 + if (projectType === 'auto') { + const htmlPath = join(process.cwd(), 'index.html'); + projectType = existsSync(htmlPath) ? 'application' : 'library'; + } + + switch (projectType) { + case 'application': { + return defineApplicationConfig(userConfigPromise); + } + case 'library': { + return defineLibraryConfig(userConfigPromise); + } + default: { + throw new Error(`Unsupported project type: ${projectType}`); + } + } +} + +export { defineConfig }; diff --git a/internal/vite-config/src/config/library.ts b/internal/vite-config/src/config/library.ts new file mode 100644 index 0000000..3522b98 --- /dev/null +++ b/internal/vite-config/src/config/library.ts @@ -0,0 +1,59 @@ +import type { ConfigEnv, UserConfig } from 'vite'; + +import type { DefineLibraryOptions } from '../typing'; + +import { readPackageJSON } from '@aiflowy/node-utils'; + +import { defineConfig, mergeConfig } from 'vite'; + +import { loadLibraryPlugins } from '../plugins'; +import { getCommonConfig } from './common'; + +function defineLibraryConfig(userConfigPromise?: DefineLibraryOptions) { + return defineConfig(async (config: ConfigEnv) => { + const options = await userConfigPromise?.(config); + const { command, mode } = config; + const { library = {}, vite = {} } = options || {}; + const root = process.cwd(); + const isBuild = command === 'build'; + + const plugins = await loadLibraryPlugins({ + dts: false, + injectMetadata: true, + isBuild, + mode, + ...library, + }); + + const { dependencies = {}, peerDependencies = {} } = + await readPackageJSON(root); + + const externalPackages = [ + ...Object.keys(dependencies), + ...Object.keys(peerDependencies), + ]; + + const packageConfig: UserConfig = { + build: { + lib: { + entry: 'src/index.ts', + fileName: () => 'index.mjs', + formats: ['es'], + }, + rollupOptions: { + external: (id) => { + return externalPackages.some( + (pkg) => id === pkg || id.startsWith(`${pkg}/`), + ); + }, + }, + }, + plugins, + }; + const commonConfig = await getCommonConfig(); + const mergedConmonConfig = mergeConfig(commonConfig, packageConfig); + return mergeConfig(mergedConmonConfig, vite); + }); +} + +export { defineLibraryConfig }; diff --git a/internal/vite-config/src/index.ts b/internal/vite-config/src/index.ts new file mode 100644 index 0000000..352a323 --- /dev/null +++ b/internal/vite-config/src/index.ts @@ -0,0 +1,4 @@ +export * from './config'; +export * from './options'; +export * from './plugins'; +export { loadAndConvertEnv } from './utils/env'; diff --git a/internal/vite-config/src/options.ts b/internal/vite-config/src/options.ts new file mode 100644 index 0000000..af1d648 --- /dev/null +++ b/internal/vite-config/src/options.ts @@ -0,0 +1,45 @@ +import type { Options as PwaPluginOptions } from 'vite-plugin-pwa'; + +import type { ImportmapPluginOptions } from './typing'; + +const isDevelopment = process.env.NODE_ENV === 'development'; + +const getDefaultPwaOptions = (name: string): Partial => ({ + manifest: { + description: + 'AIFlowy Admin is a modern admin dashboard template based on Vue 3. ', + icons: [ + { + sizes: '192x192', + src: 'https://unpkg.com/@aiflowy/static-source@0.1.7/source/pwa-icon-192.png', + type: 'image/png', + }, + { + sizes: '512x512', + src: 'https://unpkg.com/@aiflowy/static-source@0.1.7/source/pwa-icon-512.png', + type: 'image/png', + }, + ], + name: `${name}${isDevelopment ? ' dev' : ''}`, + short_name: `${name}${isDevelopment ? ' dev' : ''}`, + }, +}); + +/** + * importmap CDN 暂时不开启,因为有些包不支持,且网络不稳定 + */ +const defaultImportmapOptions: ImportmapPluginOptions = { + // 通过 Importmap CDN 方式引入, + // 目前只有esm.sh源兼容性好一点,jspm.io对于 esm 入口要求高 + defaultProvider: 'esm.sh', + importmap: [ + { name: 'vue' }, + { name: 'pinia' }, + { name: 'vue-router' }, + // { name: 'vue-i18n' }, + { name: 'dayjs' }, + { name: 'vue-demi' }, + ], +}; + +export { defaultImportmapOptions, getDefaultPwaOptions }; diff --git a/internal/vite-config/src/plugins/archiver.ts b/internal/vite-config/src/plugins/archiver.ts new file mode 100644 index 0000000..8eec8a0 --- /dev/null +++ b/internal/vite-config/src/plugins/archiver.ts @@ -0,0 +1,75 @@ +import type { PluginOption } from 'vite'; + +import type { ArchiverPluginOptions } from '../typing'; + +import fs from 'node:fs'; +import fsp from 'node:fs/promises'; +import { join } from 'node:path'; + +import archiver from 'archiver'; + +export const viteArchiverPlugin = ( + options: ArchiverPluginOptions = {}, +): PluginOption => { + return { + apply: 'build', + closeBundle: { + handler() { + const { name = 'dist', outputDir = '.' } = options; + + setTimeout(async () => { + const folderToZip = 'dist'; + + const zipOutputDir = join(process.cwd(), outputDir); + const zipOutputPath = join(zipOutputDir, `${name}.zip`); + try { + await fsp.mkdir(zipOutputDir, { recursive: true }); + } catch { + // ignore + } + + try { + await zipFolder(folderToZip, zipOutputPath); + console.log(`Folder has been zipped to: ${zipOutputPath}`); + } catch (error) { + console.error('Error zipping folder:', error); + } + }, 0); + }, + order: 'post', + }, + enforce: 'post', + name: 'vite:archiver', + }; +}; + +async function zipFolder( + folderPath: string, + outputPath: string, +): Promise { + return new Promise((resolve, reject) => { + const output = fs.createWriteStream(outputPath); + const archive = archiver('zip', { + zlib: { level: 9 }, // 设置压缩级别为 9 以实现最高压缩率 + }); + + output.on('close', () => { + console.log( + `ZIP file created: ${outputPath} (${archive.pointer()} total bytes)`, + ); + resolve(); + }); + + archive.on('error', (err) => { + reject(err); + }); + + archive.pipe(output); + + // 使用 directory 方法以流的方式压缩文件夹,减少内存消耗 + archive.directory(folderPath, false); + + // 流式处理完成 + archive.finalize(); + }); +} diff --git a/internal/vite-config/src/plugins/extra-app-config.ts b/internal/vite-config/src/plugins/extra-app-config.ts new file mode 100644 index 0000000..00007ba --- /dev/null +++ b/internal/vite-config/src/plugins/extra-app-config.ts @@ -0,0 +1,92 @@ +import type { PluginOption } from 'vite'; + +import { + colors, + generatorContentHash, + readPackageJSON, +} from '@aiflowy/node-utils'; + +import { loadEnv } from '../utils/env'; + +interface PluginOptions { + isBuild: boolean; + root: string; +} + +const GLOBAL_CONFIG_FILE_NAME = '_app.config.js'; +const APP_ADMIN_PRO_APP_CONF = '_APP_ADMIN_PRO_APP_CONF_'; + +/** + * 用于将配置文件抽离出来并注入到项目中 + * @returns + */ + +async function viteExtraAppConfigPlugin({ + isBuild, + root, +}: PluginOptions): Promise { + let publicPath: string; + let source: string; + + if (!isBuild) { + return; + } + + const { version = '' } = await readPackageJSON(root); + + return { + async configResolved(config) { + publicPath = ensureTrailingSlash(config.base); + source = await getConfigSource(); + }, + async generateBundle() { + try { + this.emitFile({ + fileName: GLOBAL_CONFIG_FILE_NAME, + source, + type: 'asset', + }); + + console.log(colors.cyan(`✨configuration file is build successfully!`)); + } catch (error) { + console.log( + colors.red( + `configuration file configuration file failed to package:\n${error}`, + ), + ); + } + }, + name: 'vite:extra-app-config', + async transformIndexHtml(html) { + const hash = `v=${version}-${generatorContentHash(source, 8)}`; + + const appConfigSrc = `${publicPath}${GLOBAL_CONFIG_FILE_NAME}?${hash}`; + + return { + html, + tags: [{ attrs: { src: appConfigSrc }, tag: 'script' }], + }; + }, + }; +} + +async function getConfigSource() { + const config = await loadEnv(); + const windowVariable = `window.${APP_ADMIN_PRO_APP_CONF}`; + // 确保变量不会被修改 + let source = `${windowVariable}=${JSON.stringify(config)};`; + source += ` + Object.freeze(${windowVariable}); + Object.defineProperty(window, "${APP_ADMIN_PRO_APP_CONF}", { + configurable: false, + writable: false, + }); + `.replaceAll(/\s/g, ''); + return source; +} + +function ensureTrailingSlash(path: string) { + return path.endsWith('/') ? path : `${path}/`; +} + +export { viteExtraAppConfigPlugin }; diff --git a/internal/vite-config/src/plugins/importmap.ts b/internal/vite-config/src/plugins/importmap.ts new file mode 100644 index 0000000..0ccda99 --- /dev/null +++ b/internal/vite-config/src/plugins/importmap.ts @@ -0,0 +1,245 @@ +/** + * 参考 https://github.com/jspm/vite-plugin-jspm,调整为需要的功能 + */ +import type { GeneratorOptions } from '@jspm/generator'; +import type { Plugin } from 'vite'; + +import { Generator } from '@jspm/generator'; +import { load } from 'cheerio'; +import { minify } from 'html-minifier-terser'; + +const DEFAULT_PROVIDER = 'jspm.io'; + +type pluginOptions = GeneratorOptions & { + debug?: boolean; + defaultProvider?: 'esm.sh' | 'jsdelivr' | 'jspm.io'; + importmap?: Array<{ name: string; range?: string }>; +}; + +// async function getLatestVersionOfShims() { +// const result = await fetch('https://ga.jspm.io/npm:es-module-shims'); +// const version = result.text(); +// return version; +// } + +async function getShimsUrl(provide: string) { + // const version = await getLatestVersionOfShims(); + const version = '1.10.0'; + + const shimsSubpath = `dist/es-module-shims.js`; + const providerShimsMap: Record = { + 'esm.sh': `https://esm.sh/es-module-shims@${version}/${shimsSubpath}`, + // unpkg: `https://unpkg.com/es-module-shims@${version}/${shimsSubpath}`, + jsdelivr: `https://cdn.jsdelivr.net/npm/es-module-shims@${version}/${shimsSubpath}`, + + // 下面两个CDN不稳定,暂时不用 + 'jspm.io': `https://ga.jspm.io/npm:es-module-shims@${version}/${shimsSubpath}`, + }; + + return providerShimsMap[provide] || providerShimsMap[DEFAULT_PROVIDER]; +} + +let generator: Generator; + +async function viteImportMapPlugin( + pluginOptions?: pluginOptions, +): Promise { + const { importmap } = pluginOptions || {}; + + let isSSR = false; + let isBuild = false; + let installed = false; + let installError: Error | null = null; + + const options: pluginOptions = Object.assign( + {}, + { + debug: false, + defaultProvider: 'jspm.io', + env: ['production', 'browser', 'module'], + importmap: [], + }, + pluginOptions, + ); + + generator = new Generator({ + ...options, + baseUrl: process.cwd(), + }); + + if (options?.debug) { + (async () => { + for await (const { message, type } of generator.logStream()) { + console.log(`${type}: ${message}`); + } + })(); + } + + const imports = options.inputMap?.imports ?? {}; + const scopes = options.inputMap?.scopes ?? {}; + const firstLayerKeys = Object.keys(scopes); + const inputMapScopes: string[] = []; + firstLayerKeys.forEach((key) => { + inputMapScopes.push(...Object.keys(scopes[key] || {})); + }); + const inputMapImports = Object.keys(imports); + + const allDepNames: string[] = [ + ...(importmap?.map((item) => item.name) || []), + ...inputMapImports, + ...inputMapScopes, + ]; + const depNames = new Set(allDepNames); + + const installDeps = importmap?.map((item) => ({ + range: item.range, + target: item.name, + })); + + return [ + { + async config(_, { command, isSsrBuild }) { + isBuild = command === 'build'; + isSSR = !!isSsrBuild; + }, + enforce: 'pre', + name: 'importmap:external', + resolveId(id) { + if (isSSR || !isBuild) { + return null; + } + + if (!depNames.has(id)) { + return null; + } + return { external: true, id }; + }, + }, + { + enforce: 'post', + name: 'importmap:install', + async resolveId() { + if (isSSR || !isBuild || installed) { + return null; + } + try { + installed = true; + await Promise.allSettled( + (installDeps || []).map((dep) => generator.install(dep)), + ); + } catch (error: any) { + installError = error; + installed = false; + } + return null; + }, + }, + { + buildEnd() { + // 未生成importmap时,抛出错误,防止被turbo缓存 + if (!installed && !isSSR) { + installError && console.error(installError); + throw new Error('Importmap installation failed.'); + } + }, + enforce: 'post', + name: 'importmap:html', + transformIndexHtml: { + async handler(html) { + if (isSSR || !isBuild) { + return html; + } + + const importmapJson = generator.getMap(); + + if (!importmapJson) { + return html; + } + + const esModuleShimsSrc = await getShimsUrl( + options.defaultProvider || DEFAULT_PROVIDER, + ); + + const resultHtml = await injectShimsToHtml( + html, + esModuleShimsSrc || '', + ); + html = await minify(resultHtml || html, { + collapseWhitespace: true, + minifyCSS: true, + minifyJS: true, + removeComments: false, + }); + + return { + html, + tags: [ + { + attrs: { + type: 'importmap', + }, + injectTo: 'head-prepend', + tag: 'script', + children: `${JSON.stringify(importmapJson)}`, + }, + ], + }; + }, + order: 'post', + }, + }, + ]; +} + +async function injectShimsToHtml(html: string, esModuleShimUrl: string) { + const $ = load(html); + + const $script = $(`script[type='module']`); + + if (!$script) { + return; + } + + const entry = $script.attr('src'); + + $script.removeAttr('type'); + $script.removeAttr('crossorigin'); + $script.removeAttr('src'); + $script.html(` +if (!HTMLScriptElement.supports || !HTMLScriptElement.supports('importmap')) { + self.importShim = function () { + const promise = new Promise((resolve, reject) => { + document.head.appendChild( + Object.assign(document.createElement('script'), { + src: '${esModuleShimUrl}', + crossorigin: 'anonymous', + async: true, + onload() { + if (!importShim.$proxy) { + resolve(importShim); + } else { + reject(new Error('No globalThis.importShim found:' + esModuleShimUrl)); + } + }, + onerror(error) { + reject(error); + }, + }), + ); + }); + importShim.$proxy = true; + return promise.then((importShim) => importShim(...arguments)); + }; +} + +var modules = ['${entry}']; +typeof importShim === 'function' + ? modules.forEach((moduleName) => importShim(moduleName)) + : modules.forEach((moduleName) => import(moduleName)); + `); + $('body').after($script); + $('head').remove(`script[type='module']`); + return $.html(); +} + +export { viteImportMapPlugin }; diff --git a/internal/vite-config/src/plugins/index.ts b/internal/vite-config/src/plugins/index.ts new file mode 100644 index 0000000..da08db4 --- /dev/null +++ b/internal/vite-config/src/plugins/index.ts @@ -0,0 +1,247 @@ +import type { PluginOption } from 'vite'; + +import type { + ApplicationPluginOptions, + CommonPluginOptions, + ConditionPlugin, + LibraryPluginOptions, +} from '../typing'; + +import viteVueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'; +import viteVue from '@vitejs/plugin-vue'; +import viteVueJsx from '@vitejs/plugin-vue-jsx'; +import { visualizer as viteVisualizerPlugin } from 'rollup-plugin-visualizer'; +import viteCompressPlugin from 'vite-plugin-compression'; +import viteDtsPlugin from 'vite-plugin-dts'; +import { createHtmlPlugin as viteHtmlPlugin } from 'vite-plugin-html'; +import { VitePWA } from 'vite-plugin-pwa'; +import viteVueDevTools from 'vite-plugin-vue-devtools'; + +import { viteArchiverPlugin } from './archiver'; +import { viteExtraAppConfigPlugin } from './extra-app-config'; +import { viteImportMapPlugin } from './importmap'; +import { viteInjectAppLoadingPlugin } from './inject-app-loading'; +import { viteMetadataPlugin } from './inject-metadata'; +import { viteLicensePlugin } from './license'; +import { viteNitroMockPlugin } from './nitro-mock'; +import { vitePrintPlugin } from './print'; +import { viteVxeTableImportsPlugin } from './vxe-table'; + +/** + * 获取条件成立的 vite 插件 + * @param conditionPlugins + */ +async function loadConditionPlugins(conditionPlugins: ConditionPlugin[]) { + const plugins: PluginOption[] = []; + for (const conditionPlugin of conditionPlugins) { + if (conditionPlugin.condition) { + const realPlugins = await conditionPlugin.plugins(); + plugins.push(...realPlugins); + } + } + return plugins.flat(); +} + +/** + * 根据条件获取通用的vite插件 + */ +async function loadCommonPlugins( + options: CommonPluginOptions, +): Promise { + const { devtools, injectMetadata, isBuild, visualizer } = options; + return [ + { + condition: true, + plugins: () => [ + viteVue({ + script: { + defineModel: true, + // propsDestructure: true, + }, + }), + viteVueJsx(), + ], + }, + + { + condition: !isBuild && devtools, + plugins: () => [viteVueDevTools()], + }, + { + condition: injectMetadata, + plugins: async () => [await viteMetadataPlugin()], + }, + { + condition: isBuild && !!visualizer, + plugins: () => [viteVisualizerPlugin({ + filename: './node_modules/.cache/visualizer/stats.html', + gzipSize: true, + open: true, + })], + }, + ]; +} + +/** + * 根据条件获取应用类型的vite插件 + */ +async function loadApplicationPlugins( + options: ApplicationPluginOptions, +): Promise { + // 单独取,否则commonOptions拿不到 + const isBuild = options.isBuild; + const env = options.env; + + const { + archiver, + archiverPluginOptions, + compress, + compressTypes, + extraAppConfig, + html, + i18n, + importmap, + importmapOptions, + injectAppLoading, + license, + nitroMock, + nitroMockOptions, + print, + printInfoMap, + pwa, + pwaOptions, + vxeTableLazyImport, + ...commonOptions + } = options; + + const commonPlugins = await loadCommonPlugins(commonOptions); + + return await loadConditionPlugins([ + ...commonPlugins, + { + condition: i18n, + plugins: async () => { + return [ + viteVueI18nPlugin({ + compositionOnly: true, + fullInstall: true, + runtimeOnly: true, + }), + ]; + }, + }, + { + condition: print, + plugins: async () => { + return [await vitePrintPlugin({ infoMap: printInfoMap })]; + }, + }, + { + condition: vxeTableLazyImport, + plugins: async () => { + return [await viteVxeTableImportsPlugin()]; + }, + }, + { + condition: nitroMock, + plugins: async () => { + return [await viteNitroMockPlugin(nitroMockOptions)]; + }, + }, + + { + condition: injectAppLoading, + plugins: async () => [await viteInjectAppLoadingPlugin(!!isBuild, env)], + }, + { + condition: license, + plugins: async () => [await viteLicensePlugin()], + }, + { + condition: pwa, + plugins: () => + VitePWA({ + injectRegister: false, + workbox: { + globPatterns: [], + }, + ...pwaOptions, + manifest: { + display: 'standalone', + start_url: '/', + theme_color: '#ffffff', + ...pwaOptions?.manifest, + }, + }), + }, + { + condition: isBuild && !!compress, + plugins: () => { + const compressPlugins: PluginOption[] = []; + if (compressTypes?.includes('brotli')) { + compressPlugins.push( + viteCompressPlugin({ deleteOriginFile: false, ext: '.br' }), + ); + } + if (compressTypes?.includes('gzip')) { + compressPlugins.push( + viteCompressPlugin({ deleteOriginFile: false, ext: '.gz' }), + ); + } + return compressPlugins; + }, + }, + { + condition: !!html, + plugins: () => [viteHtmlPlugin({ minify: true })], + }, + { + condition: isBuild && importmap, + plugins: () => { + return [viteImportMapPlugin(importmapOptions)]; + }, + }, + { + condition: isBuild && extraAppConfig, + plugins: async () => [ + await viteExtraAppConfigPlugin({ isBuild: true, root: process.cwd() }), + ], + }, + { + condition: archiver, + plugins: async () => { + return [await viteArchiverPlugin(archiverPluginOptions)]; + }, + }, + ]); +} + +/** + * 根据条件获取库类型的vite插件 + */ +async function loadLibraryPlugins( + options: LibraryPluginOptions, +): Promise { + // 单独取,否则commonOptions拿不到 + const isBuild = options.isBuild; + const { dts, ...commonOptions } = options; + const commonPlugins = await loadCommonPlugins(commonOptions); + return await loadConditionPlugins([ + ...commonPlugins, + { + condition: isBuild && !!dts, + plugins: () => [viteDtsPlugin({ logLevel: 'error' })], + }, + ]); +} + +export { + loadApplicationPlugins, + loadLibraryPlugins, + viteArchiverPlugin, + viteCompressPlugin, + viteDtsPlugin, + viteHtmlPlugin, + viteVisualizerPlugin, + viteVxeTableImportsPlugin, +}; diff --git a/internal/vite-config/src/plugins/inject-app-loading/README.md b/internal/vite-config/src/plugins/inject-app-loading/README.md new file mode 100644 index 0000000..8d2358f --- /dev/null +++ b/internal/vite-config/src/plugins/inject-app-loading/README.md @@ -0,0 +1,3 @@ +# inject-app-loading + +用于在应用加载时显示加载动画的插件,可自行选择加载动画的样式。 diff --git a/internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html b/internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html new file mode 100644 index 0000000..20a21fb --- /dev/null +++ b/internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html @@ -0,0 +1,107 @@ + +
+ +
<%= VITE_APP_TITLE %>
+
diff --git a/internal/vite-config/src/plugins/inject-app-loading/default-loading.html b/internal/vite-config/src/plugins/inject-app-loading/default-loading.html new file mode 100644 index 0000000..2895705 --- /dev/null +++ b/internal/vite-config/src/plugins/inject-app-loading/default-loading.html @@ -0,0 +1,113 @@ + +
+
+
<%= VITE_APP_TITLE %>
+
diff --git a/internal/vite-config/src/plugins/inject-app-loading/index.ts b/internal/vite-config/src/plugins/inject-app-loading/index.ts new file mode 100644 index 0000000..6880163 --- /dev/null +++ b/internal/vite-config/src/plugins/inject-app-loading/index.ts @@ -0,0 +1,66 @@ +import type { PluginOption } from 'vite'; + +import fs from 'node:fs'; +import fsp from 'node:fs/promises'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { readPackageJSON } from '@aiflowy/node-utils'; + +/** + * 用于生成将loading样式注入到项目中 + * 为多app提供loading样式,无需在每个 app -> index.html单独引入 + */ +async function viteInjectAppLoadingPlugin( + isBuild: boolean, + env: Record = {}, + loadingTemplate = 'loading.html', +): Promise { + const loadingHtml = await getLoadingRawByHtmlTemplate(loadingTemplate); + const { version } = await readPackageJSON(process.cwd()); + const envRaw = isBuild ? 'prod' : 'dev'; + const cacheName = `'${env.VITE_APP_NAMESPACE}-${version}-${envRaw}-preferences-theme'`; + + // 获取缓存的主题 + // 保证黑暗主题下,刷新页面时,loading也是黑暗主题 + const injectScript = ` + +`; + + if (!loadingHtml) { + return; + } + + return { + enforce: 'pre', + name: 'vite:inject-app-loading', + transformIndexHtml: { + handler(html) { + const re = //; + html = html.replace(re, `${injectScript}${loadingHtml}`); + return html; + }, + order: 'pre', + }, + }; +} + +/** + * 用于获取loading的html模板 + */ +async function getLoadingRawByHtmlTemplate(loadingTemplate: string) { + // 支持在app内自定义loading模板,模版参考default-loading.html即可 + let appLoadingPath = join(process.cwd(), loadingTemplate); + + if (!fs.existsSync(appLoadingPath)) { + const __dirname = fileURLToPath(new URL('.', import.meta.url)); + appLoadingPath = join(__dirname, './default-loading.html'); + } + + return await fsp.readFile(appLoadingPath, 'utf8'); +} + +export { viteInjectAppLoadingPlugin }; diff --git a/internal/vite-config/src/plugins/inject-metadata.ts b/internal/vite-config/src/plugins/inject-metadata.ts new file mode 100644 index 0000000..c9c4da3 --- /dev/null +++ b/internal/vite-config/src/plugins/inject-metadata.ts @@ -0,0 +1,111 @@ +import type { PluginOption } from 'vite'; + +import { + dateUtil, + findMonorepoRoot, + getPackages, + readPackageJSON, +} from '@aiflowy/node-utils'; + +import { readWorkspaceManifest } from '@pnpm/workspace.read-manifest'; + +function resolvePackageVersion( + pkgsMeta: Record, + name: string, + value: string, + catalog: Record, +) { + if (value.includes('catalog:')) { + return catalog[name]; + } + + if (value.includes('workspace')) { + return pkgsMeta[name]; + } + + return value; +} + +async function resolveMonorepoDependencies() { + const { packages } = await getPackages(); + const manifest = await readWorkspaceManifest(findMonorepoRoot()); + const catalog = manifest?.catalog || {}; + + const resultDevDependencies: Record = {}; + const resultDependencies: Record = {}; + const pkgsMeta: Record = {}; + + for (const { packageJson } of packages) { + pkgsMeta[packageJson.name] = packageJson.version; + } + + for (const { packageJson } of packages) { + const { dependencies = {}, devDependencies = {} } = packageJson; + for (const [key, value] of Object.entries(dependencies)) { + resultDependencies[key] = resolvePackageVersion( + pkgsMeta, + key, + value, + catalog, + ); + } + for (const [key, value] of Object.entries(devDependencies)) { + resultDevDependencies[key] = resolvePackageVersion( + pkgsMeta, + key, + value, + catalog, + ); + } + } + return { + dependencies: resultDependencies, + devDependencies: resultDevDependencies, + }; +} + +/** + * 用于注入项目信息 + */ +async function viteMetadataPlugin( + root = process.cwd(), +): Promise { + const { author, description, homepage, license, version } = + await readPackageJSON(root); + + const buildTime = dateUtil().format('YYYY-MM-DD HH:mm:ss'); + + return { + async config() { + const { dependencies, devDependencies } = + await resolveMonorepoDependencies(); + + const isAuthorObject = typeof author === 'object'; + const authorName = isAuthorObject ? author.name : author; + const authorEmail = isAuthorObject ? author.email : null; + const authorUrl = isAuthorObject ? author.url : null; + + return { + define: { + __APP_ADMIN_METADATA__: JSON.stringify({ + authorEmail, + authorName, + authorUrl, + buildTime, + dependencies, + description, + devDependencies, + homepage, + license, + version, + }), + 'import.meta.env.VITE_APP_VERSION': JSON.stringify(version), + }, + }; + }, + enforce: 'post', + name: 'vite:inject-metadata', + }; +} + +export { viteMetadataPlugin }; diff --git a/internal/vite-config/src/plugins/license.ts b/internal/vite-config/src/plugins/license.ts new file mode 100644 index 0000000..683f366 --- /dev/null +++ b/internal/vite-config/src/plugins/license.ts @@ -0,0 +1,63 @@ +import type { + NormalizedOutputOptions, + OutputBundle, + OutputChunk, +} from 'rollup'; +import type { PluginOption } from 'vite'; + +import { EOL } from 'node:os'; + +import { dateUtil, readPackageJSON } from '@aiflowy/node-utils'; + +/** + * 用于注入版权信息 + * @returns + */ + +async function viteLicensePlugin( + root = process.cwd(), +): Promise { + const { + description = '', + homepage = '', + version = '', + } = await readPackageJSON(root); + + return { + apply: 'build', + enforce: 'post', + generateBundle: { + handler: (_options: NormalizedOutputOptions, bundle: OutputBundle) => { + const date = dateUtil().format('YYYY-MM-DD '); + const copyrightText = `/*! + * AIFlowy Admin + * Version: ${version} + * Author: aiflowy + * Copyright (C) 2026 AIFlowy + * License: MIT License + * Description: ${description} + * Date Created: ${date} + * Homepage: ${homepage} + * Contact: fuhai999@gmail.com +*/ + `.trim(); + + for (const [, fileContent] of Object.entries(bundle)) { + if (fileContent.type === 'chunk' && fileContent.isEntry) { + const chunkContent = fileContent as OutputChunk; + // 插入版权信息 + const content = chunkContent.code; + const updatedContent = `${copyrightText}${EOL}${content}`; + + // 更新bundle + (fileContent as OutputChunk).code = updatedContent; + } + } + }, + order: 'post', + }, + name: 'vite:license', + }; +} + +export { viteLicensePlugin }; diff --git a/internal/vite-config/src/plugins/nitro-mock.ts b/internal/vite-config/src/plugins/nitro-mock.ts new file mode 100644 index 0000000..c04b7d4 --- /dev/null +++ b/internal/vite-config/src/plugins/nitro-mock.ts @@ -0,0 +1,98 @@ +import type { PluginOption } from 'vite'; + +import type { NitroMockPluginOptions } from '../typing'; + +import { colors, consola, getPackage } from '@aiflowy/node-utils'; + +import getPort from 'get-port'; +import { build, createDevServer, createNitro, prepare } from 'nitropack'; + +const hmrKeyRe = /^runtimeConfig\.|routeRules\./; + +export const viteNitroMockPlugin = ({ + mockServerPackage = '@aiflowy/backend-mock', + port = 5320, + verbose = true, +}: NitroMockPluginOptions = {}): PluginOption => { + return { + async configureServer(server) { + const availablePort = await getPort({ port }); + if (availablePort !== port) { + return; + } + + const pkg = await getPackage(mockServerPackage); + if (!pkg) { + consola.log( + `Package ${mockServerPackage} not found. Skip mock server.`, + ); + return; + } + + runNitroServer(pkg.dir, port, verbose); + + const _printUrls = server.printUrls; + server.printUrls = () => { + _printUrls(); + + consola.log( + ` ${colors.green('➜')} ${colors.bold('Nitro Mock Server')}: ${colors.cyan(`http://localhost:${port}/api`)}`, + ); + }; + }, + enforce: 'pre', + name: 'vite:mock-server', + }; +}; + +async function runNitroServer(rootDir: string, port: number, verbose: boolean) { + let nitro: any; + const reload = async () => { + if (nitro) { + consola.info('Restarting dev server...'); + if ('unwatch' in nitro.options._c12) { + await nitro.options._c12.unwatch(); + } + await nitro.close(); + } + nitro = await createNitro( + { + dev: true, + preset: 'nitro-dev', + rootDir, + }, + { + c12: { + async onUpdate({ getDiff, newConfig }) { + const diff = getDiff(); + if (diff.length === 0) { + return; + } + verbose && + consola.info( + `Nitro config updated:\n${diff + .map((entry) => ` ${entry.toString()}`) + .join('\n')}`, + ); + await (diff.every((e) => hmrKeyRe.test(e.key)) + ? nitro.updateConfig(newConfig.config) + : reload()); + }, + }, + watch: true, + }, + ); + nitro.hooks.hookOnce('restart', reload); + + const server = createDevServer(nitro); + await server.listen(port, { showURL: false }); + await prepare(nitro); + await build(nitro); + + if (verbose) { + console.log(''); + consola.success(colors.bold(colors.green('Nitro Mock Server started.'))); + } + }; + return await reload(); +} diff --git a/internal/vite-config/src/plugins/print.ts b/internal/vite-config/src/plugins/print.ts new file mode 100644 index 0000000..dc96424 --- /dev/null +++ b/internal/vite-config/src/plugins/print.ts @@ -0,0 +1,28 @@ +import type { PluginOption } from 'vite'; + +import type { PrintPluginOptions } from '../typing'; + +import { colors } from '@aiflowy/node-utils'; + +export const vitePrintPlugin = ( + options: PrintPluginOptions = {}, +): PluginOption => { + const { infoMap = {} } = options; + + return { + configureServer(server) { + const _printUrls = server.printUrls; + server.printUrls = () => { + _printUrls(); + + for (const [key, value] of Object.entries(infoMap)) { + console.log( + ` ${colors.green('➜')} ${colors.bold(key)}: ${colors.cyan(value)}`, + ); + } + }; + }, + enforce: 'pre', + name: 'vite:print-info', + }; +}; diff --git a/internal/vite-config/src/plugins/vxe-table.ts b/internal/vite-config/src/plugins/vxe-table.ts new file mode 100644 index 0000000..3c107a7 --- /dev/null +++ b/internal/vite-config/src/plugins/vxe-table.ts @@ -0,0 +1,20 @@ +import type { PluginOption } from 'vite'; + +import { lazyImport, VxeResolver } from 'vite-plugin-lazy-import'; + +async function viteVxeTableImportsPlugin(): Promise { + return [ + lazyImport({ + resolvers: [ + VxeResolver({ + libraryName: 'vxe-table', + }), + VxeResolver({ + libraryName: 'vxe-pc-ui', + }), + ], + }), + ]; +} + +export { viteVxeTableImportsPlugin }; diff --git a/internal/vite-config/src/typing.ts b/internal/vite-config/src/typing.ts new file mode 100644 index 0000000..5c6a5c0 --- /dev/null +++ b/internal/vite-config/src/typing.ts @@ -0,0 +1,343 @@ +import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer'; +import type { ConfigEnv, PluginOption, UserConfig } from 'vite'; +import type { PluginOptions } from 'vite-plugin-dts'; +import type { Options as PwaPluginOptions } from 'vite-plugin-pwa'; + +/** + * ImportMap 配置接口 + * @description 用于配置模块导入映射,支持自定义导入路径和范围 + * @example + * ```typescript + * { + * imports: { + * 'vue': 'https://unpkg.com/vue@3.2.47/dist/vue.esm-browser.js' + * }, + * scopes: { + * 'https://site.com/': { + * 'vue': 'https://unpkg.com/vue@3.2.47/dist/vue.esm-browser.js' + * } + * } + * } + * ``` + */ +interface IImportMap { + /** 模块导入映射 */ + imports?: Record; + /** 作用域特定的导入映射 */ + scopes?: { + [scope: string]: Record; + }; +} + +/** + * 打印插件配置选项 + * @description 用于配置控制台打印信息 + */ +interface PrintPluginOptions { + /** + * 打印的数据映射 + * @description 键值对形式的数据,将在控制台打印 + * @example + * ```typescript + * { + * 'App Version': '1.0.0', + * 'Build Time': '2024-01-01' + * } + * ``` + */ + infoMap?: Record; +} + +/** + * Nitro Mock 插件配置选项 + * @description 用于配置 Nitro Mock 服务器的行为 + */ +interface NitroMockPluginOptions { + /** + * Mock 服务器包名 + * @default '@aiflowy/nitro-mock' + */ + mockServerPackage?: string; + + /** + * Mock 服务端口 + * @default 3000 + */ + port?: number; + + /** + * 是否打印 Mock 日志 + * @default false + */ + verbose?: boolean; +} + +/** + * 归档插件配置选项 + * @description 用于配置构建产物的压缩归档 + */ +interface ArchiverPluginOptions { + /** + * 输出文件名 + * @default 'dist' + */ + name?: string; + /** + * 输出目录 + * @default '.' + */ + outputDir?: string; +} + +/** + * ImportMap 插件配置 + * @description 用于配置模块的 CDN 导入 + */ +interface ImportmapPluginOptions { + /** + * CDN 供应商 + * @default 'jspm.io' + * @description 支持 esm.sh 和 jspm.io 两种 CDN 供应商 + */ + defaultProvider?: 'esm.sh' | 'jspm.io'; + /** + * ImportMap 配置数组 + * @description 配置需要从 CDN 导入的包 + * @example + * ```typescript + * [ + * { name: 'vue' }, + * { name: 'pinia', range: '^2.0.0' } + * ] + * ``` + */ + importmap?: Array<{ name: string; range?: string }>; + /** + * 手动配置 ImportMap + * @description 自定义 ImportMap 配置 + */ + inputMap?: IImportMap; +} + +/** + * 条件插件配置 + * @description 用于根据条件动态加载插件 + */ +interface ConditionPlugin { + /** + * 判断条件 + * @description 当条件为 true 时加载插件 + */ + condition?: boolean; + /** + * 插件对象 + * @description 返回插件数组或 Promise + */ + plugins: () => PluginOption[] | PromiseLike; +} + +/** + * 通用插件配置选项 + * @description 所有插件共用的基础配置 + */ +interface CommonPluginOptions { + /** + * 是否开启开发工具 + * @default false + */ + devtools?: boolean; + /** + * 环境变量 + * @description 自定义环境变量 + */ + env?: Record; + /** + * 是否注入元数据 + * @default true + */ + injectMetadata?: boolean; + /** + * 是否为构建模式 + * @default false + */ + isBuild?: boolean; + /** + * 构建模式 + * @default 'development' + */ + mode?: string; + /** + * 是否开启依赖分析 + * @default false + * @description 使用 rollup-plugin-visualizer 分析依赖 + */ + visualizer?: boolean | PluginVisualizerOptions; +} + +/** + * 应用插件配置选项 + * @description 用于配置应用构建时的插件选项 + */ +interface ApplicationPluginOptions extends CommonPluginOptions { + /** + * 是否开启压缩归档 + * @default false + * @description 开启后会在打包目录生成 zip 文件 + */ + archiver?: boolean; + /** + * 压缩归档插件配置 + * @description 配置压缩归档的行为 + */ + archiverPluginOptions?: ArchiverPluginOptions; + /** + * 是否开启压缩 + * @default false + * @description 支持 gzip 和 brotli 压缩 + */ + compress?: boolean; + /** + * 压缩类型 + * @default ['gzip'] + * @description 可选的压缩类型 + */ + compressTypes?: ('brotli' | 'gzip')[]; + /** + * 是否抽离配置文件 + * @default false + * @description 在构建时抽离配置文件 + */ + extraAppConfig?: boolean; + /** + * 是否开启 HTML 插件 + * @default true + */ + html?: boolean; + /** + * 是否开启国际化 + * @default false + */ + i18n?: boolean; + /** + * 是否开启 ImportMap CDN + * @default false + */ + importmap?: boolean; + /** + * ImportMap 插件配置 + */ + importmapOptions?: ImportmapPluginOptions; + /** + * 是否注入应用加载动画 + * @default true + */ + injectAppLoading?: boolean; + /** + * 是否注入全局 SCSS + * @default true + */ + injectGlobalScss?: boolean; + /** + * 是否注入版权信息 + * @default true + */ + license?: boolean; + /** + * 是否开启 Nitro Mock + * @default false + */ + nitroMock?: boolean; + /** + * Nitro Mock 插件配置 + */ + nitroMockOptions?: NitroMockPluginOptions; + /** + * 是否开启控制台打印 + * @default false + */ + print?: boolean; + /** + * 打印插件配置 + */ + printInfoMap?: PrintPluginOptions['infoMap']; + /** + * 是否开启 PWA + * @default false + */ + pwa?: boolean; + /** + * PWA 插件配置 + */ + pwaOptions?: Partial; + /** + * 是否开启 VXE Table 懒加载 + * @default false + */ + vxeTableLazyImport?: boolean; +} + +/** + * 库插件配置选项 + * @description 用于配置库构建时的插件选项 + */ +interface LibraryPluginOptions extends CommonPluginOptions { + /** + * 是否开启 DTS 输出 + * @default true + * @description 生成 TypeScript 类型声明文件 + */ + dts?: boolean | PluginOptions; +} + +/** + * 应用配置选项类型 + */ +type ApplicationOptions = ApplicationPluginOptions; + +/** + * 库配置选项类型 + */ +type LibraryOptions = LibraryPluginOptions; + +/** + * 应用配置定义函数类型 + * @description 用于定义应用构建配置 + */ +type DefineApplicationOptions = (config?: ConfigEnv) => Promise<{ + /** 应用插件配置 */ + application?: ApplicationOptions; + /** Vite 配置 */ + vite?: UserConfig; +}>; + +/** + * 库配置定义函数类型 + * @description 用于定义库构建配置 + */ +type DefineLibraryOptions = (config?: ConfigEnv) => Promise<{ + /** 库插件配置 */ + library?: LibraryOptions; + /** Vite 配置 */ + vite?: UserConfig; +}>; + +/** + * 配置定义类型 + * @description 应用或库的配置定义 + */ +type DefineConfig = DefineApplicationOptions | DefineLibraryOptions; + +export type { + ApplicationPluginOptions, + ArchiverPluginOptions, + CommonPluginOptions, + ConditionPlugin, + DefineApplicationOptions, + DefineConfig, + DefineLibraryOptions, + IImportMap, + ImportmapPluginOptions, + LibraryPluginOptions, + NitroMockPluginOptions, + PrintPluginOptions, +}; diff --git a/internal/vite-config/src/utils/env.ts b/internal/vite-config/src/utils/env.ts new file mode 100644 index 0000000..b7b4baf --- /dev/null +++ b/internal/vite-config/src/utils/env.ts @@ -0,0 +1,110 @@ +import type { ApplicationPluginOptions } from '../typing'; + +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; + +import { fs } from '@aiflowy/node-utils'; + +import dotenv from 'dotenv'; + +const getBoolean = (value: string | undefined) => value === 'true'; + +const getString = (value: string | undefined, fallback: string) => + value ?? fallback; + +const getNumber = (value: string | undefined, fallback: number) => + Number(value) || fallback; + +/** + * 获取当前环境下生效的配置文件名 + */ +function getConfFiles() { + const script = process.env.npm_lifecycle_script as string; + const reg = /--mode ([\d_a-z]+)/; + const result = reg.exec(script); + let mode = 'production'; + if (result) { + mode = result[1] as string; + } + return ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`]; +} + +/** + * Get the environment variables starting with the specified prefix + * @param match prefix + * @param confFiles ext + */ +async function loadEnv>( + match = 'VITE_GLOB_', + confFiles = getConfFiles(), +) { + let envConfig = {}; + + for (const confFile of confFiles) { + try { + const confFilePath = join(process.cwd(), confFile); + if (existsSync(confFilePath)) { + const envPath = await fs.readFile(confFilePath, { + encoding: 'utf8', + }); + const env = dotenv.parse(envPath); + envConfig = { ...envConfig, ...env }; + } + } catch (error) { + console.error(`Error while parsing ${confFile}`, error); + } + } + const reg = new RegExp(`^(${match})`); + Object.keys(envConfig).forEach((key) => { + if (!reg.test(key)) { + Reflect.deleteProperty(envConfig, key); + } + }); + return envConfig as T; +} + +async function loadAndConvertEnv( + match = 'VITE_', + confFiles = getConfFiles(), +): Promise< + Partial & { + appTitle: string; + base: string; + port: number; + } +> { + const envConfig = await loadEnv(match, confFiles); + + const { + VITE_APP_TITLE, + VITE_ARCHIVER, + VITE_BASE, + VITE_COMPRESS, + VITE_DEVTOOLS, + VITE_INJECT_APP_LOADING, + VITE_NITRO_MOCK, + VITE_PORT, + VITE_PWA, + VITE_VISUALIZER, + } = envConfig; + + const compressTypes = (VITE_COMPRESS ?? '') + .split(',') + .filter((item) => item === 'brotli' || item === 'gzip'); + + return { + appTitle: getString(VITE_APP_TITLE, 'AIFlowy Admin'), + archiver: getBoolean(VITE_ARCHIVER), + base: getString(VITE_BASE, '/'), + compress: compressTypes.length > 0, + compressTypes, + devtools: getBoolean(VITE_DEVTOOLS), + injectAppLoading: getBoolean(VITE_INJECT_APP_LOADING), + nitroMock: getBoolean(VITE_NITRO_MOCK), + port: getNumber(VITE_PORT, 5173), + pwa: getBoolean(VITE_PWA), + visualizer: getBoolean(VITE_VISUALIZER), + }; +} + +export { loadAndConvertEnv, loadEnv }; diff --git a/internal/vite-config/tsconfig.json b/internal/vite-config/tsconfig.json new file mode 100644 index 0000000..c12c7ab --- /dev/null +++ b/internal/vite-config/tsconfig.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@aiflowy/tsconfig/node.json", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..7280174 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,76 @@ +# EXAMPLE USAGE: +# +# Refer for explanation to following link: +# https://lefthook.dev/configuration/ +# +# pre-push: +# jobs: +# - name: packages audit +# tags: +# - frontend +# - security +# run: yarn audit +# +# - name: gems audit +# tags: +# - backend +# - security +# run: bundle audit +# +# pre-commit: +# parallel: true +# jobs: +# - run: yarn eslint {staged_files} +# glob: "*.{js,ts,jsx,tsx}" +# +# - name: rubocop +# glob: "*.rb" +# exclude: +# - config/application.rb +# - config/routes.rb +# run: bundle exec rubocop --force-exclusion {all_files} +# +# - name: govet +# files: git ls-files -m +# glob: "*.go" +# run: go vet {files} +# +# - script: "hello.js" +# runner: node +# +# - script: "hello.go" +# runner: go run + +pre-commit: + parallel: true + commands: + code-workspace: + run: pnpm vsh code-workspace --auto-commit + lint-md: + run: pnpm prettier --cache --ignore-unknown --write {staged_files} + glob: '*.md' + lint-vue: + run: pnpm prettier --write {staged_files} && pnpm eslint --cache --fix {staged_files} && pnpm stylelint --fix --allow-empty-input {staged_files} + glob: '*.vue' + lint-js: + run: pnpm prettier --cache --ignore-unknown --write {staged_files} && pnpm eslint --cache --fix {staged_files} + glob: '*.{js,jsx,ts,tsx}' + lint-style: + run: pnpm prettier --cache --ignore-unknown --write {staged_files} && pnpm stylelint --fix --allow-empty-input {staged_files} + glob: '*.{scss,less,styl,html,vue,css}' + lint-package: + run: pnpm prettier --cache --write {staged_files} + glob: 'package.json' + lint-json: + run: pnpm prettier --cache --write --parser json {staged_files} + glob: '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}' + +post-merge: + commands: + install: + run: pnpm install + +commit-msg: + commands: + commitlint: + run: pnpm exec commitlint --edit $1 diff --git a/package.json b/package.json new file mode 100644 index 0000000..32c5943 --- /dev/null +++ b/package.json @@ -0,0 +1,96 @@ +{ + "name": "aiflowy-admin", + "version": "1.0.0", + "private": true, + "homepage": "https://github.com/aiflowy/aiflowy", + "bugs": "https://github.com/aiflowy/aiflowy/issues", + "repository": "aiflowy/aiflowy.git", + "license": "MIT", + "type": "module", + "scripts": { + "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build", + "build:analyze": "turbo build:analyze", + "build:docker": "./scripts/deploy/build-local-docker-image.sh", + "build:app": "pnpm run build --filter=@aiflowy/app", + "changeset": "pnpm exec changeset", + "check": "pnpm run check:circular && pnpm run check:dep && pnpm run check:type && pnpm check:cspell", + "check:circular": "vsh check-circular", + "check:cspell": "cspell lint **/*.ts **/README.md .changeset/*.md --no-progress", + "check:dep": "vsh check-dep", + "check:type": "turbo run typecheck", + "clean": "node ./scripts/clean.mjs", + "commit": "czg", + "dev": "turbo-run dev", + "dev:app": "pnpm -F @aiflowy/app run dev", + "format": "vsh lint --format", + "lint": "vsh lint", + "postinstall": "pnpm -r run stub --if-present", + "preinstall": "npx only-allow pnpm", + "preview": "turbo-run preview", + "publint": "vsh publint", + "reinstall": "pnpm clean --del-lock && pnpm install", + "test:unit": "vitest run --dom", + "test:e2e": "turbo run test:e2e", + "update:deps": "npx taze -r -w", + "version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile", + "catalog": "pnpx codemod pnpm/catalog" + }, + "devDependencies": { + "@aiflowy/commitlint-config": "workspace:*", + "@aiflowy/eslint-config": "workspace:*", + "@aiflowy/prettier-config": "workspace:*", + "@aiflowy/stylelint-config": "workspace:*", + "@aiflowy/tailwind-config": "workspace:*", + "@aiflowy/tsconfig": "workspace:*", + "@aiflowy/turbo-run": "workspace:*", + "@aiflowy/vite-config": "workspace:*", + "@aiflowy/vsh": "workspace:*", + "@changesets/changelog-github": "catalog:", + "@changesets/cli": "catalog:", + "@playwright/test": "catalog:", + "@types/node": "catalog:", + "@vitejs/plugin-vue": "catalog:", + "@vitejs/plugin-vue-jsx": "catalog:", + "@vue/test-utils": "catalog:", + "autoprefixer": "catalog:", + "cross-env": "catalog:", + "cspell": "catalog:", + "happy-dom": "catalog:", + "is-ci": "catalog:", + "lefthook": "catalog:", + "playwright": "catalog:", + "rimraf": "catalog:", + "tailwindcss": "catalog:", + "turbo": "catalog:", + "typescript": "catalog:", + "unbuild": "catalog:", + "vite": "catalog:", + "vitest": "catalog:", + "vue": "^3.5.24", + "vue-tsc": "catalog:" + }, + "engines": { + "node": ">=20.10.0", + "pnpm": ">=9.12.0" + }, + "packageManager": "pnpm@10.14.0", + "pnpm": { + "peerDependencyRules": { + "allowedVersions": { + "eslint": "*" + } + }, + "overrides": { + "@ast-grep/napi": "catalog:", + "@ctrl/tinycolor": "catalog:", + "clsx": "catalog:", + "esbuild": "0.25.3", + "pinia": "catalog:", + "vue": "catalog:" + }, + "neverBuiltDependencies": [ + "canvas", + "node-gyp" + ] + } +} diff --git a/packages/@core/README.md b/packages/@core/README.md new file mode 100644 index 0000000..b4a1f00 --- /dev/null +++ b/packages/@core/README.md @@ -0,0 +1,3 @@ +# @aiflowy-core + +系统一些比较基础的SDK和UI组件库,该目录后续完善后,可能会迁移出去或者发布到npm,请勿将任何业务逻辑和业务包放在该目录。 diff --git a/packages/@core/base/README.md b/packages/@core/base/README.md new file mode 100644 index 0000000..cc745b4 --- /dev/null +++ b/packages/@core/base/README.md @@ -0,0 +1,5 @@ +# base + +基础共享包,请勿引入 workspace 依赖 + +- diff --git a/packages/@core/base/design/package.json b/packages/@core/base/design/package.json new file mode 100644 index 0000000..7350798 --- /dev/null +++ b/packages/@core/base/design/package.json @@ -0,0 +1,41 @@ +{ + "name": "@aiflowy-core/design", + "version": "1.0.0", + "homepage": "https://github.com/aiflowy/aiflowy", + "bugs": "https://github.com/aiflowy/aiflowy/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/aiflowy/aiflowy.git", + "directory": "packages/@aiflowy-core/base/design" + }, + "license": "MIT", + "type": "module", + "scripts": { + "build": "pnpm vite build", + "prepublishOnly": "npm run build" + }, + "files": [ + "dist", + "src" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "exports": { + "./bem": { + "development": "./src/scss-bem/bem.scss", + "default": "./dist/bem.scss" + }, + ".": { + "types": "./src/index.ts", + "development": "./src/index.ts", + "default": "./dist/design.css" + } + }, + "publishConfig": { + "exports": { + ".": { + "default": "./dist/index.mjs" + } + } + } +} diff --git a/packages/@core/base/design/src/css/global.css b/packages/@core/base/design/src/css/global.css new file mode 100644 index 0000000..9734cf2 --- /dev/null +++ b/packages/@core/base/design/src/css/global.css @@ -0,0 +1,170 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + *, + ::after, + ::before { + @apply border-border; + + box-sizing: border-box; + border-style: solid; + border-width: 0; + } + + html { + @apply text-foreground bg-background font-sans text-[100%]; + + font-variation-settings: normal; + line-height: 1.15; + text-size-adjust: 100%; + font-synthesis-weight: none; + scroll-behavior: smooth; + text-rendering: optimizelegibility; + -webkit-tap-highlight-color: transparent; + + /* -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; */ + } + + #app, + body, + html { + @apply size-full; + + /* scrollbar-gutter: stable; */ + } + + body { + min-height: 100vh; + + /* pointer-events: auto !important; */ + + /* overflow: overlay; */ + + /* -webkit-font-smoothing: antialiased; */ + + /* -moz-osx-font-smoothing: grayscale; */ + } + + a, + a:active, + a:hover, + a:link, + a:visited { + @apply no-underline; + } + + ::view-transition-new(root), + ::view-transition-old(root) { + @apply animate-none mix-blend-normal; + } + + ::view-transition-old(root) { + @apply z-[1]; + } + + ::view-transition-new(root) { + @apply z-[2147483646]; + } + + html.dark::view-transition-old(root) { + @apply z-[2147483646]; + } + + html.dark::view-transition-new(root) { + @apply z-[1]; + } + + input::placeholder, + textarea::placeholder { + @apply opacity-100; + } + + /* input:-webkit-autofill { + @apply border-none; + + box-shadow: 0 0 0 1000px transparent inset; + } */ + + input[type='number']::-webkit-inner-spin-button, + input[type='number']::-webkit-outer-spin-button { + @apply m-0 appearance-none; + } + + /* 只有非mac下才进行调整,mac下使用默认滚动条 */ + html:not([data-platform='macOs']) { + ::-webkit-scrollbar { + @apply h-[10px] w-[10px]; + } + + ::-webkit-scrollbar-thumb { + @apply bg-border rounded-sm border-none; + } + + ::-webkit-scrollbar-track { + @apply rounded-sm border-none bg-transparent shadow-none; + } + + ::-webkit-scrollbar-button { + @apply hidden; + } + } +} + +@layer components { + .flex-center { + @apply flex items-center justify-center; + } + + .flex-col-center { + @apply flex flex-col items-center justify-center; + } + + .outline-box { + @apply outline-border relative cursor-pointer rounded-md p-1 outline outline-1; + } + + .outline-box::after { + @apply absolute left-1/2 top-1/2 z-20 h-0 w-[1px] rounded-sm opacity-0 outline outline-2 outline-transparent transition-all duration-300 content-[""]; + } + + .outline-box.outline-box-active { + @apply outline-primary outline outline-2; + } + + .outline-box.outline-box-active::after { + display: none; + } + + .outline-box:not(.outline-box-active):hover::after { + @apply outline-primary left-0 top-0 h-full w-full p-1 opacity-100; + } + + .aiflowy-link { + @apply text-primary hover:text-primary-hover active:text-primary-active cursor-pointer; + } + + .card-box { + @apply bg-card text-card-foreground border-border rounded-xl border; + } +} + +html.invert-mode { + @apply invert; +} + +html.grayscale-mode { + @apply grayscale; +} + +.page-container { + margin: 20px; + background-color: var(--el-bg-color); + border-radius: var(--el-border-radius-base); + padding: 20px; +} +.handle-div { + margin-bottom: 10px; +} diff --git a/packages/@core/base/design/src/css/nprogress.css b/packages/@core/base/design/src/css/nprogress.css new file mode 100644 index 0000000..3503dab --- /dev/null +++ b/packages/@core/base/design/src/css/nprogress.css @@ -0,0 +1,59 @@ +/* Make clicks pass-through */ +#nprogress { + @apply pointer-events-none; +} + +#nprogress .bar { + @apply bg-primary fixed left-0 top-0 z-[1031] h-[2px] w-full; +} + +/* Fancy blur effect */ +#nprogress .peg { + @apply absolute right-0 block h-full w-[100px]; + + box-shadow: + 0 0 10px hsl(var(--primary)), + 0 0 5px hsl(var(--primary)); + opacity: 1; + transform: rotate(3deg) translate(0, -4px); +} + +/* Remove these to get rid of the spinner */ +#nprogress .spinner { + @apply fixed right-4 top-4 z-[1031] block; +} + +#nprogress .spinner-icon { + @apply border-t-primary border-l-primary size-4 rounded-full border-[2px] border-solid border-transparent; + + animation: nprogress-spinner 400ms linear infinite; +} + +.nprogress-custom-parent { + @apply relative overflow-hidden; +} + +.nprogress-custom-parent #nprogress .spinner, +.nprogress-custom-parent #nprogress .bar { + @apply absolute; +} + +@keyframes nprogress-spinner { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +@keyframes nprogress-spinner { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} diff --git a/packages/@core/base/design/src/css/transition.css b/packages/@core/base/design/src/css/transition.css new file mode 100644 index 0000000..c1cb0e4 --- /dev/null +++ b/packages/@core/base/design/src/css/transition.css @@ -0,0 +1,236 @@ +.slide-up-enter-active, +.slide-up-leave-active { + transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1); +} + +.slide-up-move { + transition: transform 0.3s; +} + +.slide-up-enter-from, +.slide-up-leave-to { + opacity: 0; + transform: translateY(-15px); +} + +.slide-down-enter-active, +.slide-down-leave-active { + transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1); +} + +.slide-down-move { + transition: transform 0.3s; +} + +.slide-down-enter-from, +.slide-down-leave-to { + opacity: 0; + transform: translateY(15px); +} + +.slide-left-enter-active, +.slide-left-leave-active { + transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1); +} + +.slide-left-move { + transition: transform 0.3s; +} + +.slide-left-enter-from, +.slide-left-leave-to { + opacity: 0; + transform: translate(-15px); +} + +.slide-right-enter-active, +.slide-right-leave-active { + transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1); +} + +.slide-right-move { + transition: transform 0.3s; +} + +.slide-right-enter-from, +.slide-right-leave-to { + opacity: 0; + transform: translate(15px); +} + +.fade-transition-enter-active, +.fade-transition-leave-active { + transition: opacity 0.2s ease-in-out; +} + +.fade-transition-enter-from, +.fade-transition-leave-to { + opacity: 0; +} + +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.2s ease-in-out; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} + +.fade-slide-leave-active, +.fade-slide-enter-active { + transition: all 0.3s; +} + +.fade-slide-enter-from { + opacity: 0; + transform: translate(-30px); +} + +.fade-slide-leave-to { + opacity: 0; + transform: translate(30px); +} + +.fade-down-enter-active, +.fade-down-leave-active { + transition: + opacity 0.25s, + transform 0.3s; +} + +.fade-down-enter-from { + opacity: 0; + transform: translateY(-10%); +} + +.fade-down-leave-to { + opacity: 0; + transform: translateY(10%); +} + +.fade-scale-leave-active, +.fade-scale-enter-active { + transition: all 0.28s; +} + +.fade-scale-enter-from { + opacity: 0; + transform: scale(1.2); +} + +.fade-scale-leave-to { + opacity: 0; + transform: scale(0.8); +} + +.fade-up-enter-active, +.fade-up-leave-active { + transition: + opacity 0.2s, + transform 0.25s; +} + +.fade-up-enter-from { + opacity: 0; + transform: translateY(10%); +} + +.fade-up-leave-to { + opacity: 0; + transform: translateY(-10%); +} + +@keyframes fade-slide { + 0% { + opacity: 0; + transform: translate(-30px); + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0; + transform: translate(30px); + } +} + +@keyframes fade { + 0% { + opacity: 0; + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} + +@keyframes fade-up { + 0% { + opacity: 0; + transform: translateY(10%); + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0; + transform: translateY(-10%); + } +} + +@keyframes fade-down { + 0% { + opacity: 0; + transform: translateY(-10%); + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0; + transform: translateY(10%); + } +} + +.fade-slow { + animation: fade 3s infinite; +} + +.fade-slide-slow { + animation: fade-slide 3s infinite; +} + +.fade-up-slow { + animation: fade-up 3s infinite; +} + +.fade-down-slow { + animation: fade-down 3s infinite; +} + +.collapse-transition { + transition: + 0.2s height ease-in-out, + 0.2s padding-top ease-in-out, + 0.2s padding-bottom ease-in-out; +} + +.collapse-transition-leave-active, +.collapse-transition-enter-active { + transition: + 0.2s max-height ease-in-out, + 0.2s padding-top ease-in-out, + 0.2s margin-top ease-in-out; +} diff --git a/packages/@core/base/design/src/css/ui.css b/packages/@core/base/design/src/css/ui.css new file mode 100644 index 0000000..a1bf024 --- /dev/null +++ b/packages/@core/base/design/src/css/ui.css @@ -0,0 +1,101 @@ +.side-content { + animation-duration: 0.3s; + animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); +} + +.side-content[data-side='top'] { + animation-name: slide-up; +} + +.side-content[data-side='bottom'] { + animation-name: slide-down; +} + +.side-content[data-side='left'] { + animation-name: slide-left; +} + +.side-content[data-side='right'] { + animation-name: slide-right; +} + +.breadcrumb-transition-enter-active { + transition: + transform 0.4s cubic-bezier(0.76, 0, 0.24, 1), + opacity 0.4s cubic-bezier(0.76, 0, 0.24, 1); +} + +.breadcrumb-transition-leave-active { + display: none; +} + +.breadcrumb-transition-enter-from { + opacity: 0; + transform: translateX(30px) skewX(-30deg); +} + +@keyframes slide-down { + from { + opacity: 0; + transform: translateY(50px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slide-left { + from { + opacity: 0; + transform: translateX(-50px); + } + + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes slide-right { + from { + opacity: 0; + transform: translateX(50px); + } + + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes slide-up { + from { + opacity: 0; + transform: translateY(-50px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +.z-popup { + z-index: var(--popup-z-index); +} + +@keyframes shrink { + 0% { + transform: scale(1); + } + + 50% { + transform: scale(0.9); + } + + 100% { + transform: scale(1); + } +} diff --git a/packages/@core/base/design/src/design-tokens/dark.css b/packages/@core/base/design/src/design-tokens/dark.css new file mode 100644 index 0000000..8c9f986 --- /dev/null +++ b/packages/@core/base/design/src/design-tokens/dark.css @@ -0,0 +1,454 @@ +.dark, +.dark[data-theme='custom'], +.dark[data-theme='default'] { + /* Default background color of ...etc */ + --background: 222.34deg 10.43% 12.27%; + + /* 主体区域背景色 */ + --background-deep: 220deg 13.06% 9%; + --foreground: 0 0% 95%; + + /* Background color for */ + --card: 222.34deg 10.43% 12.27%; + + /* --card: 222.2 84% 4.9%; */ + --card-foreground: 210 40% 98%; + + /* Background color for popovers such as , , */ + + /* --popover: 222.82deg 8.43% 12.27%; */ + + /* 弹出层的背景色与主题区域背景色太过接近 */ + --popover: 0 0% 14.2%; + --popover-foreground: 210 40% 98%; + + /* Muted backgrounds such as , and */ + + /* --muted: 220deg 6.82% 17.25%; */ + + /* --muted-foreground: 215 20.2% 65.1%; */ + + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + + /* 主题颜色 */ + + /* --primary: 245 82% 67%; */ + --primary-foreground: 0 0% 98%; + + /* Used for destructive actions such as