├── .commitlintrc.json ├── .dockerignore ├── .editorconfig ├── .env ├── .eslintignore ├── .eslintrc.cjs ├── .gitattributes ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .npmrc ├── CHANGELOG.md ├── CONTRIBUTING.en.md ├── CONTRIBUTING.md ├── Dockerfile ├── README.md ├── SPONSOR.md ├── api ├── proxy.js ├── session.js └── verify.js ├── changlog.md ├── docker-compose ├── docker-compose.yml ├── gpts-mj-file │ ├── docker-compose.yml │ ├── nginx │ │ └── nginx.conf │ ├── readme.md │ ├── start.sh │ └── start_h.sh ├── nginx │ └── nginx.conf └── readme.md ├── docs ├── alipay.jpg ├── c1-2.8.0.png ├── c1-2.9.0.png ├── c1.png ├── c2-2.8.0.png ├── c2-2.9.0.png ├── c2.png ├── check_error.jpg ├── docker.png ├── gptbase.jpg ├── gpts.jpg ├── gpts1.jpg ├── mj1.jpg ├── mj2.jpg ├── mj2a1.jpg ├── mj2a2.jpg ├── mj2a3.jpg ├── mj3a2.jpg ├── mj4a1.png ├── mjs1.jpg ├── mjs2.jpg ├── mjs3.jpg ├── tts-whisper.png ├── tts.jpg └── wxpay.jpg ├── index.html ├── kubernetes ├── README.md ├── deploy.yaml └── ingress.yaml ├── license ├── nginx.conf ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── postcss.config.js ├── public ├── docmee-ui-sdk-iframe.min.js ├── favicon.ico ├── favicon.svg ├── gpts.json ├── index.htm ├── pwa-192x192.png └── pwa-512x512.png ├── src ├── App.vue ├── api │ ├── Recognition.ts │ ├── bot.ts │ ├── chat.ts │ ├── chatmsg.ts │ ├── fanyi.ts │ ├── index.ts │ ├── knowledge.ts │ ├── luma.ts │ ├── lumaStore.ts │ ├── mic.ts │ ├── mjapi.ts │ ├── mjsave.ts │ ├── model.ts │ ├── openapi.ts │ ├── pay.ts │ ├── ppt.ts │ ├── sse │ │ ├── fetch.ts │ │ ├── fetchsse.ts │ │ ├── stream-async-iterable.ts │ │ └── types.ts │ ├── store.ts │ ├── suno.ts │ ├── sunoStore.ts │ ├── units.ts │ └── user.ts ├── assets │ ├── 01.png │ ├── Subtract-2.png │ ├── Subtract-3.png │ ├── Subtract-active.png │ ├── Subtract.png │ ├── ageer.png │ ├── avatar.jpg │ ├── background.jpg │ ├── fileType.png │ ├── icons │ │ ├── Application.svg │ │ ├── Book.svg │ │ ├── Gallery.svg │ │ ├── Logout.svg │ │ ├── Music.svg │ │ ├── Robot.svg │ │ ├── Setting.svg │ │ ├── Vector.svg │ │ ├── add.svg │ │ ├── block-0.svg │ │ ├── block-1.svg │ │ ├── block-2.svg │ │ ├── block-3.svg │ │ ├── chatGPT.svg │ │ ├── clear.svg │ │ ├── gou.svg │ │ ├── message.svg │ │ ├── money.svg │ │ ├── music1.svg │ │ ├── refresh.svg │ │ ├── screenshot.svg │ │ ├── send.svg │ │ ├── upload.svg │ │ ├── video.svg │ │ └── voice.svg │ └── recommend.json ├── components │ ├── common │ │ ├── HoverButton │ │ │ ├── Button.vue │ │ │ └── index.vue │ │ ├── IconSvg │ │ │ └── index.vue │ │ ├── NaiveProvider │ │ │ └── index.vue │ │ ├── PromptStore │ │ │ └── index.vue │ │ ├── Setting │ │ │ ├── About.vue │ │ │ ├── Advanced.vue │ │ │ ├── General.vue │ │ │ └── index.vue │ │ ├── SvgIcon │ │ │ └── index.vue │ │ ├── UserAvatar │ │ │ └── index.vue │ │ └── index.ts │ └── custom │ │ ├── GithubSite.vue │ │ └── index.ts ├── enums │ └── RespEnum.ts ├── hooks │ ├── useBasicLayout.ts │ ├── useIconRender.ts │ ├── useLanguage.ts │ └── useTheme.ts ├── icons.ts ├── icons │ ├── 403.vue │ ├── 404.svg │ └── 500.vue ├── locales │ ├── en-US.ts │ ├── fr-FR.ts │ ├── index.ts │ ├── ko-KR.ts │ ├── ru-RU.ts │ ├── tr-TR.ts │ ├── vi-VN.ts │ ├── zh-CN.ts │ └── zh-TW.ts ├── main.ts ├── plugins │ ├── assets.ts │ ├── cache.ts │ ├── index.ts │ └── scrollbarStyle.ts ├── router │ ├── index.ts │ └── permission.ts ├── store │ ├── helper.ts │ ├── homeStore.ts │ ├── index.ts │ └── modules │ │ ├── app │ │ ├── helper.ts │ │ └── index.ts │ │ ├── auth │ │ ├── anth.ts │ │ ├── helper.ts │ │ └── index.ts │ │ ├── chat │ │ ├── helper.ts │ │ └── index.ts │ │ ├── index.ts │ │ ├── prompt │ │ ├── helper.ts │ │ └── index.ts │ │ ├── settings │ │ ├── helper.ts │ │ └── index.ts │ │ └── user │ │ ├── helper.ts │ │ └── index.ts ├── styles │ ├── global.less │ └── lib │ │ ├── github-markdown.less │ │ ├── highlight.less │ │ └── tailwind.css ├── typings │ ├── chat.d.ts │ ├── env.d.ts │ ├── global.d.ts │ └── user.d.ts ├── utils │ ├── copy.ts │ ├── errorCode.ts │ ├── functions │ │ ├── debounce.ts │ │ └── index.ts │ ├── is │ │ └── index.ts │ ├── request │ │ ├── axios.ts │ │ ├── index.ts │ │ └── req.ts │ ├── ruoyi.ts │ └── storage │ │ └── index.ts └── views │ ├── chat │ ├── components │ │ ├── Header │ │ │ └── index.vue │ │ ├── Message │ │ │ ├── Avatar.vue │ │ │ ├── Text.vue │ │ │ ├── index.vue │ │ │ └── style.less │ │ └── index.ts │ ├── hooks │ │ ├── useChat.ts │ │ ├── useScroll.ts │ │ └── useUsingContext.ts │ ├── index.vue │ └── layout │ │ ├── Layout.vue │ │ ├── Permission.vue │ │ ├── index.ts │ │ └── sider │ │ ├── Footer.vue │ │ ├── List.vue │ │ └── index.vue │ ├── exception │ ├── 404 │ │ └── index.vue │ └── 500 │ │ └── index.vue │ ├── knowledge │ ├── annex.vue │ ├── fragment.vue │ ├── index.vue │ └── layout.vue │ ├── login │ ├── index.vue │ ├── regist.vue │ └── reset.vue │ ├── luma │ ├── layout.vue │ ├── video.vue │ ├── voInput.vue │ └── voList.vue │ ├── mj │ ├── aiBlend.vue │ ├── aiCanvas.vue │ ├── aiDall.vue │ ├── aiDrawInput.vue │ ├── aiDrawInputItem.vue │ ├── aiFace.vue │ ├── aiFooter.vue │ ├── aiGallery.vue │ ├── aiGalleryItem.vue │ ├── aiGpt.vue │ ├── aiGptInput.vue │ ├── aiGpts.vue │ ├── aiGptsCom.vue │ ├── aiListText.vue │ ├── aiMic.vue │ ├── aiMobileMenu.vue │ ├── aiModel.vue │ ├── aiMsg.vue │ ├── aiSetAuth.vue │ ├── aiSetServer.vue │ ├── aiSider.vue │ ├── aiSiderInput.vue │ ├── aiTextSetting.vue │ ├── dallText.vue │ ├── draw.json │ ├── draw.vue │ ├── drawList.vue │ ├── index.ts │ ├── layout.vue │ ├── mjText.vue │ ├── mjTextAttr.vue │ ├── myTest.vue │ ├── ttsText.vue │ └── whisperText.vue │ ├── ppt │ ├── layout.vue │ └── ppt.vue │ └── suno │ ├── layout.vue │ ├── mcInput.vue │ ├── mcList.vue │ ├── mcUploadMp3.vue │ ├── mcplayer.vue │ ├── music.vue │ ├── player.vue │ └── playui.vue ├── start.cmd ├── start.sh ├── tailwind.config.js ├── tsconfig.json ├── vercel.json └── vite.config.ts /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | */node_modules 3 | node_modules 4 | Dockerfile 5 | .* 6 | */.* 7 | !.env 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = tab 8 | indent_size = 2 9 | end_of_line = lf 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # Glob API URL 2 | VITE_GLOB_API_URL=/api 3 | 4 | VITE_APP_API_BASE_URL = http://127.0.0.1:6039/ 5 | 6 | # Whether long replies are supported, which may result in higher API fees 7 | VITE_GLOB_OPEN_LONG_REPLY=false 8 | 9 | # When you want to use PWA 10 | VITE_GLOB_APP_PWA=false 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | docker-compose 2 | kubernetes 3 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@antfu'], 4 | } 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | "*.vue" eol=lf 2 | "*.js" eol=lf 3 | "*.ts" eol=lf 4 | "*.jsx" eol=lf 5 | "*.tsx" eol=lf 6 | "*.cjs" eol=lf 7 | "*.cts" eol=lf 8 | "*.mjs" eol=lf 9 | "*.mts" eol=lf 10 | "*.json" eol=lf 11 | "*.html" eol=lf 12 | "*.css" eol=lf 13 | "*.less" eol=lf 14 | "*.scss" eol=lf 15 | "*.sass" eol=lf 16 | "*.styl" eol=lf 17 | "*.md" eol=lf 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/extensions.json 24 | .idea 25 | *.suo 26 | *.ntvs* 27 | *.njsproj 28 | *.sln 29 | *.sw? 30 | 31 | # Environment variables files 32 | /service/.env 33 | /service/uploads -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | strict-peer-dependencies=false 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.en.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | Thank you for your valuable time. Your contributions will make this project better! Before submitting a contribution, please take some time to read the getting started guide below. 3 | 4 | ## Semantic Versioning 5 | This project follows semantic versioning. We release patch versions for important bug fixes, minor versions for new features or non-important changes, and major versions for significant and incompatible changes. 6 | 7 | Each major change will be recorded in the `changelog`. 8 | 9 | ## Submitting Pull Request 10 | 1. Fork [this repository](https://github.com/Chanzhaoyu/chatgpt-web) and create a branch from `main`. For new feature implementations, submit a pull request to the `feature` branch. For other changes, submit to the `main` branch. 11 | 2. Install the `pnpm` tool using `npm install pnpm -g`. 12 | 3. Install the `Eslint` plugin for `VSCode`, or enable `eslint` functionality for other editors such as `WebStorm`. 13 | 4. Execute `pnpm bootstrap` in the root directory. 14 | 5. Execute `pnpm install` in the `/service/` directory. 15 | 6. Make changes to the codebase. If applicable, ensure that appropriate testing has been done. 16 | 7. Execute `pnpm lint:fix` in the root directory to perform a code formatting check. 17 | 8. Execute `pnpm type-check` in the root directory to perform a type check. 18 | 9. Submit a git commit, following the [Commit Guidelines](#commit-guidelines). 19 | 10. Submit a `pull request`. If there is a corresponding `issue`, please link it using the [linking-a-pull-request-to-an-issue keyword](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword). 20 | 21 | ## Commit Guidelines 22 | 23 | Commit messages should follow the [conventional-changelog standard](https://www.conventionalcommits.org/en/v1.0.0/): 24 | 25 | ```bash 26 | [optional scope]: 27 | 28 | [optional body] 29 | 30 | [optional footer] 31 | ``` 32 | 33 | ### Commit Types 34 | 35 | The following is a list of commit types: 36 | 37 | - feat: New feature or functionality 38 | - fix: Bug fix 39 | - docs: Documentation update 40 | - style: Code style or component style update 41 | - refactor: Code refactoring, no new features or bug fixes introduced 42 | - perf: Performance optimization 43 | - test: Unit test 44 | - chore: Other commits that do not modify src or test files 45 | 46 | 47 | ## License 48 | 49 | [MIT](./license) -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 贡献指南 2 | 感谢你的宝贵时间。你的贡献将使这个项目变得更好!在提交贡献之前,请务必花点时间阅读下面的入门指南。 3 | 4 | ## 语义化版本 5 | 该项目遵循语义化版本。我们对重要的漏洞修复发布修订号,对新特性或不重要的变更发布次版本号,对重大且不兼容的变更发布主版本号。 6 | 7 | 每个重大更改都将记录在 `changelog` 中。 8 | 9 | ## 提交 Pull Request 10 | 1. Fork [此仓库](https://github.com/Chanzhaoyu/chatgpt-web),从 `main` 创建分支。新功能实现请发 pull request 到 `feature` 分支。其他更改发到 `main` 分支。 11 | 2. 使用 `npm install pnpm -g` 安装 `pnpm` 工具。 12 | 3. `vscode` 安装了 `Eslint` 插件,其它编辑器如 `webStorm` 打开了 `eslint` 功能。 13 | 4. 根目录下执行 `pnpm bootstrap`。 14 | 5. `/service/` 目录下执行 `pnpm install`。 15 | 6. 对代码库进行更改。如果适用的话,请确保进行了相应的测试。 16 | 7. 请在根目录下执行 `pnpm lint:fix` 进行代码格式检查。 17 | 8. 请在根目录下执行 `pnpm type-check` 进行类型检查。 18 | 9. 提交 git commit, 请同时遵守 [Commit 规范](#commit-指南) 19 | 10. 提交 `pull request`, 如果有对应的 `issue`,请进行[关联](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)。 20 | 21 | ## Commit 指南 22 | 23 | Commit messages 请遵循[conventional-changelog 标准](https://www.conventionalcommits.org/en/v1.0.0/): 24 | 25 | ```bash 26 | <类型>[可选 范围]: <描述> 27 | 28 | [可选 正文] 29 | 30 | [可选 脚注] 31 | ``` 32 | 33 | ### Commit 类型 34 | 35 | 以下是 commit 类型列表: 36 | 37 | - feat: 新特性或功能 38 | - fix: 缺陷修复 39 | - docs: 文档更新 40 | - style: 代码风格或者组件样式更新 41 | - refactor: 代码重构,不引入新功能和缺陷修复 42 | - perf: 性能优化 43 | - test: 单元测试 44 | - chore: 其他不修改 src 或测试文件的提交 45 | 46 | 47 | ## License 48 | 49 | [MIT](./license) 50 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # build front-end 2 | FROM node:lts-alpine AS frontend 3 | 4 | RUN npm install pnpm -g 5 | 6 | WORKDIR /app 7 | 8 | COPY ./package.json /app 9 | 10 | COPY ./pnpm-lock.yaml /app 11 | 12 | RUN pnpm install 13 | 14 | COPY . /app 15 | 16 | RUN pnpm run build 17 | 18 | # build backend 19 | FROM node:lts-alpine as backend 20 | 21 | RUN npm install pnpm -g 22 | 23 | WORKDIR /app 24 | 25 | COPY /service/package.json /app 26 | 27 | COPY /service/pnpm-lock.yaml /app 28 | 29 | RUN pnpm install 30 | 31 | COPY /service /app 32 | 33 | RUN pnpm build 34 | 35 | # service 36 | FROM node:lts-alpine 37 | 38 | RUN npm install pnpm -g 39 | 40 | WORKDIR /app 41 | 42 | COPY /service/package.json /app 43 | 44 | COPY /service/pnpm-lock.yaml /app 45 | 46 | RUN pnpm install --production && rm -rf /root/.npm /root/.pnpm-store /usr/local/share/.cache /tmp/* 47 | 48 | COPY /service /app 49 | 50 | COPY --from=frontend /app/dist /app/public 51 | 52 | COPY --from=backend /app/build /app/build 53 | 54 | EXPOSE 3002 55 | 56 | CMD ["pnpm", "run", "prod"] 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ruoyi-web 2 | ruoyi-ai 用户端页面 3 | -------------------------------------------------------------------------------- /SPONSOR.md: -------------------------------------------------------------------------------- 1 | # Sponsor My Open Source Works 2 | 3 | 如果我的开源项目对你有帮助,请考虑通过以下任意一种方式赞助: 4 | 5 | ## 微信赞助 6 | ![微信](./docs/wxpay.jpg) 7 | ## 支付宝赞助 8 | ![支付宝](./docs/alipay.jpg) -------------------------------------------------------------------------------- /api/proxy.js: -------------------------------------------------------------------------------- 1 | const { 2 | createProxyMiddleware 3 | } = require('http-proxy-middleware') 4 | 5 | module.exports = (req, res) => { 6 | let target = '' 7 | let headers= {} 8 | // 代理目标地址 9 | if (req.url.startsWith('/mjapi')) { //这里使用/api可能会与vercel serverless 的 api 路径冲突,根据接口进行调整 10 | target = process.env.MJ_SERVER??'https://api.openai.com'; 11 | headers= { 12 | 'Mj-Api-Secret': process.env.MJ_API_SECRET // 添加自定义请求头 13 | } 14 | }else if(req.url.startsWith('/openapi')){ 15 | target = process.env.OPENAI_API_BASE_URL??'https://api.openai.com'; 16 | headers= { 17 | 'Authorization': 'Bearer ' +process.env.OPENAI_API_KEY // 添加自定义请求头 18 | } 19 | } 20 | // 创建代理对象并转发请求 21 | createProxyMiddleware({ 22 | target, 23 | changeOrigin: true, 24 | headers, 25 | pathRewrite: { 26 | // 通过路径重写,去除请求路径中的 `/api` 27 | '^/mjapi/': '/' 28 | ,'^/openapi/': '/' 29 | } 30 | })(req, res) 31 | } 32 | -------------------------------------------------------------------------------- /api/session.js: -------------------------------------------------------------------------------- 1 | module.exports = (req, res) => { 2 | try { 3 | let data = req.body.data; 4 | let obj ={ 5 | "status": "Success", 6 | "message": "", 7 | "data": { 8 | "isHideServer": false, 9 | "isUpload": false, 10 | "auth": process.env.AUTH_SECRET_KEY?true:false , 11 | "model": "ChatGPTAPI", 12 | "amodel": process.env.OPENAI_API_MODEL?? "gpt-3.5-turbo" 13 | ,isApiGallery: process.env.MJ_API_GALLERY ? true : false 14 | ,cmodels : process.env.CUSTOM_MODELS??'' 15 | ,baiduId : process.env.TJ_BAIDU_ID?? "" 16 | ,googleId: process.env.TJ_GOOGLE_ID?? "" 17 | , notify : process.env.SYS_NOTIFY?? "" 18 | } 19 | } 20 | res.writeHead(200).end( 21 | JSON.stringify( obj ) 22 | ); 23 | } catch (e) { 24 | console.error('session.js', e, req.body); 25 | } 26 | } -------------------------------------------------------------------------------- /api/verify.js: -------------------------------------------------------------------------------- 1 | module.exports = (req, res) => { 2 | let obj= {"status":"Fail","message":"密钥无效 | Secret key is invalid","data":null}; 3 | if( req.body && req.body.token && process.env.AUTH_SECRET_KEY == req.body.token) obj= { status: 'Success', message: 'Verify successfully', data: null } 4 | res.setHeader('Content-type', 'application/json' ); 5 | res.writeHead(200).end( 6 | JSON.stringify( obj ) 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /changlog.md: -------------------------------------------------------------------------------- 1 | # 功能升级日志 2 | ## 2.13.5 3 | - 🐞 修复: 历史记录未带附件链接 4 | - 😄 新增: 系统通知 #47 5 | 6 | ## 2.13.4 7 | - 🐞 修复: midjourney 参数不起作用 8 | 9 | ## 2.13.3 10 | - 😄 更新: 更新本月使用量、余额的请求方式 11 | 12 | ## 2.13.2 13 | - 🐞 修复: midjourney 刷新错误 14 | - ✅ 新增: tts whisper 界面支持 15 | - 😄 新增: midjourney 新增 V6 16 | 17 | 18 | ## 2.13.1 19 | - 🐞 修复: gpts 加载排序 20 | - 😄 新增: google、百度统计 21 | - 😄 新增: 文件支持拖拽上传、粘贴截图上传 #39 22 | 23 | 24 | ## 2.12.11 25 | - 🐞 紧急修复: gpt-4-vision-preview 格式错误 26 | 27 | ## 2.12.10 28 | - 🐞 修复:希望实现左侧绘画功能区固定,不随对话界面上下滚动 #29 29 | - 🐞 修复:当服务端有错误,ui错误显示为空bug 30 | - 🔨 优化:新增 `CUSTOM_MODELS` 环境变量 可自定义可选模型 #32 31 | 32 | 33 | 34 | ## 2.12.9 35 | - 🐞 修复:按住回车不放,会一直触发提交刷新页面 #24 36 | - 🔨 优化:我的画廊 支持来之服务端的数据(适合自建服务器) 37 | - 🔨 优化:midjourney 新增 `原始链接` 按钮 38 | 39 | ## 2.12.8 40 | - 😄 新增:我的画廊 41 | - 🐞 修复:重试按钮、停止按钮功能 #15 42 | 43 | 44 | ## 2.12.7 45 | - 😄 新增:vercel 增加 AUTH_SECRET_KEY 可以访问输入验证 #15 46 | - 🐞 修复:OPENAI_API_MODEL 默认模型 同时支持 vercel #16 47 | - 🐞 修复:在手机端左侧没法下滑 #18 48 | 49 | 50 | ## 2.12.6 51 | - 🐞 修复:`经常出现“新建聊天框”遮挡对话框的情况 ` #13 52 | - 🐞 修复:当GPTS在绘画窗口,切换不起效果 53 | - 🐞 修复:分页载入GPTs计数出现的问题 54 | 55 | ## 2.12.5 56 | - 🔨 优化:丰富 GPTS 内容,可以搜索 57 | - 😄 新增:前端UI 设置 上传文件入口 #11 58 | - 🐞 修复:`经常出现“新建聊天框”遮挡对话框的情况 ` #13 59 | 60 | 61 | ## 2.12.4 62 | - 😄 新增 dall-e-2模型 63 | - 🐞 修复:上传错误提示为空 64 | - 🐞 修复:`环境变量中配置的OPENAI_API_BASE_URL和OPENAI_API_KEY依旧没有效果,前端对话一直处于“思考中...”` #4 65 | 66 | ## 2.12.3 67 | - 😄 新增 支持超链模型切换 http://ip:6013/#/m/gpt-4-all http://ip:6013/#/m/gpt-4-gizmo-1234 68 | - 😄 新增 支持 GPTs 多模态 69 | 70 | ## 2.12.2 71 | - 😄 新增:支持文件后端上传(供给gpt-4-all gpt-4-gizmo-xxx 模型)! 默认是关闭的 打开需要环境变量 API_UPLOADER=1 72 | - 😄 新增:支持逆向模型 gpt-4-all gpt-4-v gpt-4-gizmo-(gizmo_id) 73 | 74 | ## 2.12.1 75 | - 😄 新增:图片上传图片 供gpt-4-vision-preview使用 76 | - 🐞 修复::midjourney 辅助提示“模型版本” 去掉早期过时版本 77 | 78 | ## 2.11.10 79 | - 🐞 修复:dall-e-3的大小规格 80 | - 😄 新增:gpt模型选择 81 | - 😄 新增:gpt自定义模型、上下文数、回复数 82 | - 🔩 更改:流请求方式由原来的axios改为fetch 打字效果更加顺滑 83 | 84 | ## 2.11.9 85 | - 😄 新增:dall-e-3 画图 86 | ## 2.11.8 87 | - 🐞 修复:midjourney 辅助提示“性格” 出现的bug 88 | - 😄 新增:最新版本检查 89 | - 😄 新增:midjourney 获取seed 90 | 91 | ## 2.11.7 92 | - 😀 新增:midjourney 换脸 93 | - 😀 新增:midjourney 混图 -------------------------------------------------------------------------------- /docker-compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | app: 5 | container_name: chatgpt-web 6 | image: chenzhaoyu94/chatgpt-web # 总是使用latest,更新时重新pull该tag镜像即可 7 | ports: 8 | - 3002:3002 9 | environment: 10 | # 二选一 11 | OPENAI_API_KEY: 12 | # 二选一 13 | OPENAI_ACCESS_TOKEN: 14 | # API接口地址,可选,设置 OPENAI_API_KEY 时可用 15 | OPENAI_API_BASE_URL: 16 | # API模型,可选,设置 OPENAI_API_KEY 时可用 17 | OPENAI_API_MODEL: 18 | # 反向代理,可选 19 | API_REVERSE_PROXY: 20 | # 访问权限密钥,可选 21 | AUTH_SECRET_KEY: 22 | # 每小时最大请求次数,可选,默认无限 23 | MAX_REQUEST_PER_HOUR: 0 24 | # 超时,单位毫秒,可选 25 | TIMEOUT_MS: 60000 26 | # Socks代理,可选,和 SOCKS_PROXY_PORT 一起时生效 27 | SOCKS_PROXY_HOST: 28 | # Socks代理端口,可选,和 SOCKS_PROXY_HOST 一起时生效 29 | SOCKS_PROXY_PORT: 30 | # Socks代理用户名,可选,和 SOCKS_PROXY_HOST & SOCKS_PROXY_PORT 一起时生效 31 | SOCKS_PROXY_USERNAME: 32 | # Socks代理密码,可选,和 SOCKS_PROXY_HOST & SOCKS_PROXY_PORT 一起时生效 33 | SOCKS_PROXY_PASSWORD: 34 | # HTTPS_PROXY 代理,可选 35 | HTTPS_PROXY: 36 | nginx: 37 | container_name: nginx 38 | image: nginx:alpine 39 | ports: 40 | - '80:80' 41 | expose: 42 | - '80' 43 | volumes: 44 | - ./nginx/html:/usr/share/nginx/html 45 | - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf 46 | links: 47 | - app 48 | -------------------------------------------------------------------------------- /docker-compose/gpts-mj-file/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | gptweb: 5 | container_name: chatgpt-web-midjourney-proxy 6 | image: ydlhero/chatgpt-web-midjourney-proxy # 总是使用latest,更新时重新pull该tag镜像即可 7 | ports: 8 | - 6050:3002 9 | environment: 10 | TZ: Asia/Shanghai # 指定时区 11 | # 必选 12 | OPENAI_API_KEY: sk-xxxx 13 | # API接口地址,可选,设置 OPENAI_API_KEY 时可用 14 | OPENAI_API_BASE_URL: 15 | # 访问权限密钥,可选 注意修改 16 | AUTH_SECRET_KEY: mygod 17 | # midjourney 服务器地址,可选 可用下面的 http://midjourney-proxy:8080 18 | MJ_SERVER: http://midjourney-proxy:8080 19 | # midjourney API密钥,可选 20 | MJ_API_SECRET: mygod 21 | #API_UPLOADER 是否可以上传 1 可以其他都不可以,可选 22 | API_UPLOADER: 1 23 | #FILE_SERVER 文件服务器,可选 可以用下面的 http://fileserver:3012 24 | FILE_SERVER: http://fileserver:3012 25 | #爆破:验证次数 注意: vercel 不支持 nginx 请设置 proxy_set_header X-Forwarded-For $remote_addr; 26 | AUTH_SECRET_ERROR_COUNT: 3 27 | #爆破:验证停留时间 单位分钟 注意: vercel 不支持 28 | AUTH_SECRET_ERROR_TIME: 10 29 | 30 | # midjourney服务 可选 31 | midjourney-proxy: 32 | image: novicezk/midjourney-proxy:2.5.5 33 | restart: always 34 | # ports: 35 | # - 6013:8080 #映射端口 36 | environment: 37 | TZ: Asia/Shanghai # 指定时区 38 | mj.discord.guild-id: xxx # xxx 如何获取 39 | mj.discord.channel-id: xxx 40 | mj.discord.user-token: xxx 41 | mj.api-secret: mygod # MJ_API_SECRET 42 | 43 | #文件服务 可选 44 | fileserver: 45 | image: ydlhero/file-server:latest 46 | restart: always 47 | environment: 48 | TZ: Asia/Shanghai # 指定时区 49 | #对外显示的域名 50 | SERVER_NAME: http://myip:3102 51 | ports: 52 | - "3102:3102" 53 | volumes: 54 | - /data/uploads:/app/uploads 55 | 56 | -------------------------------------------------------------------------------- /docker-compose/gpts-mj-file/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | charset utf-8; 5 | error_page 500 502 503 504 /50x.html; 6 | 7 | # 防止爬虫抓取 8 | if ($http_user_agent ~* "360Spider|JikeSpider|Spider|spider|bot|Bot|2345Explorer|curl|wget|webZIP|qihoobot|Baiduspider|Googlebot|Googlebot-Mobile|Googlebot-Image|Mediapartners-Google|Adsbot-Google|Feedfetcher-Google|Yahoo! Slurp|Yahoo! Slurp China|YoudaoBot|Sosospider|Sogou spider|Sogou web spider|MSNBot|ia_archiver|Tomato Bot|NSPlayer|bingbot") 9 | { 10 | return 403; 11 | } 12 | 13 | location / { 14 | root /usr/share/nginx/html; 15 | try_files $uri /index.html; 16 | } 17 | 18 | location /api { 19 | proxy_set_header X-Real-IP $remote_addr; #转发用户IP 20 | proxy_pass http://app:3002; 21 | } 22 | 23 | proxy_set_header Host $host; 24 | proxy_set_header X-Real-IP $remote_addr; 25 | proxy_set_header REMOTE-HOST $remote_addr; 26 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 27 | } 28 | -------------------------------------------------------------------------------- /docker-compose/gpts-mj-file/readme.md: -------------------------------------------------------------------------------- 1 | ### docker-compose 部署教程 2 | 3 | ```shell 4 | git clone https://github.com/Dooy/chatgpt-web-midjourney-proxy.git 5 | cd chatgpt-web-midjourney-proxy/docker-compose/gpts-mj-file 6 | #修改 docker-compose.yml 文件配置问文件 7 | 8 | #如果执行有问题 请执行 start_h.sh 9 | start.sh 10 | ``` -------------------------------------------------------------------------------- /docker-compose/gpts-mj-file/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | # docker compose pull 4 | # docker compose up -d --remove-orphans 5 | docker-compose pull 6 | docker-compose up -d --remove-orphans -------------------------------------------------------------------------------- /docker-compose/gpts-mj-file/start_h.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | docker compose pull 4 | docker compose up -d --remove-orphans 5 | -------------------------------------------------------------------------------- /docker-compose/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | charset utf-8; 5 | error_page 500 502 503 504 /50x.html; 6 | 7 | # 防止爬虫抓取 8 | if ($http_user_agent ~* "360Spider|JikeSpider|Spider|spider|bot|Bot|2345Explorer|curl|wget|webZIP|qihoobot|Baiduspider|Googlebot|Googlebot-Mobile|Googlebot-Image|Mediapartners-Google|Adsbot-Google|Feedfetcher-Google|Yahoo! Slurp|Yahoo! Slurp China|YoudaoBot|Sosospider|Sogou spider|Sogou web spider|MSNBot|ia_archiver|Tomato Bot|NSPlayer|bingbot") 9 | { 10 | return 403; 11 | } 12 | 13 | location / { 14 | root /usr/share/nginx/html; 15 | try_files $uri /index.html; 16 | } 17 | 18 | location /api { 19 | proxy_set_header X-Real-IP $remote_addr; #转发用户IP 20 | proxy_pass http://app:3002; 21 | } 22 | 23 | proxy_set_header Host $host; 24 | proxy_set_header X-Real-IP $remote_addr; 25 | proxy_set_header REMOTE-HOST $remote_addr; 26 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 27 | } 28 | -------------------------------------------------------------------------------- /docker-compose/readme.md: -------------------------------------------------------------------------------- 1 | ### docker-compose 部署教程 2 | - 将打包好的前端文件放到 `nginx/html` 目录下 3 | - ```shell 4 | # 启动 5 | docker-compose up -d 6 | ``` 7 | - ```shell 8 | # 查看运行状态 9 | docker ps 10 | ``` 11 | - ```shell 12 | # 结束运行 13 | docker-compose down 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/alipay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/alipay.jpg -------------------------------------------------------------------------------- /docs/c1-2.8.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/c1-2.8.0.png -------------------------------------------------------------------------------- /docs/c1-2.9.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/c1-2.9.0.png -------------------------------------------------------------------------------- /docs/c1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/c1.png -------------------------------------------------------------------------------- /docs/c2-2.8.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/c2-2.8.0.png -------------------------------------------------------------------------------- /docs/c2-2.9.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/c2-2.9.0.png -------------------------------------------------------------------------------- /docs/c2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/c2.png -------------------------------------------------------------------------------- /docs/check_error.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/check_error.jpg -------------------------------------------------------------------------------- /docs/docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/docker.png -------------------------------------------------------------------------------- /docs/gptbase.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/gptbase.jpg -------------------------------------------------------------------------------- /docs/gpts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/gpts.jpg -------------------------------------------------------------------------------- /docs/gpts1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/gpts1.jpg -------------------------------------------------------------------------------- /docs/mj1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/mj1.jpg -------------------------------------------------------------------------------- /docs/mj2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/mj2.jpg -------------------------------------------------------------------------------- /docs/mj2a1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/mj2a1.jpg -------------------------------------------------------------------------------- /docs/mj2a2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/mj2a2.jpg -------------------------------------------------------------------------------- /docs/mj2a3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/mj2a3.jpg -------------------------------------------------------------------------------- /docs/mj3a2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/mj3a2.jpg -------------------------------------------------------------------------------- /docs/mj4a1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/mj4a1.png -------------------------------------------------------------------------------- /docs/mjs1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/mjs1.jpg -------------------------------------------------------------------------------- /docs/mjs2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/mjs2.jpg -------------------------------------------------------------------------------- /docs/mjs3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/mjs3.jpg -------------------------------------------------------------------------------- /docs/tts-whisper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/tts-whisper.png -------------------------------------------------------------------------------- /docs/tts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/tts.jpg -------------------------------------------------------------------------------- /docs/wxpay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/docs/wxpay.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | AI Assistant 11 | 12 | 13 | 14 |
15 | 72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /kubernetes/README.md: -------------------------------------------------------------------------------- 1 | ## 增加一个Kubernetes的部署方式 2 | ``` 3 | kubectl apply -f deploy.yaml 4 | ``` 5 | 6 | ### 如果需要Ingress域名接入 7 | ``` 8 | kubectl apply -f ingress.yaml 9 | ``` 10 | -------------------------------------------------------------------------------- /kubernetes/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: chatgpt-web 5 | labels: 6 | app: chatgpt-web 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: chatgpt-web 12 | strategy: 13 | type: RollingUpdate 14 | template: 15 | metadata: 16 | labels: 17 | app: chatgpt-web 18 | spec: 19 | containers: 20 | - image: chenzhaoyu94/chatgpt-web 21 | name: chatgpt-web 22 | imagePullPolicy: Always 23 | ports: 24 | - containerPort: 3002 25 | env: 26 | - name: OPENAI_API_KEY 27 | value: sk-xxx 28 | - name: OPENAI_API_BASE_URL 29 | value: 'https://api.openai.com' 30 | - name: OPENAI_API_MODEL 31 | value: gpt-3.5-turbo 32 | - name: API_REVERSE_PROXY 33 | value: https://ai.fakeopen.com/api/conversation 34 | - name: AUTH_SECRET_KEY 35 | value: '123456' 36 | - name: TIMEOUT_MS 37 | value: '60000' 38 | - name: SOCKS_PROXY_HOST 39 | value: '' 40 | - name: SOCKS_PROXY_PORT 41 | value: '' 42 | - name: HTTPS_PROXY 43 | value: '' 44 | resources: 45 | limits: 46 | cpu: 500m 47 | memory: 500Mi 48 | requests: 49 | cpu: 300m 50 | memory: 300Mi 51 | --- 52 | apiVersion: v1 53 | kind: Service 54 | metadata: 55 | labels: 56 | app: chatgpt-web 57 | name: chatgpt-web 58 | spec: 59 | ports: 60 | - name: chatgpt-web 61 | port: 3002 62 | protocol: TCP 63 | targetPort: 3002 64 | selector: 65 | app: chatgpt-web 66 | type: ClusterIP 67 | -------------------------------------------------------------------------------- /kubernetes/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | annotations: 5 | kubernetes.io/ingress.class: nginx 6 | nginx.ingress.kubernetes.io/proxy-connect-timeout: '5' 7 | name: chatgpt-web 8 | spec: 9 | rules: 10 | - host: chatgpt.example.com 11 | http: 12 | paths: 13 | - backend: 14 | service: 15 | name: chatgpt-web 16 | port: 17 | number: 3002 18 | path: / 19 | pathType: ImplementationSpecific 20 | tls: 21 | - secretName: chatgpt-web-tls 22 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ageerle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | include mime.types; 9 | default_type application/octet-stream; 10 | 11 | sendfile on; 12 | keepalive_timeout 65; 13 | 14 | server { 15 | listen 8081; 16 | server_name localhost; 17 | 18 | location / { 19 | root /usr/share/nginx/html/web; 20 | index index.html index.htm; 21 | try_files $uri $uri/ /index.html; 22 | } 23 | 24 | location /api/{ 25 | proxy_pass http://ruoyi-server:6039/; 26 | # 避免出现反代https域名出现502错误 27 | proxy_ssl_server_name on; 28 | } 29 | 30 | error_page 500 502 503 504 /50x.html; 31 | location = /50x.html { 32 | root /usr/share/nginx/html; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ruoyi-web", 3 | "version": "2.0.0", 4 | "private": false, 5 | "description": "ruoyi-web", 6 | "author": "ageer ", 7 | "keywords": [ 8 | "rouyi-web", 9 | "chatgpt", 10 | "chatbot", 11 | "Midjourney", 12 | "Midjourney UI", 13 | "Midjourney Proxy", 14 | "gpts", 15 | "gpts ui", 16 | "vue" 17 | ], 18 | "scripts": { 19 | "dev": "vite", 20 | "build": "run-p build-only", 21 | "buildold": "run-p type-check build-only", 22 | "preview": "vite preview", 23 | "build-only": "vite build", 24 | "type-check": "vue-tsc --noEmit", 25 | "lint": "eslint .", 26 | "lint:fix": "eslint . --fix", 27 | "bootstrap": "pnpm install && pnpm run common:prepare", 28 | "common:cleanup": "rimraf node_modules && rimraf pnpm-lock.yaml", 29 | "common:prepare": "husky install", 30 | "docs:dev": "vitepress dev docs", 31 | "docs:build": "vitepress build docs", 32 | "docs:preview": "vitepress preview docs" 33 | }, 34 | "dependencies": { 35 | "@tinyflow-ai/vue": "^0.0.9", 36 | "@traptitech/markdown-it-katex": "^3.6.0", 37 | "@vicons/ionicons5": "^0.13.0", 38 | "@vue-office/pdf": "^2.0.10", 39 | "@vueuse/core": "^9.13.0", 40 | "await-to-js": "^3.0.0", 41 | "element-plus": "^2.9.2", 42 | "eventsource-parser": "^1.1.1", 43 | "form-data": "^4.0.0", 44 | "gpt-tokenizer": "^2.1.2", 45 | "highlight.js": "^11.7.0", 46 | "html2canvas": "^1.4.1", 47 | "js-audio-recorder": "^1.0.7", 48 | "katex": "^0.16.4", 49 | "localforage": "^1.10.0", 50 | "markdown-it": "^13.0.1", 51 | "naive-ui": "^2.34.3", 52 | "pinia": "^2.0.33", 53 | "vue": "^3.2.47", 54 | "vue-demi": "^0.14.10", 55 | "vue-i18n": "^9.2.2", 56 | "vue-router": "^4.1.6", 57 | "vue-waterfall-plugin-next": "^2.3.1" 58 | }, 59 | "devDependencies": { 60 | "@antfu/eslint-config": "^0.35.3", 61 | "@commitlint/cli": "^17.4.4", 62 | "@commitlint/config-conventional": "^17.4.4", 63 | "@iconify/vue": "^4.1.0", 64 | "@types/crypto-js": "^4.1.1", 65 | "@types/katex": "^0.16.0", 66 | "@types/markdown-it": "^12.2.3", 67 | "@types/markdown-it-link-attributes": "^3.0.1", 68 | "@types/node": "^18.14.6", 69 | "@vitejs/plugin-vue": "^4.0.0", 70 | "autoprefixer": "^10.4.13", 71 | "axios": "^1.3.4", 72 | "crypto-js": "^4.1.1", 73 | "eslint": "^8.35.0", 74 | "http-proxy-middleware": "^2.0.6", 75 | "husky": "^8.0.3", 76 | "less": "^4.1.3", 77 | "lint-staged": "^13.1.2", 78 | "markdown-it-link-attributes": "^4.0.1", 79 | "npm-run-all": "^4.1.5", 80 | "postcss": "^8.4.21", 81 | "rimraf": "^4.2.0", 82 | "svg-sprite-loader": "^6.0.11", 83 | "svgo": "^3.3.2", 84 | "tailwindcss": "^3.3.6", 85 | "typescript": "~4.9.5", 86 | "vite": "^4.2.0", 87 | "vite-plugin-pwa": "^0.14.4", 88 | "vite-plugin-svg-icons": "^2.0.1", 89 | "vitepress": "^1.6.3", 90 | "vue-tsc": "^1.2.0" 91 | }, 92 | "lint-staged": { 93 | "*.{ts,tsx,vue}": [ 94 | "pnpm lint:fix" 95 | ] 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | onlyBuiltDependencies: 2 | - '@vue-office/pdf' 3 | - esbuild 4 | - vue-demi 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/gpts.json: -------------------------------------------------------------------------------- 1 | { 2 | "tag": [ 3 | "图像", 4 | "论文", 5 | "文件", 6 | "识别", 7 | "PDF", 8 | "新闻", 9 | "医生", 10 | "老师", 11 | "Logo" 12 | ], 13 | "gpts": [ 14 | { 15 | "gid": "gpt-4-all", 16 | "name": "gpt-4-all", 17 | "logo": "https://cos.aitutu.cc/gpts/gpt4all.jpg", 18 | "info": "集合官方GPT-4、联网,多模态(gpt-4v),绘图功能(dall-e3),限制不支持function等", 19 | "use_cnt": "8624", 20 | "bad": "0" 21 | }, 22 | { 23 | "gid": "gpt-4-gizmo-g-2fkFE8rbu", 24 | "name": "DALL·E", 25 | "logo": "https://files.oaiusercontent.com/file-SxYQO0Fq1ZkPagkFtg67DRVb?se=2123-10-12T23%3A57%3A32Z&sp=r&sv=2021-08-06&sr=b&rscc=max-age%3D31536000%2C%20immutable&rscd=attachment%3B%20filename%3Dagent_3.webp&sig=pLlQh8oUktqQzhM09SDDxn5aakqFuM2FAPptuA0mbqc%3D", 26 | "info": "让我将你的想象变成图像 – 基于 ChatGPT", 27 | "use_cnt": "3087", 28 | "bad": "1" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /public/pwa-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/public/pwa-192x192.png -------------------------------------------------------------------------------- /public/pwa-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/public/pwa-512x512.png -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 23 | -------------------------------------------------------------------------------- /src/api/chat.ts: -------------------------------------------------------------------------------- 1 | //import { reactive } from 'vue' 2 | 3 | import { gptConfigStore, gptConfigType } from "@/store"; 4 | import { ss } from '@/utils/storage' 5 | import { mlog } from "./mjapi"; 6 | 7 | export class chatSetting{ 8 | private uuid: number; 9 | private localKey='chat-setting'; 10 | //private gptConfig: gptConfigType 11 | // 构造函数 12 | constructor(uuid: number) { 13 | this.uuid = uuid; 14 | //this.gptConfig = gptConfigStore.myData; 15 | //this.init(); 16 | } 17 | 18 | public setUuid(uuid: number){ 19 | this.uuid = uuid; 20 | return this 21 | } 22 | public getGptConfig():gptConfigType { 23 | const index = this.findIndex(); 24 | if( index<=-1) return gptConfigStore.myData; 25 | const arr = this.getObjs(); 26 | return arr[index]; 27 | } 28 | public getObjs():gptConfigType[]{ 29 | const obj = ss.get( this.localKey ) as undefined| gptConfigType[]; 30 | if(!obj) return []; 31 | return obj; 32 | } 33 | public findIndex(){ 34 | return this.getObjs().findIndex(v=>v.uuid && v.uuid==this.uuid ) 35 | } 36 | public save( obj : Partial){ 37 | mlog("chatsave","gogo") 38 | let sobj ={ ...gptConfigStore.myData , ...obj }; 39 | sobj.uuid= this.uuid; 40 | const index = this.findIndex(); 41 | let arr = this.getObjs(); 42 | if( index>-1 )arr[index]= sobj; 43 | else arr.push( sobj ); 44 | ss.set(this.localKey, arr ); 45 | return this ; 46 | } 47 | 48 | 49 | } -------------------------------------------------------------------------------- /src/api/chatmsg.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request/req'; 2 | 3 | export function listByUser(params: { pageNum: number; pageSize: number }) { 4 | return request({ 5 | url: '/system/message/listByUser', 6 | method: 'get', 7 | params 8 | }); 9 | } 10 | 11 | export function getGpts(params: { pageNum: number; pageSize: number }) { 12 | return request({ 13 | url: '/system/gpts/list', 14 | method: 'get', 15 | params 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /src/api/fanyi.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request/req'; 2 | 3 | export function translation(data: any) { 4 | return request({ 5 | url: '/chat/translation', 6 | method: 'post', 7 | data: data 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosProgressEvent, GenericAbortSignal } from 'axios' 2 | import { post } from '@/utils/request' 3 | import { useAuthStore, useSettingStore } from '@/store' 4 | 5 | 6 | export function fetchChatAPI( 7 | prompt: string, 8 | options?: { conversationId?: string; parentMessageId?: string }, 9 | signal?: GenericAbortSignal, 10 | ) { 11 | return post({ 12 | url: '/chat', 13 | data: { prompt, options }, 14 | signal, 15 | }) 16 | } 17 | 18 | export function fetchChatConfig() { 19 | return post({ 20 | url: '/config', 21 | }) 22 | } 23 | 24 | export function fetchChatAPIProcess( 25 | params: { 26 | prompt: string 27 | options?: { conversationId?: string; parentMessageId?: string } 28 | signal?: GenericAbortSignal 29 | onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void }, 30 | ) { 31 | const settingStore = useSettingStore() 32 | const authStore = useAuthStore() 33 | 34 | let data: Record = { 35 | prompt: params.prompt, 36 | options: params.options, 37 | } 38 | 39 | if (authStore.isChatGPTAPI) { 40 | data = { 41 | ...data, 42 | systemMessage: settingStore.systemMessage, 43 | temperature: settingStore.temperature, 44 | top_p: settingStore.top_p, 45 | } 46 | } 47 | 48 | return post({ 49 | url: '/chat-process', 50 | data, 51 | signal: params.signal, 52 | onDownloadProgress: params.onDownloadProgress, 53 | }) 54 | } 55 | 56 | export function fetchSession() { 57 | return post({ 58 | url: '/session', 59 | }) 60 | } 61 | 62 | export function fetchVerify(token: string) { 63 | return post({ 64 | url: '/verify', 65 | data: { token }, 66 | }) 67 | } 68 | 69 | export * from "./mjapi" 70 | export * from "./mjsave" 71 | export * from "./openapi" 72 | export * from "./units" 73 | export * from "./mic" 74 | export * from "./chat" 75 | export * from "./sse/fetchsse" 76 | export * from "./Recognition" 77 | export * from "./luma" 78 | -------------------------------------------------------------------------------- /src/api/knowledge.ts: -------------------------------------------------------------------------------- 1 | import request from "@/utils/request/req"; 2 | 3 | export interface KnowledgeReq { 4 | id: string; // 知识库id 5 | kid: string; // 附件id 6 | uid: string; // 用户id 7 | kname: string; // 知识库名称 8 | description: string; // 知识库描述 9 | } 10 | 11 | export interface KnowledgeDelReq { 12 | id: string; // 附件id 13 | } 14 | 15 | export interface KnowledgeDetailDelReq { 16 | kid: string; // 附件id 17 | docId: string; // 文档id 18 | } 19 | 20 | export interface SimpleGenerate { 21 | model: string; 22 | randomness: number; 23 | stability_boost: number; 24 | voiceId: string; 25 | text: string; 26 | } 27 | 28 | export function getKnowledge() { 29 | return request({ 30 | url: "/knowledge/list", 31 | method: "get", 32 | }); 33 | } 34 | 35 | export function createKnowledgeReq(params: KnowledgeReq) { 36 | return request({ 37 | url: "/knowledge/save", 38 | method: "post", 39 | data: params, 40 | }); 41 | } 42 | 43 | export function delKnowledge(params: KnowledgeDelReq) { 44 | return request({ 45 | url: "/knowledge/remove/" + params.id, 46 | method: "post", 47 | }); 48 | } 49 | 50 | export function getKnowledgeDetail(kid: string) { 51 | return request({ 52 | url: "/knowledge/detail/" + kid, 53 | method: "get", 54 | }); 55 | } 56 | 57 | export function delKnowledgeDetail(params: KnowledgeDetailDelReq) { 58 | return request({ 59 | url: "knowledge/attach/remove/" + params.docId, 60 | method: "post", 61 | }); 62 | } 63 | 64 | export function getfragmentList(docId: string) { 65 | return request({ 66 | url: "/knowledge/fragment/list/" + docId, 67 | method: "get", 68 | }); 69 | } 70 | -------------------------------------------------------------------------------- /src/api/lumaStore.ts: -------------------------------------------------------------------------------- 1 | import { ss } from '@/utils/storage' 2 | 3 | type LumaVideo = { 4 | url: string; 5 | width: number; 6 | height: number; 7 | thumbnail: string | null; 8 | download_url?: string; 9 | }; 10 | 11 | 12 | export type LumaMedia = { 13 | id: string; 14 | prompt: string; 15 | state: string; 16 | created_at?: string; 17 | video?: LumaVideo; 18 | liked?: boolean | null; 19 | estimate_wait_seconds?: number | null; 20 | last_feed?:number 21 | }; 22 | export class lumaStore{ 23 | //private id: string; 24 | private localKey='luma-store'; 25 | public save(obj:LumaMedia ){ 26 | if(!obj.id ) throw "id must"; 27 | let arr= this.getObjs(); 28 | let i= arr.findIndex( v=>v.id==obj.id ); 29 | if(i>-1) arr[i]= obj; 30 | else arr.push(obj); 31 | ss.set(this.localKey, arr ); 32 | return this; 33 | } 34 | public findIndex(id:string){ 35 | return this.getObjs().findIndex( v=>v.id== id ) 36 | } 37 | 38 | public getObjs():LumaMedia[]{ 39 | const obj = ss.get( this.localKey ) as undefined| LumaMedia[]; 40 | if(!obj) return []; 41 | return obj; 42 | } 43 | } -------------------------------------------------------------------------------- /src/api/mic.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const tts=(index:Number)=>{ 4 | 5 | } 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/api/mjsave.ts: -------------------------------------------------------------------------------- 1 | import { gptServerStore, homeStore } from "@/store"; 2 | import localforage from "localforage" 3 | import { mlog } from "./mjapi"; 4 | 5 | localforage.config({ 6 | driver : localforage.INDEXEDDB, // Force WebSQL; same as using setDriver() 7 | name : 'mj', 8 | version : 1.0, 9 | size : 4980736, // Size of database, in bytes. WebSQL-only for now. 10 | storeName : 'mjkv', // Should be alphanumeric, with underscores. 11 | description : 'some description' 12 | }); 13 | 14 | export async function saveImg( key:string, value:string ){ 15 | await localforage.setItem( key, value ) 16 | } 17 | export async function getImg( key:string ): Promise 18 | { 19 | return await localforage.getItem( key ) 20 | } 21 | 22 | //本地存储使用了 23 | export const localSave= async ( key:string, value:any)=>{ 24 | await localforage.setItem( key, value ) 25 | } 26 | //本地存储获取 27 | export const localGet= async( key:string )=>{ 28 | return await localforage.getItem( key ) 29 | } 30 | 31 | export const localSaveAny = async( value:any,key?:string )=>{ 32 | if(!key) key=`MJ:r:${Date.now()}:${Math.floor(Math.random() * 100)}` ; 33 | await localSave(key,value); 34 | return key; 35 | } 36 | 37 | 38 | export function img2base64(img:any) { 39 | let canvas = document.createElement('canvas'); 40 | canvas.width = img.width; 41 | canvas.height = img.height; 42 | let ctx = canvas.getContext('2d'); 43 | if( ! ctx) return ""; 44 | ctx.drawImage(img, 0, 0); 45 | return canvas.toDataURL('image/jpeg'); 46 | } 47 | 48 | export function url2base64More(url:string,key?:string){ 49 | return new Promise<{key:string,base64:string}>((resolve, reject) => { 50 | 51 | const img = new Image(); 52 | img.crossOrigin = "anonymous"; 53 | img.onload=()=>{ 54 | const base64 = img2base64(img) ; 55 | localSaveAny(base64,key).then(d=>resolve({key:d, base64})).catch(e=>reject(e)); 56 | } 57 | img.onerror=(e)=>reject(e); 58 | img.src = url; 59 | }); 60 | 61 | } 62 | 63 | export const url2base64= async (url:string,key?:string)=>{ 64 | try{ 65 | return await url2base64More (url,key); 66 | }catch(e){ 67 | return await url2base64More( wsrvUrl(url) ,key); 68 | } 69 | } 70 | 71 | export const wsrvUrl=(url:string)=>{ 72 | const arr = url.split(/([a-z\-]+)ttachments/ig, 3 ); 73 | if( arr.length==3){ 74 | url= `https://cdn.discordapp.com/${arr[1]}ttachments`+ arr[2]; 75 | } 76 | return `https://wsrv.nl/?url=`+ encodeURIComponent(url); 77 | } 78 | 79 | export const mjImgUrl= (url:string)=>{ 80 | if (gptServerStore.myData.MJ_CDN_WSRV || homeStore.myData.session.isWsrv ) return wsrvUrl(url); 81 | return url; 82 | } 83 | 84 | export const getMjAll= async ( ChatState:Chat.ChatState)=>{ 85 | let rz:Chat.Chat[]=[] 86 | ChatState.chat.forEach(v=>{ 87 | // mlog('uid>>', v.uuid ); 88 | v.data.forEach(chat=>{ 89 | if( chat.mjID ){ 90 | // mlog('MJID>> ',chat.mjID); 91 | rz.push(chat ); 92 | } 93 | }) 94 | }); 95 | return rz ; 96 | 97 | } -------------------------------------------------------------------------------- /src/api/model.ts: -------------------------------------------------------------------------------- 1 | import request from "@/utils/request/req"; 2 | 3 | /** 4 | * 查询未隐藏模型 5 | * @returns 6 | */ 7 | export function modelList() { 8 | return request({ 9 | url: "/system/model/modelList", 10 | method: "get", 11 | }); 12 | } 13 | 14 | /** 15 | * 查询所有模型 16 | * 17 | * @returns 18 | */ 19 | export function list(category: string) { 20 | return request({ 21 | url: "/system/model/list", 22 | method: "get", 23 | params: { 24 | category, 25 | }, 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /src/api/pay.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request/req'; 2 | export interface OrderReq { 3 | money:string; // 商品价格 4 | name:string; //商品名称 5 | } 6 | 7 | export interface VoucherVO { 8 | /** 9 | * 主键 10 | */ 11 | id: string | number; 12 | 13 | /** 14 | * 用户id 15 | */ 16 | userId: string | number; 17 | 18 | /** 19 | * 兑换码 20 | */ 21 | code: string; 22 | 23 | /** 24 | * 兑换金额 25 | */ 26 | amount: number; 27 | 28 | /** 29 | * 兑换状态 30 | */ 31 | status: string; 32 | 33 | /** 34 | * 兑换前余额 35 | */ 36 | balanceBefore: number; 37 | 38 | /** 39 | * 兑换后余额 40 | */ 41 | balanceAfter: number; 42 | 43 | /** 44 | * 备注 45 | */ 46 | remark: string; 47 | 48 | } 49 | 50 | export function payUrl(params:OrderReq) { 51 | return request({ 52 | url: '/pay/payUrl', 53 | method: 'post', 54 | data: params, 55 | }) 56 | } 57 | 58 | export function getSPayUrl(params:OrderReq) { 59 | return request({ 60 | url: '/pay/stripePay', 61 | method: 'post', 62 | data: params, 63 | }) 64 | } 65 | 66 | export function getOrderInfo(orderNo:string) { 67 | return request({ 68 | url: '/pay/orderInfo', 69 | method: 'post', 70 | data: {"orderNo":orderNo} 71 | }); 72 | } 73 | 74 | export function redeemKey(params:VoucherVO) { 75 | return request({ 76 | url: '/system/voucher/redeem', 77 | method: 'post', 78 | data: params 79 | }); 80 | } 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/api/ppt.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request/req'; 2 | 3 | // 获取API Token 4 | export function getApiToken() { 5 | return request({ 6 | url: '/system/model/getPPT', 7 | method: 'get', 8 | }) 9 | } 10 | 11 | // 成功回调 12 | export function successCallback() { 13 | return request({ 14 | url: '/ppt/successCallback', 15 | method: 'post', 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /src/api/sse/fetch.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | const fetch = globalThis.fetch 4 | 5 | export { fetch } 6 | -------------------------------------------------------------------------------- /src/api/sse/fetchsse.ts: -------------------------------------------------------------------------------- 1 | import { createParser } from 'eventsource-parser' 2 | 3 | import * as types from './types' 4 | import { fetch as globalFetch } from './fetch' 5 | import { streamAsyncIterable } from './stream-async-iterable' 6 | 7 | export class ChatGPTError2 extends types.ChatGPTError{ 8 | reason?:string 9 | } 10 | export async function fetchSSE( 11 | url: string, 12 | options: Parameters[1] & { 13 | onMessage: (data: string) => void 14 | onError?: (error: any) => void 15 | }, 16 | fetch: types.FetchFn = globalFetch 17 | ) { 18 | const { onMessage, onError, ...fetchOptions } = options 19 | let res ; 20 | try{ 21 | res = await fetch(url, fetchOptions) 22 | console.log("resbody==========",res.body) 23 | }catch(e :any ){ 24 | throw {reason: JSON.stringify({message:'fetch error, pleace check url',url ,code:'fetch_error'}) } 25 | } 26 | if (!res.ok) { 27 | let reason: string 28 | 29 | try { 30 | reason = await res.text() 31 | } catch (err) { 32 | reason = res.statusText 33 | } 34 | 35 | const msg = `ChatGPT error ${res.status}: ${reason}` 36 | const error = new ChatGPTError2(msg, { cause: res }) 37 | error.statusCode = res.status 38 | error.statusText = res.statusText 39 | error.reason =reason 40 | throw error 41 | } 42 | 43 | const parser = createParser((event) => { 44 | if (event.type === 'event') { 45 | onMessage(event.data) 46 | } 47 | }) 48 | 49 | // handle special response errors 50 | const feed = (chunk: string) => { 51 | let response = null 52 | 53 | try { 54 | response = JSON.parse(chunk) 55 | } catch { 56 | // ignore 57 | } 58 | 59 | if (response?.detail?.type === 'invalid_request_error') { 60 | const msg = `ChatGPT error ${response.detail.message}: ${response.detail.code} (${response.detail.type})` 61 | const error = new types.ChatGPTError(msg, { cause: response }) 62 | error.statusCode = response.detail.code 63 | error.statusText = response.detail.message 64 | 65 | if (onError) { 66 | onError(error) 67 | } else { 68 | console.error(error) 69 | } 70 | 71 | // don't feed to the event parser 72 | return 73 | } 74 | 75 | parser.feed(chunk) 76 | } 77 | 78 | if (!res.body.getReader) { 79 | // Vercel polyfills `fetch` with `node-fetch`, which doesn't conform to 80 | // web standards, so this is a workaround... 81 | const body: NodeJS.ReadableStream = res.body as any 82 | 83 | if (!body.on || !body.read) { 84 | throw new types.ChatGPTError('unsupported "fetch" implementation') 85 | } 86 | 87 | body.on('readable', () => { 88 | let chunk: string | Buffer 89 | while (null !== (chunk = body.read())) { 90 | feed(chunk.toString()) 91 | } 92 | }) 93 | } else { 94 | for await (const chunk of streamAsyncIterable(res.body)) { 95 | const str = new TextDecoder().decode(chunk) 96 | //console.log(str ); 97 | feed(str) 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/api/sse/stream-async-iterable.ts: -------------------------------------------------------------------------------- 1 | export async function* streamAsyncIterable(stream: ReadableStream) { 2 | const reader = stream.getReader() 3 | try { 4 | while (true) { 5 | const { done, value } = await reader.read() 6 | if (done) { 7 | return 8 | } 9 | yield value 10 | } 11 | } finally { 12 | reader.releaseLock() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/api/store.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request/req'; 2 | 3 | export interface RoleReq { 4 | name:string; // 名称 5 | description:string; //描述 6 | avatar: string; //头像地址 7 | appUrl:string;//应用地址 8 | } 9 | 10 | export interface SimpleGenerate { 11 | model: string, 12 | randomness: number, 13 | stability_boost: number, 14 | voiceId: string, 15 | text: string 16 | } 17 | 18 | export interface Character { 19 | id:string; 20 | name:string; // 名称 21 | description:string; //描述 22 | avatar: string; //头像地址 23 | appUrl:string;//应用地址 24 | } 25 | 26 | export function createRole(params:RoleReq) { 27 | return request({ 28 | url: '/system/voice/add', 29 | method: 'post', 30 | data: params, 31 | }) 32 | } 33 | 34 | 35 | export function simpleGenerateReq(params:SimpleGenerate) { 36 | return request({ 37 | url: '/system/voice/simpleGenerate', 38 | method: 'post', 39 | data: params, 40 | }) 41 | } 42 | 43 | export function delRole(id:string) { 44 | return request({ 45 | url: '/system/voice/'+ id, 46 | method: 'delete', 47 | }) 48 | } 49 | 50 | export function getRole() { 51 | return request({ 52 | url: '/system/voice/list', 53 | method: 'get', 54 | }) 55 | } 56 | 57 | /** 58 | * 获取应用信息 59 | * 60 | * @returns 应用列表 61 | * 62 | */ 63 | export function getAppList() { 64 | return request({ 65 | url: '/system/store/appList', 66 | method: 'get' 67 | }) 68 | } 69 | 70 | /** 71 | * 收藏声音市场角色 72 | * 73 | */ 74 | export function copyRoleList(item: any) { 75 | return request({ 76 | url: '/system/voice/copyRole', 77 | method: 'post', 78 | data: item 79 | }) 80 | } 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/api/sunoStore.ts: -------------------------------------------------------------------------------- 1 | import { ss } from '@/utils/storage' 2 | 3 | export type SunoMedia = { 4 | id: string; 5 | video_url: string; 6 | audio_url: string; 7 | image_url: string; 8 | image_large_url: string; 9 | is_video_pending: boolean; 10 | major_model_version: string; 11 | model_name: string; 12 | metadata: { 13 | tags?: string; 14 | prompt: string; 15 | gpt_description_prompt?: string ; 16 | audio_prompt_id?: string ; 17 | history?: string ; 18 | concat_history?: string ; 19 | type: string; 20 | duration: number; 21 | refund_credits: boolean; 22 | stream: boolean; 23 | error_type?: string ; 24 | error_message?: string ; 25 | }; 26 | is_liked: boolean; 27 | user_id: string; 28 | display_name: string; 29 | handle: string; 30 | is_handle_updated: boolean; 31 | is_trashed: boolean; 32 | reaction?: any; // You might want to define a proper type for this 33 | created_at: string; 34 | status: string; 35 | title: string; 36 | play_count: number; 37 | upvote_count: number; 38 | is_public: boolean; 39 | }; 40 | export class sunoStore{ 41 | //private id: string; 42 | private localKey='suno-store'; 43 | public save(obj:SunoMedia ){ 44 | if(!obj.id ) throw "id must"; 45 | let arr= this.getObjs(); 46 | let i= arr.findIndex( v=>v.id==obj.id ); 47 | if(i>-1) arr[i]= obj; 48 | else arr.push(obj); 49 | ss.set(this.localKey, arr ); 50 | return this; 51 | } 52 | public findIndex(id:string){ 53 | return this.getObjs().findIndex( v=>v.id== id ) 54 | } 55 | 56 | public getObjs():SunoMedia[]{ 57 | const obj = ss.get( this.localKey ) as undefined| SunoMedia[]; 58 | if(!obj) return []; 59 | return obj; 60 | } 61 | } -------------------------------------------------------------------------------- /src/api/units.ts: -------------------------------------------------------------------------------- 1 | import { homeStore } from "@/store" 2 | 3 | export const checkDisableGpt4=( model:string )=>{ 4 | if(!homeStore.myData.session.disableGpt4 || homeStore.myData.session.disableGpt4!='1') return false; 5 | const rz = model.indexOf('gpt-4')>-1 ; 6 | if(rz){ 7 | homeStore.setMyData({isLoader:false,act:'stopLoading'}); 8 | } 9 | return rz ; 10 | 11 | } 12 | 13 | export const isApikeyError=( text:string )=>{ 14 | text= text.toLocaleLowerCase(); 15 | if( 16 | text.indexOf('error') && ( 17 | text.indexOf('无效的令牌')>-1 //one-api 错误 18 | || text.indexOf('incorrect api key')>-1 //原生 19 | )) return true ; 20 | return false; 21 | } 22 | export const isAuthSessionError = ( text:string )=>{ 23 | text= text.toLocaleLowerCase(); 24 | if(text.indexOf('token_check')>-1 ) return true ; 25 | return false; 26 | } -------------------------------------------------------------------------------- /src/api/user.ts: -------------------------------------------------------------------------------- 1 | import {LoginFrom} from "@/typings/user"; 2 | import request from '@/utils/request/req'; 3 | /** 4 | * 用户登录 5 | */ 6 | export function doLogin(params:LoginFrom) { 7 | return request({ 8 | url: '/auth/login', 9 | headers: { 10 | isToken: false 11 | }, 12 | method: 'post', 13 | data: params 14 | }); 15 | } 16 | 17 | /** 18 | * 用户注册 19 | */ 20 | export function doRegist(username: string, password: string,code:string) { 21 | return request({ 22 | url: '/auth/register', 23 | method: 'post', 24 | data: {username, password,code}, 25 | }) 26 | } 27 | 28 | /** 29 | * 重置密码 30 | */ 31 | export function reset(username: string, password: string,code:string) { 32 | return request({ 33 | url: '/auth/reset/password', 34 | method: 'post', 35 | data: {username, password,code}, 36 | }) 37 | } 38 | 39 | /** 40 | * 获取邮箱验证码 41 | */ 42 | export function getVerificationCode(username:string) { 43 | return request({ 44 | url:'/resource/email/code', 45 | method: 'post', 46 | data:{username} 47 | }) 48 | } 49 | 50 | 51 | /** 52 | * 获取用户登录信息 53 | */ 54 | export function getUserInfo() { 55 | return request({ 56 | url:'/system/user/getInfo', 57 | method: 'get', 58 | }) 59 | } 60 | 61 | /** 62 | * 修改用户名称 63 | */ 64 | export function editUserNmae(nickName:string) { 65 | return request({ 66 | url:'/system/user/editName', 67 | method: 'post', 68 | data:{nickName} 69 | }) 70 | } 71 | 72 | /** 73 | * 退出登录 74 | */ 75 | export function loginOut() { 76 | return request({ 77 | url:'/auth/logout', 78 | method: 'post', 79 | }) 80 | } 81 | 82 | // 根据参数键名查询参数值 83 | export function getConfigKey(configKey: string){ 84 | return request({ 85 | url: '/chat/config/configKey/' + configKey, 86 | method: 'get' 87 | }); 88 | } 89 | 90 | // 根据授权编码激活系统 91 | export function authSystem(code: string){ 92 | return request({ 93 | url: '/chat/config/authSystem/' + code, 94 | method: 'post' 95 | }); 96 | } 97 | 98 | // 获取登录二维码 99 | export function getMpQrCode(){ 100 | return request({ 101 | url: '/user/qrcode', 102 | method: 'get' 103 | }); 104 | } 105 | 106 | // 查询登陆状态 107 | export function getLoginType(ticket:string){ 108 | const encodedTicket = encodeURIComponent(ticket); 109 | return request({ 110 | url: `/user/login/qrcode?ticket=${encodedTicket}`, 111 | method: 'get' 112 | }); 113 | } -------------------------------------------------------------------------------- /src/assets/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/src/assets/01.png -------------------------------------------------------------------------------- /src/assets/Subtract-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/src/assets/Subtract-2.png -------------------------------------------------------------------------------- /src/assets/Subtract-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/src/assets/Subtract-3.png -------------------------------------------------------------------------------- /src/assets/Subtract-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/src/assets/Subtract-active.png -------------------------------------------------------------------------------- /src/assets/Subtract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/src/assets/Subtract.png -------------------------------------------------------------------------------- /src/assets/ageer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/src/assets/ageer.png -------------------------------------------------------------------------------- /src/assets/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/src/assets/avatar.jpg -------------------------------------------------------------------------------- /src/assets/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/src/assets/background.jpg -------------------------------------------------------------------------------- /src/assets/fileType.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageerle/ruoyi-web/276b5ea4f48767a007e25abd3c51869742a7ad6f/src/assets/fileType.png -------------------------------------------------------------------------------- /src/assets/icons/Gallery.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/Logout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/Music.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/Vector.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/block-0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/block-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/block-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/block-3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/gou.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/message.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/assets/icons/music1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/screenshot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/send.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/icons/video.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/voice.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/recommend.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "key": "awesome-chatgpt-prompts-zh", 4 | "desc": "ChatGPT 中文调教指南", 5 | "downloadUrl": "https://raw.githubusercontent.com/PlexPt/awesome-chatgpt-prompts-zh/main/prompts-zh.json", 6 | "url": "https://github.com/PlexPt/awesome-chatgpt-prompts-zh" 7 | }, 8 | { 9 | "key": "awesome-chatgpt-prompts-zh-TW", 10 | "desc": "ChatGPT 中文調教指南 (透過 OpenAI / OpenCC 協助,從簡體中文轉換為繁體中文的版本)", 11 | "downloadUrl": "https://raw.githubusercontent.com/PlexPt/awesome-chatgpt-prompts-zh/main/prompts-zh-TW.json", 12 | "url": "https://github.com/PlexPt/awesome-chatgpt-prompts-zh" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /src/components/common/HoverButton/Button.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /src/components/common/HoverButton/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 47 | -------------------------------------------------------------------------------- /src/components/common/IconSvg/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 35 | 36 | 42 | -------------------------------------------------------------------------------- /src/components/common/NaiveProvider/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 44 | -------------------------------------------------------------------------------- /src/components/common/Setting/About.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 54 | -------------------------------------------------------------------------------- /src/components/common/Setting/Advanced.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 71 | -------------------------------------------------------------------------------- /src/components/common/Setting/index.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 63 | -------------------------------------------------------------------------------- /src/components/common/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /src/components/common/UserAvatar/index.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 80 | -------------------------------------------------------------------------------- /src/components/common/index.ts: -------------------------------------------------------------------------------- 1 | import HoverButton from './HoverButton/index.vue' 2 | import NaiveProvider from './NaiveProvider/index.vue' 3 | import SvgIcon from './SvgIcon/index.vue' 4 | import UserAvatar from './UserAvatar/index.vue' 5 | import Setting from './Setting/index.vue' 6 | import PromptStore from './PromptStore/index.vue' 7 | 8 | 9 | export { HoverButton, NaiveProvider, SvgIcon, UserAvatar, Setting, PromptStore } 10 | -------------------------------------------------------------------------------- /src/components/custom/GithubSite.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /src/components/custom/index.ts: -------------------------------------------------------------------------------- 1 | import GithubSite from './GithubSite.vue' 2 | 3 | export { GithubSite } 4 | -------------------------------------------------------------------------------- /src/enums/RespEnum.ts: -------------------------------------------------------------------------------- 1 | export enum HttpStatus { 2 | /** 3 | * 操作成功 4 | */ 5 | SUCCESS = 200, 6 | /** 7 | * 对象创建成功 8 | */ 9 | CREATED = 201, 10 | /** 11 | * 请求已经被接受 12 | */ 13 | ACCEPTED = 202, 14 | /** 15 | * 操作已经执行成功,但是没有返回数据 16 | */ 17 | NO_CONTENT = 204, 18 | /** 19 | * 资源已经被移除 20 | */ 21 | MOVED_PERM = 301, 22 | /** 23 | * 重定向 24 | */ 25 | SEE_OTHER = 303, 26 | /** 27 | * 资源没有被修改 28 | */ 29 | NOT_MODIFIED = 304, 30 | /** 31 | * 参数列表错误(缺少,格式不匹配) 32 | */ 33 | PARAM_ERROR = 400, 34 | /** 35 | * 未授权 36 | */ 37 | UNAUTHORIZED = 401, 38 | /** 39 | * 访问受限,授权过期 40 | */ 41 | FORBIDDEN = 403, 42 | /** 43 | * 资源,服务未找到 44 | */ 45 | NOT_FOUND = 404, 46 | /** 47 | * 不允许的http方法 48 | */ 49 | BAD_METHOD = 405, 50 | /** 51 | * 资源冲突,或者资源被锁 52 | */ 53 | CONFLICT = 409, 54 | /** 55 | * 不支持的数据,媒体类型 56 | */ 57 | UNSUPPORTED_TYPE = 415, 58 | /** 59 | * 系统内部错误 60 | */ 61 | SERVER_ERROR = 500, 62 | /** 63 | * 接口未实现 64 | */ 65 | NOT_IMPLEMENTED = 501, 66 | /** 67 | * 服务不可用,过载或者维护 68 | */ 69 | BAD_GATEWAY = 502, 70 | /** 71 | * 网关超时 72 | */ 73 | GATEWAY_TIMEOUT = 504, 74 | /** 75 | * 未知错误 76 | */ 77 | UNKNOWN_ERROR = 520, 78 | /** 79 | * 服务未知错误 80 | */ 81 | SERVICE_ERROR = 521, 82 | /** 83 | * 数据库未知错误 84 | */ 85 | DATABASE_ERROR = 522, 86 | /** 87 | * 系统警告消息 88 | */ 89 | WARN = 601 90 | } 91 | -------------------------------------------------------------------------------- /src/hooks/useBasicLayout.ts: -------------------------------------------------------------------------------- 1 | import { breakpointsTailwind, useBreakpoints } from '@vueuse/core' 2 | 3 | export function useBasicLayout() { 4 | const breakpoints = useBreakpoints(breakpointsTailwind) 5 | const isMobile = breakpoints.smaller('sm') 6 | 7 | return { isMobile } 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/useIconRender.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue' 2 | import { SvgIcon } from '@/components/common' 3 | 4 | export const useIconRender = () => { 5 | interface IconConfig { 6 | icon?: string 7 | color?: string 8 | fontSize?: number 9 | } 10 | 11 | interface IconStyle { 12 | color?: string 13 | fontSize?: string 14 | } 15 | 16 | const iconRender = (config: IconConfig) => { 17 | const { color, fontSize, icon } = config 18 | 19 | const style: IconStyle = {} 20 | 21 | if (color) 22 | style.color = color 23 | 24 | if (fontSize) 25 | style.fontSize = `${fontSize}px` 26 | 27 | if (!icon) 28 | window.console.warn('iconRender: icon is required') 29 | 30 | return () => h(SvgIcon, { icon, style }) 31 | } 32 | 33 | return { 34 | iconRender, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/hooks/useLanguage.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue' 2 | import { enUS, koKR, zhCN, zhTW } from 'naive-ui' 3 | import { useAppStore } from '@/store' 4 | import { setLocale } from '@/locales' 5 | 6 | export function useLanguage() { 7 | const appStore = useAppStore() 8 | 9 | const language = computed(() => { 10 | switch (appStore.language) { 11 | case 'en-US': 12 | setLocale('en-US') 13 | return enUS 14 | case 'ru-RU': 15 | setLocale('ru-RU') 16 | return enUS 17 | case 'ko-KR': 18 | setLocale('ko-KR') 19 | return koKR 20 | case 'zh-CN': 21 | setLocale('zh-CN') 22 | return zhCN 23 | case 'zh-TW': 24 | setLocale('zh-TW') 25 | return zhTW 26 | default: 27 | setLocale('zh-CN') 28 | return zhCN 29 | } 30 | }) 31 | 32 | return { language } 33 | } 34 | -------------------------------------------------------------------------------- /src/hooks/useTheme.ts: -------------------------------------------------------------------------------- 1 | import type { GlobalThemeOverrides } from 'naive-ui' 2 | import { computed, watch } from 'vue' 3 | import { darkTheme, useOsTheme } from 'naive-ui' 4 | import { useAppStore } from '@/store' 5 | import { useBasicLayout } from '@/hooks/useBasicLayout' 6 | 7 | export function useTheme() { 8 | const appStore = useAppStore() 9 | 10 | const OsTheme = useOsTheme() 11 | 12 | const isDark = computed(() => { 13 | if (appStore.theme === 'auto') 14 | return OsTheme.value === 'dark' 15 | else 16 | return appStore.theme === 'dark' 17 | }) 18 | 19 | const theme = computed(() => { 20 | return isDark.value ? darkTheme : undefined 21 | }) 22 | 23 | const themeOverrides = computed(() => { 24 | if (isDark.value) { 25 | return { 26 | common: {}, 27 | } 28 | } 29 | return {} 30 | }) 31 | const { isMobile } = useBasicLayout() 32 | watch( 33 | () => isDark.value, 34 | (dark) => { 35 | if (dark) 36 | document.documentElement.classList.add('dark') 37 | else 38 | document.documentElement.classList.remove('dark') 39 | }, 40 | { immediate: true }, 41 | ) 42 | watch( 43 | () => isMobile.value, 44 | (dark) => { 45 | if (dark) 46 | document.documentElement.classList.add('is-mobile') 47 | else 48 | document.documentElement.classList.remove('is-mobile') 49 | }, 50 | { immediate: true }, 51 | ) 52 | 53 | return { theme, themeOverrides } 54 | } 55 | -------------------------------------------------------------------------------- /src/icons.ts: -------------------------------------------------------------------------------- 1 | // src/icons.js 2 | function importAll(r:any) { 3 | r.keys().forEach(r); 4 | } 5 | 6 | // 动态导入 ./assets/icons 文件夹中的所有 .svg 文件 7 | importAll(require.context('./assets/icons', false, /\.svg$/)); 8 | -------------------------------------------------------------------------------- /src/locales/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import { createI18n } from 'vue-i18n' 3 | import enUS from './en-US' 4 | import koKR from './ko-KR' 5 | import zhCN from './zh-CN' 6 | import zhTW from './zh-TW' 7 | import ruRU from './ru-RU' 8 | import viVn from './vi-VN' 9 | import frFr from './fr-FR' 10 | import trTr from './tr-TR' 11 | import { useAppStoreWithOut } from '@/store/modules/app' 12 | import type { Language } from '@/store/modules/app/helper' 13 | 14 | const appStore = useAppStoreWithOut() 15 | 16 | const defaultLocale = appStore.language || 'zh-CN' 17 | 18 | const i18n = createI18n({ 19 | locale: defaultLocale, 20 | fallbackLocale: 'en-US', 21 | allowComposition: true, 22 | messages: { 23 | 'en-US': enUS, 24 | 'ko-KR': koKR, 25 | 'zh-CN': zhCN, 26 | 'zh-TW': zhTW, 27 | 'ru-RU': ruRU, 28 | 'vi-VN': viVn, 29 | 'fr-FR': frFr, 30 | 'tr-TR': trTr, 31 | }, 32 | }) 33 | 34 | export const t = i18n.global.t 35 | 36 | export function setLocale(locale: Language) { 37 | i18n.global.locale = locale 38 | } 39 | 40 | export function setupI18n(app: App) { 41 | app.use(i18n) 42 | } 43 | 44 | export default i18n 45 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import { setupI18n } from './locales' 4 | import { setupAssets, setupScrollbarStyle } from './plugins' 5 | import { setupStore } from './store' 6 | import { setupRouter } from './router' 7 | import 'virtual:svg-icons-register'; // 引入虚拟的 svg 图标模块 8 | import IconSvg from '@/components/common/IconSvg/index.vue'; 9 | 10 | async function bootstrap() { 11 | const app = createApp(App) 12 | // 全局注册 SvgIcon 组件 13 | app.component('IconSvg', IconSvg); 14 | setupAssets() 15 | 16 | setupScrollbarStyle() 17 | 18 | setupStore(app) 19 | 20 | setupI18n(app) 21 | 22 | await setupRouter(app) 23 | 24 | app.mount('#app') 25 | } 26 | 27 | bootstrap() 28 | -------------------------------------------------------------------------------- /src/plugins/assets.ts: -------------------------------------------------------------------------------- 1 | import 'katex/dist/katex.min.css' 2 | import '@/styles/lib/tailwind.css' 3 | import '@/styles/lib/highlight.less' 4 | import '@/styles/lib/github-markdown.less' 5 | import '@/styles/global.less' 6 | 7 | /** Tailwind's Preflight Style Override */ 8 | function naiveStyleOverride() { 9 | const meta = document.createElement('meta') 10 | meta.name = 'naive-ui-style' 11 | document.head.appendChild(meta) 12 | } 13 | 14 | function setupAssets() { 15 | naiveStyleOverride() 16 | } 17 | 18 | export default setupAssets 19 | -------------------------------------------------------------------------------- /src/plugins/cache.ts: -------------------------------------------------------------------------------- 1 | const sessionCache = { 2 | set(key: string, value: any) { 3 | if (!sessionStorage) { 4 | return; 5 | } 6 | if (key != null && value != null) { 7 | sessionStorage.setItem(key, value); 8 | } 9 | }, 10 | get(key: string) { 11 | if (!sessionStorage) { 12 | return null; 13 | } 14 | if (key == null) { 15 | return null; 16 | } 17 | return sessionStorage.getItem(key); 18 | }, 19 | setJSON(key: string, jsonValue: any) { 20 | if (jsonValue != null) { 21 | this.set(key, JSON.stringify(jsonValue)); 22 | } 23 | }, 24 | getJSON(key: string) { 25 | const value = this.get(key); 26 | if (value != null) { 27 | return JSON.parse(value); 28 | } 29 | }, 30 | remove(key: string) { 31 | sessionStorage.removeItem(key); 32 | } 33 | }; 34 | const localCache = { 35 | set(key: string, value: any) { 36 | if (!localStorage) { 37 | return; 38 | } 39 | if (key != null && value != null) { 40 | localStorage.setItem(key, value); 41 | } 42 | }, 43 | get(key: string) { 44 | if (!localStorage) { 45 | return null; 46 | } 47 | if (key == null) { 48 | return null; 49 | } 50 | return localStorage.getItem(key); 51 | }, 52 | setJSON(key: string, jsonValue: any) { 53 | if (jsonValue != null) { 54 | this.set(key, JSON.stringify(jsonValue)); 55 | } 56 | }, 57 | getJSON(key: string) { 58 | const value = this.get(key); 59 | if (value != null) { 60 | return JSON.parse(value); 61 | } 62 | }, 63 | remove(key: string) { 64 | localStorage.removeItem(key); 65 | } 66 | }; 67 | 68 | export default { 69 | /** 70 | * 会话级缓存 71 | */ 72 | session: sessionCache, 73 | /** 74 | * 本地缓存 75 | */ 76 | local: localCache 77 | }; 78 | -------------------------------------------------------------------------------- /src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | import setupAssets from './assets' 2 | import setupScrollbarStyle from './scrollbarStyle' 3 | 4 | export { setupAssets, setupScrollbarStyle } 5 | -------------------------------------------------------------------------------- /src/plugins/scrollbarStyle.ts: -------------------------------------------------------------------------------- 1 | import { darkTheme, lightTheme } from 'naive-ui' 2 | 3 | const setupScrollbarStyle = () => { 4 | const style = document.createElement('style') 5 | const styleContent = ` 6 | ::-webkit-scrollbar { 7 | background-color: transparent; 8 | width: ${lightTheme.Scrollbar.common?.scrollbarWidth}; 9 | } 10 | ::-webkit-scrollbar-thumb { 11 | background-color: ${lightTheme.Scrollbar.common?.scrollbarColor}; 12 | border-radius: ${lightTheme.Scrollbar.common?.scrollbarBorderRadius}; 13 | } 14 | html.dark ::-webkit-scrollbar { 15 | background-color: transparent; 16 | width: ${darkTheme.Scrollbar.common?.scrollbarWidth}; 17 | } 18 | html.dark ::-webkit-scrollbar-thumb { 19 | background-color: ${darkTheme.Scrollbar.common?.scrollbarColor}; 20 | border-radius: ${darkTheme.Scrollbar.common?.scrollbarBorderRadius}; 21 | } 22 | ` 23 | 24 | style.innerHTML = styleContent 25 | document.head.appendChild(style) 26 | } 27 | 28 | export default setupScrollbarStyle 29 | -------------------------------------------------------------------------------- /src/router/permission.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router' 2 | import { ref } from 'vue'; 3 | 4 | const whiteList = ['/login', '/regist','/resetpassword']; // 不重定向白名单 5 | 6 | export function setupPageGuard(router: Router) { 7 | router.beforeEach(async (to, from, next) => { 8 | // 检查浏览器语言 9 | // const browserLanguage = navigator.language || navigator.language; 10 | // const isChineseLanguage = browserLanguage.includes('zh'); 11 | // const isInChina = ref(false); 12 | // const response = await fetch('http://ip-api.com/json/'); 13 | // const data = await response.json(); 14 | // isInChina.value = data.countryCode === 'CN'; 15 | // console.log("isInChina", isInChina.value,isChineseLanguage) 16 | // // 中国大陆的国家代码为 "CN" 17 | // if (isChineseLanguage && isInChina.value) { 18 | // alert("该网站不支持大陆ip") 19 | // // 如果是中文环境,则重定向到 zh.html 20 | // window.location.href = '/index.htm'; 21 | // } else { 22 | // next(); 23 | // console.log("next===") 24 | // } 25 | next(); 26 | 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /src/store/helper.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia' 2 | 3 | export const store = createPinia() 4 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import { store } from './helper' 3 | 4 | export function setupStore(app: App) { 5 | app.use(store) 6 | } 7 | 8 | export * from './modules' 9 | export * from "./homeStore" 10 | -------------------------------------------------------------------------------- /src/store/modules/app/helper.ts: -------------------------------------------------------------------------------- 1 | import { ss } from '@/utils/storage' 2 | 3 | const LOCAL_NAME = 'appSetting' 4 | 5 | export type Theme = 'light' | 'dark' | 'auto' 6 | 7 | export type Language = 'zh-CN' | 'zh-TW' | 'en-US' | 'ko-KR' | 'ru-RU' 8 | 9 | export interface AppState { 10 | siderCollapsed: boolean 11 | isChat: boolean 12 | theme: Theme 13 | language: Language 14 | } 15 | 16 | export function defaultSetting(): AppState { 17 | return { siderCollapsed: false,isChat:true, theme: 'light', language: 'zh-CN' } 18 | } 19 | 20 | export function getLocalSetting(): AppState { 21 | const localSetting: AppState | undefined = ss.get(LOCAL_NAME) 22 | return { ...defaultSetting(), ...localSetting } 23 | } 24 | 25 | export function setLocalSetting(setting: AppState): void { 26 | ss.set(LOCAL_NAME, setting) 27 | } 28 | -------------------------------------------------------------------------------- /src/store/modules/app/index.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import type { AppState, Language, Theme } from './helper' 3 | import { getLocalSetting, setLocalSetting } from './helper' 4 | import { store } from '@/store/helper' 5 | 6 | export const useAppStore = defineStore('app-store', { 7 | state: (): AppState => getLocalSetting(), 8 | actions: { 9 | setSiderCollapsed(collapsed: boolean) { 10 | this.siderCollapsed = collapsed 11 | this.recordState() 12 | }, 13 | setIsChat(chat: boolean) { 14 | this.isChat = chat 15 | this.recordState() 16 | }, 17 | 18 | setTheme(theme: Theme) { 19 | this.theme = theme 20 | this.recordState() 21 | }, 22 | 23 | setLanguage(language: Language) { 24 | if (this.language !== language) { 25 | this.language = language 26 | this.recordState() 27 | } 28 | }, 29 | 30 | recordState() { 31 | setLocalSetting(this.$state) 32 | }, 33 | }, 34 | }) 35 | 36 | export function useAppStoreWithOut() { 37 | return useAppStore(store) 38 | } 39 | -------------------------------------------------------------------------------- /src/store/modules/auth/anth.ts: -------------------------------------------------------------------------------- 1 | import { useStorage } from "@vueuse/core"; 2 | 3 | const TokenKey = 'RuoYi-Token'; 4 | 5 | const tokenStorage = useStorage(TokenKey, null); 6 | 7 | export const getToken = () => tokenStorage.value; 8 | 9 | export const setToken = (token: string) => (tokenStorage.value = token); 10 | 11 | export const removeToken = () => (tokenStorage.value = null); 12 | -------------------------------------------------------------------------------- /src/store/modules/auth/helper.ts: -------------------------------------------------------------------------------- 1 | import { ss } from '@/utils/storage' 2 | 3 | const LOCAL_NAME = 'TOKEN' 4 | 5 | export function getToken() { 6 | return ss.get(LOCAL_NAME) 7 | } 8 | 9 | export function setToken(token: string) { 10 | return ss.set(LOCAL_NAME, token) 11 | } 12 | 13 | export function removeToken() { 14 | return ss.remove(LOCAL_NAME) 15 | } 16 | -------------------------------------------------------------------------------- /src/store/modules/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { getToken, removeToken, setToken } from './helper' 3 | import { store } from '@/store/helper' 4 | import { fetchSession } from '@/api' 5 | import { gptConfigStore, homeStore } from '@/store/homeStore' 6 | 7 | interface SessionResponse { 8 | auth: boolean 9 | model: 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' 10 | } 11 | 12 | export interface AuthState { 13 | token: string | undefined 14 | session: SessionResponse | null 15 | } 16 | 17 | export const useAuthStore = defineStore('auth-store', { 18 | state: (): AuthState => ({ 19 | token: getToken(), 20 | session: null, 21 | }), 22 | 23 | getters: { 24 | isChatGPTAPI(state): boolean { 25 | return state.session?.model === 'ChatGPTAPI' 26 | }, 27 | }, 28 | 29 | actions: { 30 | async getSession() { 31 | try { 32 | const { data } = await fetchSession() 33 | this.session = { ...data } 34 | homeStore.setMyData({session: data }); 35 | 36 | let str = localStorage.getItem('gptConfigStore'); 37 | if( ! str ) setTimeout( ()=> gptConfigStore.setInit() , 500); 38 | return Promise.resolve(data) 39 | } 40 | catch (error) { 41 | return Promise.reject(error) 42 | } 43 | }, 44 | 45 | setToken(token: string) { 46 | this.token = token 47 | setToken(token) 48 | }, 49 | 50 | removeToken() { 51 | this.token = undefined 52 | removeToken() 53 | }, 54 | }, 55 | }) 56 | 57 | export function useAuthStoreWithout() { 58 | return useAuthStore(store) 59 | } 60 | -------------------------------------------------------------------------------- /src/store/modules/chat/helper.ts: -------------------------------------------------------------------------------- 1 | import { ss } from '@/utils/storage' 2 | 3 | const LOCAL_NAME = 'chatStorage' 4 | 5 | export function defaultState(): Chat.ChatState { 6 | const uuid = 1002 7 | return { 8 | active: uuid, 9 | usingContext: true, 10 | history: [{ uuid, title: 'New Chat', isEdit: false }], 11 | chat: [{ uuid, data: [] }], 12 | } 13 | } 14 | 15 | export function getLocalState(): Chat.ChatState { 16 | const localState = ss.get(LOCAL_NAME) 17 | return { ...defaultState(), ...localState } 18 | } 19 | 20 | export function setLocalState(state: Chat.ChatState) { 21 | ss.set(LOCAL_NAME, state) 22 | } 23 | -------------------------------------------------------------------------------- /src/store/modules/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app' 2 | export * from './chat' 3 | export * from './user' 4 | export * from './prompt' 5 | export * from './settings' 6 | export * from './auth' 7 | -------------------------------------------------------------------------------- /src/store/modules/prompt/helper.ts: -------------------------------------------------------------------------------- 1 | import { ss } from '@/utils/storage' 2 | 3 | const LOCAL_NAME = 'promptStore' 4 | 5 | export type PromptList = [] 6 | 7 | export interface PromptStore { 8 | promptList: PromptList 9 | } 10 | 11 | export function getLocalPromptList(): PromptStore { 12 | const promptStore: PromptStore | undefined = ss.get(LOCAL_NAME) 13 | return promptStore ?? { promptList: [] } 14 | } 15 | 16 | export function setLocalPromptList(promptStore: PromptStore): void { 17 | ss.set(LOCAL_NAME, promptStore) 18 | } 19 | -------------------------------------------------------------------------------- /src/store/modules/prompt/index.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import type { PromptStore } from './helper' 3 | import { getLocalPromptList, setLocalPromptList } from './helper' 4 | 5 | export const usePromptStore = defineStore('prompt-store', { 6 | state: (): PromptStore => getLocalPromptList(), 7 | 8 | actions: { 9 | updatePromptList(promptList: []) { 10 | this.$patch({ promptList }) 11 | setLocalPromptList({ promptList }) 12 | }, 13 | getPromptList() { 14 | return this.$state 15 | }, 16 | }, 17 | }) 18 | -------------------------------------------------------------------------------- /src/store/modules/settings/helper.ts: -------------------------------------------------------------------------------- 1 | import { ss } from '@/utils/storage' 2 | 3 | const LOCAL_NAME = 'settingsStorage' 4 | 5 | export interface SettingsState { 6 | systemMessage: string 7 | temperature: number 8 | top_p: number 9 | } 10 | 11 | export function defaultSetting(): SettingsState { 12 | return { 13 | systemMessage: 'Follow the user\'s instructions carefully. Respond using markdown.', 14 | temperature: 0.8, 15 | top_p: 1, 16 | } 17 | } 18 | 19 | export function getLocalState(): SettingsState { 20 | const localSetting: SettingsState | undefined = ss.get(LOCAL_NAME) 21 | return { ...defaultSetting(), ...localSetting } 22 | } 23 | 24 | export function setLocalState(setting: SettingsState): void { 25 | ss.set(LOCAL_NAME, setting) 26 | } 27 | 28 | export function removeLocalState() { 29 | ss.remove(LOCAL_NAME) 30 | } 31 | -------------------------------------------------------------------------------- /src/store/modules/settings/index.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import type { SettingsState } from './helper' 3 | import { defaultSetting, getLocalState, removeLocalState, setLocalState } from './helper' 4 | 5 | export const useSettingStore = defineStore('setting-store', { 6 | state: (): SettingsState => getLocalState(), 7 | actions: { 8 | updateSetting(settings: Partial) { 9 | this.$state = { ...this.$state, ...settings } 10 | this.recordState() 11 | }, 12 | 13 | resetSetting() { 14 | this.$state = defaultSetting() 15 | removeLocalState() 16 | }, 17 | 18 | recordState() { 19 | setLocalState(this.$state) 20 | }, 21 | }, 22 | }) 23 | -------------------------------------------------------------------------------- /src/store/modules/user/helper.ts: -------------------------------------------------------------------------------- 1 | import { ss } from '@/utils/storage' 2 | 3 | const LOCAL_NAME = 'userStorage' 4 | 5 | export interface UserInfo { 6 | avatar: string 7 | name: string 8 | userBalance: number 9 | userGrade: string, 10 | userName: string 11 | } 12 | 13 | export interface UserState { 14 | userInfo: UserInfo 15 | } 16 | 17 | export function defaultSetting(): UserState { 18 | return { 19 | userInfo: { 20 | avatar: 'https://avatars.githubusercontent.com/u/32251822?v=4', 21 | name: '熊猫助手', 22 | userBalance: 0, 23 | userGrade: '0' 24 | }, 25 | } 26 | } 27 | 28 | export function getLocalState(): UserState { 29 | const localSetting: UserState | undefined = ss.get(LOCAL_NAME) 30 | return { ...defaultSetting(), ...localSetting } 31 | } 32 | 33 | export function setLocalState(setting: UserState): void { 34 | ss.set(LOCAL_NAME, setting) 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/store/modules/user/index.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import { defineStore } from 'pinia' 3 | import type { UserInfo, UserState } from './helper' 4 | import { defaultSetting, getLocalState, setLocalState } from './helper' 5 | import {LoginFrom,UserData} from "@/typings/user"; 6 | import { doLogin,loginOut } from '@/api/user' 7 | import { removeToken,setToken,getToken } from '@/store/modules/auth/helper' 8 | import { to } from 'await-to-js'; 9 | 10 | const token = ref(getToken()) 11 | 12 | // 创建用户仓库 13 | export const useUserStore = defineStore('user-store', { 14 | // 仓库存储数据 15 | state: (): UserState => getLocalState(), 16 | // 异步|逻辑的地方 17 | actions: { 18 | // 用户登录的方法 19 | async userLogin(data: LoginFrom){ 20 | //登录请求 21 | const [err, res] = await to(doLogin(data)); 22 | if (res) { 23 | const data = res.data; 24 | // token本地存储 25 | setToken(data.token); 26 | token.value = data.token; 27 | return Promise.resolve(); 28 | } 29 | return Promise.reject(err); 30 | }, 31 | // 二维码登录的方法 32 | async userQrLogin(token: string){ 33 | setToken(token); 34 | }, 35 | 36 | logout: async (): Promise => { 37 | await loginOut(); 38 | token.value = ''; 39 | removeToken(); 40 | }, 41 | 42 | updateUserInfo(userInfo: Partial) { 43 | this.userInfo = { ...this.userInfo, ...userInfo } 44 | this.recordState() 45 | }, 46 | 47 | resetUserInfo() { 48 | this.userInfo = { ...defaultSetting().userInfo } 49 | this.recordState() 50 | }, 51 | 52 | recordState() { 53 | setLocalState(this.$state) 54 | }, 55 | }, 56 | }) 57 | -------------------------------------------------------------------------------- /src/styles/global.less: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #app { 4 | height: 100%; 5 | } 6 | 7 | body { 8 | padding-bottom: constant(safe-area-inset-bottom); 9 | padding-bottom: env(safe-area-inset-bottom); 10 | } 11 | 12 | .active{ 13 | color: #4b9e5f!important; 14 | } 15 | -------------------------------------------------------------------------------- /src/styles/lib/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/typings/chat.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Chat { 2 | 3 | interface Chat { 4 | dateTime: string 5 | text: string 6 | inversion?: boolean 7 | error?: boolean 8 | loading?: boolean 9 | conversationOptions?: ConversationRequest | null 10 | requestOptions: { prompt: string; options?: ConversationRequest | null } 11 | model?:string //模型 12 | mjID?:string //MJ的ID 13 | opt?:{ 14 | progress?:string,seed?:number, imageUrl?:string 15 | , status?:string, images?:string[] 16 | ,promptEn?:string,buttons?:any[] 17 | ,action?:string 18 | ,duration?:number 19 | ,lkey?:string 20 | } // 21 | uuid?:number 22 | index?:number 23 | myid?:string //唯一随机 24 | logo?:string 25 | 26 | //progress?:string 27 | } 28 | 29 | interface History { 30 | title: string 31 | isEdit: boolean 32 | uuid: number 33 | } 34 | 35 | interface ChatState { 36 | active: number | null 37 | usingContext: boolean; 38 | history: History[] 39 | chat: { uuid: number; data: Chat[] }[] 40 | } 41 | 42 | interface ConversationRequest { 43 | conversationId?: string 44 | parentMessageId?: string 45 | } 46 | 47 | interface ConversationResponse { 48 | conversationId: string 49 | detail: { 50 | choices: { finish_reason: string; index: number; logprobs: any; text: string }[] 51 | created: number 52 | id: string 53 | model: string 54 | object: string 55 | usage: { completion_tokens: number; prompt_tokens: number; total_tokens: number } 56 | } 57 | id: string 58 | parentMessageId: string 59 | role: string 60 | text: string 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/typings/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | readonly VITE_GLOB_API_URL: string; 5 | readonly VITE_APP_API_BASE_URL: string; 6 | readonly VITE_GLOB_OPEN_LONG_REPLY: string; 7 | readonly VITE_GLOB_APP_PWA: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/typings/global.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | $loadingBar?: import('naive-ui').LoadingBarProviderInst; 3 | $dialog?: import('naive-ui').DialogProviderInst; 4 | $message?: import('naive-ui').MessageProviderInst; 5 | $notification?: import('naive-ui').NotificationProviderInst; 6 | } 7 | -------------------------------------------------------------------------------- /src/typings/user.d.ts: -------------------------------------------------------------------------------- 1 | export interface LoginFrom { 2 | username: string // 用户名 3 | password: string // 用户密码 4 | type: string // 登录类型 5 | } 6 | 7 | export interface LoginUserInfo { 8 | userId:bigint;// 用户id 9 | nickName:string; //用户账号 10 | avatar:string; // 用户头像 11 | userBalance:number; // 账户余额 12 | } 13 | 14 | export interface UserData { 15 | token:string; // 登录token 16 | user:LoninUserInfo; //用户信息 17 | } -------------------------------------------------------------------------------- /src/utils/copy.ts: -------------------------------------------------------------------------------- 1 | export function copyToClip(text: string) { 2 | return new Promise((resolve, reject) => { 3 | try { 4 | const input: HTMLTextAreaElement = document.createElement('textarea') 5 | input.setAttribute('readonly', 'readonly') 6 | input.value = text 7 | document.body.appendChild(input) 8 | input.select() 9 | if (document.execCommand('copy')) 10 | document.execCommand('copy') 11 | document.body.removeChild(input) 12 | resolve(text) 13 | } 14 | catch (error) { 15 | reject(error) 16 | } 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/errorCode.ts: -------------------------------------------------------------------------------- 1 | export const errorCode: any = { 2 | '401': '认证失败,无法访问系统资源', 3 | '403': '当前操作没有权限', 4 | '404': '访问资源不存在', 5 | default: '系统未知错误,请反馈给管理员' 6 | }; 7 | export default errorCode; 8 | -------------------------------------------------------------------------------- /src/utils/functions/debounce.ts: -------------------------------------------------------------------------------- 1 | type CallbackFunc = (...args: T) => void 2 | 3 | export function debounce( 4 | func: CallbackFunc, 5 | wait: number, 6 | ): (...args: T) => void { 7 | let timeoutId: ReturnType | undefined 8 | 9 | return (...args: T) => { 10 | const later = () => { 11 | clearTimeout(timeoutId) 12 | func(...args) 13 | } 14 | 15 | clearTimeout(timeoutId) 16 | timeoutId = setTimeout(later, wait) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/functions/index.ts: -------------------------------------------------------------------------------- 1 | export function getCurrentDate() { 2 | const date = new Date() 3 | const day = date.getDate() 4 | const month = date.getMonth() + 1 5 | const year = date.getFullYear() 6 | return `${year}-${month}-${day}` 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/is/index.ts: -------------------------------------------------------------------------------- 1 | export function isNumber(value: T | unknown): value is number { 2 | return Object.prototype.toString.call(value) === '[object Number]' 3 | } 4 | 5 | export function isString(value: T | unknown): value is string { 6 | return Object.prototype.toString.call(value) === '[object String]' 7 | } 8 | 9 | export function isBoolean(value: T | unknown): value is boolean { 10 | return Object.prototype.toString.call(value) === '[object Boolean]' 11 | } 12 | 13 | export function isNull(value: T | unknown): value is null { 14 | return Object.prototype.toString.call(value) === '[object Null]' 15 | } 16 | 17 | export function isUndefined(value: T | unknown): value is undefined { 18 | return Object.prototype.toString.call(value) === '[object Undefined]' 19 | } 20 | 21 | export function isObject(value: T | unknown): value is object { 22 | return Object.prototype.toString.call(value) === '[object Object]' 23 | } 24 | 25 | export function isArray(value: T | unknown): value is T { 26 | return Object.prototype.toString.call(value) === '[object Array]' 27 | } 28 | 29 | export function isFunction any | void | never>(value: T | unknown): value is T { 30 | return Object.prototype.toString.call(value) === '[object Function]' 31 | } 32 | 33 | export function isDate(value: T | unknown): value is T { 34 | return Object.prototype.toString.call(value) === '[object Date]' 35 | } 36 | 37 | export function isRegExp(value: T | unknown): value is T { 38 | return Object.prototype.toString.call(value) === '[object RegExp]' 39 | } 40 | 41 | export function isPromise>(value: T | unknown): value is T { 42 | return Object.prototype.toString.call(value) === '[object Promise]' 43 | } 44 | 45 | export function isSet>(value: T | unknown): value is T { 46 | return Object.prototype.toString.call(value) === '[object Set]' 47 | } 48 | 49 | export function isMap>(value: T | unknown): value is T { 50 | return Object.prototype.toString.call(value) === '[object Map]' 51 | } 52 | 53 | export function isFile(value: T | unknown): value is T { 54 | return Object.prototype.toString.call(value) === '[object File]' 55 | } 56 | -------------------------------------------------------------------------------- /src/utils/request/axios.ts: -------------------------------------------------------------------------------- 1 | import axios, { type AxiosResponse } from 'axios' 2 | import { getToken } from '@/store/modules/auth/helper' 3 | 4 | const service = axios.create({ 5 | baseURL: import.meta.env.VITE_GLOB_API_URL, 6 | }) 7 | 8 | service.interceptors.request.use( 9 | (config: { headers: { Authorization: string } }) => { 10 | const token = getToken() 11 | if (token) 12 | config.headers.Authorization = `Bearer ${token}` 13 | return config 14 | }, 15 | (error: { response: any }) => { 16 | return Promise.reject(error.response) 17 | }, 18 | ) 19 | 20 | service.interceptors.response.use( 21 | (response: AxiosResponse): AxiosResponse => { 22 | if (response.status === 200) 23 | return response 24 | 25 | throw new Error(response.status.toString()) 26 | }, 27 | (error: any) => { 28 | return Promise.reject(error) 29 | }, 30 | ) 31 | 32 | export default service 33 | -------------------------------------------------------------------------------- /src/utils/request/index.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosProgressEvent, AxiosResponse, GenericAbortSignal } from 'axios' 2 | import request from './axios' 3 | import { useAuthStore } from '@/store' 4 | 5 | export interface HttpOption { 6 | url: string 7 | data?: any 8 | method?: string 9 | headers?: any 10 | onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void 11 | signal?: GenericAbortSignal 12 | beforeRequest?: () => void 13 | afterRequest?: () => void 14 | } 15 | 16 | export interface Response { 17 | data: T 18 | msg: string | undefined 19 | code: number, 20 | rows: [] 21 | } 22 | 23 | function http( 24 | { url, data, method, headers, onDownloadProgress, signal, beforeRequest, afterRequest }: HttpOption, 25 | ) { 26 | const successHandler = (res: AxiosResponse>) => { 27 | const authStore = useAuthStore() 28 | if (res.data.code === 200 ){ 29 | return res.data 30 | }else{ 31 | authStore.removeToken() 32 | window.location.reload() 33 | } 34 | return Promise.reject(res.data) 35 | } 36 | 37 | const failHandler = (error: Response) => { 38 | alert("failHandler") 39 | afterRequest?.() 40 | return { error: true, message: error?.msg || 'Error' }; 41 | } 42 | 43 | beforeRequest?.() 44 | 45 | method = method || 'GET' 46 | 47 | const params = Object.assign(typeof data === 'function' ? data() : data ?? {}, {}) 48 | 49 | return method === 'GET' 50 | ? request.get(url, { params, signal, onDownloadProgress }).then(successHandler, failHandler) 51 | : request.post(url, params, { headers, signal, onDownloadProgress }).then(successHandler, failHandler) 52 | } 53 | 54 | export function get( 55 | { url, data, method = 'GET', onDownloadProgress, signal, beforeRequest, afterRequest }: HttpOption, 56 | ): Promise> { 57 | return http({ 58 | url, 59 | method, 60 | data, 61 | onDownloadProgress, 62 | signal, 63 | beforeRequest, 64 | afterRequest, 65 | }) 66 | } 67 | 68 | export function post( 69 | { url, data, method = 'POST', headers, onDownloadProgress, signal, beforeRequest, afterRequest }: HttpOption, 70 | ): Promise> { 71 | return http({ 72 | url, 73 | method, 74 | data, 75 | headers, 76 | onDownloadProgress, 77 | signal, 78 | beforeRequest, 79 | afterRequest, 80 | }) 81 | } 82 | 83 | export default post 84 | -------------------------------------------------------------------------------- /src/utils/storage/index.ts: -------------------------------------------------------------------------------- 1 | interface StorageData { 2 | data: T 3 | expire: number | null 4 | } 5 | 6 | export function createLocalStorage(options?: { expire?: number | null }) { 7 | // token默认保存7天 8 | const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7 9 | 10 | const { expire } = Object.assign({ expire: DEFAULT_CACHE_TIME }, options) 11 | 12 | function set(key: string, data: T) { 13 | const storageData: StorageData = { 14 | data, 15 | expire: expire !== null ? new Date().getTime() + expire * 1000 : null, 16 | } 17 | 18 | const json = JSON.stringify(storageData) 19 | window.localStorage.setItem(key, json) 20 | } 21 | 22 | function get(key: string) { 23 | const json = window.localStorage.getItem(key) 24 | 25 | if (json) { 26 | let storageData: StorageData | null = null 27 | 28 | try { 29 | storageData = JSON.parse(json) 30 | } 31 | catch { 32 | // Prevent failure 33 | } 34 | 35 | if (storageData) { 36 | const { data, expire } = storageData 37 | if (expire === null || expire >= Date.now()) 38 | return data 39 | } 40 | 41 | remove(key) 42 | return null 43 | } 44 | } 45 | 46 | function remove(key: string) { 47 | window.localStorage.removeItem(key) 48 | } 49 | 50 | function clear() { 51 | window.localStorage.clear() 52 | } 53 | 54 | return { set, get, remove, clear } 55 | } 56 | 57 | export const ls = createLocalStorage() 58 | 59 | export const ss = createLocalStorage({ expire: null }) 60 | -------------------------------------------------------------------------------- /src/views/chat/components/Message/Avatar.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 31 | -------------------------------------------------------------------------------- /src/views/chat/components/index.ts: -------------------------------------------------------------------------------- 1 | import Message from './Message/index.vue' 2 | 3 | export { Message } 4 | -------------------------------------------------------------------------------- /src/views/chat/hooks/useChat.ts: -------------------------------------------------------------------------------- 1 | import { useChatStore } from '@/store' 2 | 3 | export function useChat() { 4 | const chatStore = useChatStore() 5 | 6 | const getChatByUuidAndIndex = (uuid: number, index: number) => { 7 | return chatStore.getChatByUuidAndIndex(uuid, index) 8 | } 9 | 10 | const addChat = (uuid: number, chat: Chat.Chat) => { 11 | chatStore.addChatByUuid(uuid, chat) 12 | } 13 | 14 | const updateChat = (uuid: number, index: number, chat: Chat.Chat) => { 15 | chatStore.updateChatByUuid(uuid, index, chat) 16 | } 17 | 18 | const updateChatSome = (uuid: number, index: number, chat: Partial) => { 19 | chatStore.updateChatSomeByUuid(uuid, index, chat) 20 | } 21 | 22 | return { 23 | addChat, 24 | updateChat, 25 | updateChatSome, 26 | getChatByUuidAndIndex, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/views/chat/hooks/useScroll.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue' 2 | import { nextTick, ref } from 'vue' 3 | 4 | type ScrollElement = HTMLDivElement | null 5 | 6 | interface ScrollReturn { 7 | scrollRef: Ref 8 | scrollToBottom: () => Promise 9 | scrollToTop: () => Promise 10 | scrollToBottomIfAtBottom: () => Promise 11 | } 12 | 13 | export function useScroll(): ScrollReturn { 14 | const scrollRef = ref(null) 15 | 16 | const scrollToBottom = async () => { 17 | await nextTick() 18 | if (scrollRef.value) 19 | scrollRef.value.scrollTop = scrollRef.value.scrollHeight 20 | } 21 | 22 | const scrollToTop = async () => { 23 | await nextTick() 24 | if (scrollRef.value) 25 | scrollRef.value.scrollTop = 0 26 | } 27 | 28 | const scrollToBottomIfAtBottom = async () => { 29 | await nextTick() 30 | if (scrollRef.value) { 31 | const threshold = 100 // 阈值,表示滚动条到底部的距离阈值 32 | const distanceToBottom = scrollRef.value.scrollHeight - scrollRef.value.scrollTop - scrollRef.value.clientHeight 33 | if (distanceToBottom <= threshold) 34 | scrollRef.value.scrollTop = scrollRef.value.scrollHeight 35 | } 36 | } 37 | 38 | return { 39 | scrollRef, 40 | scrollToBottom, 41 | scrollToTop, 42 | scrollToBottomIfAtBottom, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/views/chat/hooks/useUsingContext.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue' 2 | import { useMessage } from 'naive-ui' 3 | import { t } from '@/locales' 4 | import { useChatStore } from '@/store' 5 | 6 | export function useUsingContext() { 7 | const ms = useMessage() 8 | const chatStore = useChatStore() 9 | const usingContext = computed(() => chatStore.usingContext) 10 | 11 | function toggleUsingContext() { 12 | chatStore.setUsingContext(!usingContext.value) 13 | if (usingContext.value) 14 | ms.success(t('chat.turnOnContext')) 15 | else 16 | ms.warning(t('chat.turnOffContext')) 17 | } 18 | 19 | return { 20 | usingContext, 21 | toggleUsingContext, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/views/chat/layout/Layout.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 82 | 83 | 88 | -------------------------------------------------------------------------------- /src/views/chat/layout/Permission.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 81 | -------------------------------------------------------------------------------- /src/views/chat/layout/index.ts: -------------------------------------------------------------------------------- 1 | import ChatLayout from './Layout.vue' 2 | 3 | export { ChatLayout } 4 | -------------------------------------------------------------------------------- /src/views/chat/layout/sider/Footer.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 25 | -------------------------------------------------------------------------------- /src/views/exception/404/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 32 | -------------------------------------------------------------------------------- /src/views/exception/500/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 33 | -------------------------------------------------------------------------------- /src/views/knowledge/layout.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 57 | 62 | -------------------------------------------------------------------------------- /src/views/luma/layout.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 57 | 62 | -------------------------------------------------------------------------------- /src/views/luma/video.vue: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /src/views/luma/voInput.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | -------------------------------------------------------------------------------- /src/views/luma/voList.vue: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /src/views/mj/aiCanvas.vue: -------------------------------------------------------------------------------- 1 | 43 | -------------------------------------------------------------------------------- /src/views/mj/aiDrawInput.vue: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /src/views/mj/aiFace.vue: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /src/views/mj/aiFooter.vue: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /src/views/mj/aiGallery.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | -------------------------------------------------------------------------------- /src/views/mj/aiGpts.vue: -------------------------------------------------------------------------------- 1 | 22 | 45 | -------------------------------------------------------------------------------- /src/views/mj/aiListText.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/views/mj/aiMobileMenu.vue: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /src/views/mj/aiMsg.vue: -------------------------------------------------------------------------------- 1 | 4 | 36 | 37 | 40 | -------------------------------------------------------------------------------- /src/views/mj/aiSetAuth.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/views/mj/aiSiderInput.vue: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /src/views/mj/aiTextSetting.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/views/mj/dallText.vue: -------------------------------------------------------------------------------- 1 | 55 | -------------------------------------------------------------------------------- /src/views/mj/draw.vue: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /src/views/mj/index.ts: -------------------------------------------------------------------------------- 1 | import aiSider from "./aiSider.vue" 2 | import aiGpts from "./aiGpts.vue" 3 | import aiGallery from "./aiGallery.vue" 4 | import aiFooter from "./aiFooter.vue" 5 | 6 | export {aiSider,aiGpts,aiGallery,aiFooter } -------------------------------------------------------------------------------- /src/views/mj/layout.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 58 | 63 | -------------------------------------------------------------------------------- /src/views/mj/mjTextAttr.vue: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /src/views/mj/ttsText.vue: -------------------------------------------------------------------------------- 1 | 67 | -------------------------------------------------------------------------------- /src/views/mj/whisperText.vue: -------------------------------------------------------------------------------- 1 | 63 | -------------------------------------------------------------------------------- /src/views/ppt/layout.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 57 | 62 | -------------------------------------------------------------------------------- /src/views/suno/layout.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 59 | 64 | -------------------------------------------------------------------------------- /src/views/suno/mcUploadMp3.vue: -------------------------------------------------------------------------------- 1 | 58 | -------------------------------------------------------------------------------- /src/views/suno/mcplayer.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /src/views/suno/music.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/suno/player.vue: -------------------------------------------------------------------------------- 1 | 70 | -------------------------------------------------------------------------------- /src/views/suno/playui.vue: -------------------------------------------------------------------------------- 1 | 39 | 47 | -------------------------------------------------------------------------------- /start.cmd: -------------------------------------------------------------------------------- 1 | cd ./service 2 | start pnpm start > service.log & 3 | echo "Start service complete!" 4 | 5 | 6 | cd .. 7 | echo "" > front.log 8 | start pnpm dev > front.log & 9 | echo "Start front complete!" 10 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | 2 | cd ./service 3 | nohup pnpm start > service.log & 4 | echo "Start service complete!" 5 | 6 | 7 | cd .. 8 | echo "" > front.log 9 | nohup pnpm dev > front.log & 10 | echo "Start front complete!" 11 | tail -f front.log 12 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: 'class', 4 | content: [ 5 | './index.html', 6 | './src/**/*.{vue,js,ts,jsx,tsx}', 7 | ], 8 | theme: { 9 | extend: { 10 | animation: { 11 | blink: 'blink 1.2s infinite steps(1, start)', 12 | }, 13 | keyframes: { 14 | blink: { 15 | '0%, 100%': { 'background-color': 'currentColor' }, 16 | '50%': { 'background-color': 'transparent' }, 17 | }, 18 | }, 19 | }, 20 | }, 21 | plugins: [], 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "ESNext", 5 | "target": "ESNext", 6 | "lib": ["DOM", "ESNext"], 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "jsx": "preserve", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "noUnusedLocals": true, 14 | "strictNullChecks": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "skipLibCheck": true, 17 | "paths": { 18 | "@/*": ["./src/*"] 19 | }, 20 | "types": ["vite/client", "node", "naive-ui/volar"] 21 | }, 22 | "exclude": ["node_modules", "dist", "service"] 23 | } 24 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | { 4 | "source": "/openapi/(.*)", 5 | "destination": "/api/proxy" 6 | }, 7 | { 8 | "source": "/mjapi/(.*)", 9 | "destination": "/api/proxy" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { defineConfig, loadEnv } from 'vite' 3 | import vue from '@vitejs/plugin-vue' 4 | import { VitePWA } from 'vite-plugin-pwa' 5 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' 6 | 7 | function setupPlugins(env) { 8 | return [ 9 | vue(), 10 | env.VITE_GLOB_APP_PWA === 'true' && VitePWA({ 11 | injectRegister: 'auto', 12 | manifest: { 13 | name: 'chatGPT', 14 | short_name: 'chatGPT', 15 | icons: [ 16 | { src: 'pwa-192x192.png', sizes: '192x192', type: 'image/png' }, 17 | { src: 'pwa-512x512.png', sizes: '512x512', type: 'image/png' }, 18 | ], 19 | }, 20 | }), 21 | createSvgIconsPlugin({ 22 | // 指定需要缓存的图标文件夹 23 | iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')], 24 | // 指定 symbolId 格式 25 | symbolId: 'icon-[name]', 26 | }), 27 | ].filter(Boolean); // 过滤掉 falsy 值 28 | } 29 | 30 | export default defineConfig(({ mode }) => { 31 | const viteEnv = loadEnv(mode, process.cwd()) 32 | 33 | return { 34 | resolve: { 35 | alias: { 36 | '@': path.resolve(process.cwd(), 'src'), 37 | }, 38 | }, 39 | plugins: setupPlugins(viteEnv), 40 | server: { 41 | host: '0.0.0.0', 42 | port: 1002, 43 | open: false, 44 | proxy: { 45 | '/api': { 46 | target: viteEnv.VITE_APP_API_BASE_URL, 47 | changeOrigin: true, // 允许跨域 48 | rewrite: path => path.replace('/api/', '/'), 49 | }, 50 | '/uploads': { 51 | target: viteEnv.VITE_APP_API_BASE_URL, 52 | changeOrigin: true, // 允许跨域 53 | //rewrite: path => path.replace('/api/', '/'), 54 | }, 55 | '/openapi': { 56 | target: viteEnv.VITE_APP_API_BASE_URL, 57 | changeOrigin: true, // 允许跨域 58 | //rewrite: path => path.replace('/api/', '/'), 59 | }, 60 | }, 61 | }, 62 | build: { 63 | reportCompressedSize: false, 64 | sourcemap: false, 65 | commonjsOptions: { 66 | ignoreTryCatch: false, 67 | }, 68 | }, 69 | } 70 | }) 71 | --------------------------------------------------------------------------------