Full Code of Chanzhaoyu/chatgpt-web for AI

main 7474b2c14971 cached
136 files
288.7 KB
121.0k tokens
146 symbols
1 requests
Download .txt
Showing preview only (318K chars total). Download the full file or copy to clipboard to get everything.
Repository: Chanzhaoyu/chatgpt-web
Branch: main
Commit: 7474b2c14971
Files: 136
Total size: 288.7 KB

Directory structure:
gitextract_4kuwrg29/

├── .commitlintrc.json
├── .devcontainer/
│   └── devcontainer.json
├── .dockerignore
├── .editorconfig
├── .eslintignore
├── .eslintrc.cjs
├── .gitattributes
├── .github/
│   └── workflows/
│       ├── build_docker.yml
│       ├── ci.yml
│       └── issues_close.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   └── pre-commit
├── .npmrc
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── CHANGELOG.md
├── CONTRIBUTING.en.md
├── CONTRIBUTING.md
├── Dockerfile
├── README.md
├── README.zh.md
├── docker-compose/
│   ├── README.md
│   ├── docker-compose.yml
│   └── nginx/
│       └── nginx.conf
├── index.html
├── kubernetes/
│   ├── README.md
│   ├── deploy.yaml
│   └── ingress.yaml
├── license
├── package.json
├── postcss.config.js
├── service/
│   ├── .eslintrc.json
│   ├── .gitignore
│   ├── .npmrc
│   ├── .vscode/
│   │   ├── extensions.json
│   │   └── settings.json
│   ├── package.json
│   ├── src/
│   │   ├── chatgpt/
│   │   │   ├── index.ts
│   │   │   └── types.ts
│   │   ├── index.ts
│   │   ├── middleware/
│   │   │   ├── auth.ts
│   │   │   └── limiter.ts
│   │   ├── types.ts
│   │   └── utils/
│   │       ├── index.ts
│   │       └── is.ts
│   ├── tsconfig.json
│   └── tsup.config.ts
├── src/
│   ├── App.vue
│   ├── api/
│   │   └── index.ts
│   ├── assets/
│   │   └── recommend.json
│   ├── components/
│   │   ├── common/
│   │   │   ├── HoverButton/
│   │   │   │   ├── Button.vue
│   │   │   │   └── 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
│   ├── hooks/
│   │   ├── useBasicLayout.ts
│   │   ├── useIconRender.ts
│   │   ├── useLanguage.ts
│   │   └── useTheme.ts
│   ├── icons/
│   │   ├── 403.vue
│   │   └── 500.vue
│   ├── locales/
│   │   ├── en-US.ts
│   │   ├── es-ES.ts
│   │   ├── index.ts
│   │   ├── ko-KR.ts
│   │   ├── ru-RU.ts
│   │   ├── vi-VN.ts
│   │   ├── zh-CN.ts
│   │   └── zh-TW.ts
│   ├── main.ts
│   ├── plugins/
│   │   ├── assets.ts
│   │   ├── index.ts
│   │   └── scrollbarStyle.ts
│   ├── router/
│   │   ├── index.ts
│   │   └── permission.ts
│   ├── store/
│   │   ├── helper.ts
│   │   ├── index.ts
│   │   └── modules/
│   │       ├── app/
│   │       │   ├── helper.ts
│   │       │   └── index.ts
│   │       ├── auth/
│   │       │   ├── 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
│   ├── utils/
│   │   ├── copy.ts
│   │   ├── functions/
│   │   │   ├── debounce.ts
│   │   │   └── index.ts
│   │   ├── is/
│   │   │   └── index.ts
│   │   ├── request/
│   │   │   ├── axios.ts
│   │   │   └── index.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
├── start.cmd
├── start.sh
├── tailwind.config.js
├── tsconfig.json
└── vite.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .commitlintrc.json
================================================
{
  "extends": ["@commitlint/config-conventional"]
}


================================================
FILE: .devcontainer/devcontainer.json
================================================
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
	"name": "Node.js & TypeScript",
	// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
	"image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye"

	// Features to add to the dev container. More info: https://containers.dev/features.
	// "features": {},

	// Use 'forwardPorts' to make a list of ports inside the container available locally.
	// "forwardPorts": [],

	// Use 'postCreateCommand' to run commands after the container is created.
	// "postCreateCommand": "yarn install",

	// Configure tool-specific properties.
	// "customizations": {},

	// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
	// "remoteUser": "root"
}


================================================
FILE: .dockerignore
================================================
**/node_modules
*/node_modules
node_modules
Dockerfile
.*
*/.*
!.env


================================================
FILE: .editorconfig
================================================
# Editor configuration, see http://editorconfig.org

root = true

[*]
charset = utf-8
indent_style = tab
indent_size = 2
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true


================================================
FILE: .eslintignore
================================================
docker-compose
kubernetes


================================================
FILE: .eslintrc.cjs
================================================
module.exports = {
  root: true,
  extends: ['@antfu'],
}


================================================
FILE: .gitattributes
================================================
"*.vue"    eol=lf
"*.js"     eol=lf
"*.ts"     eol=lf
"*.jsx"    eol=lf
"*.tsx"    eol=lf
"*.cjs"    eol=lf
"*.cts"    eol=lf
"*.mjs"    eol=lf
"*.mts"    eol=lf
"*.json"   eol=lf
"*.html"   eol=lf
"*.css"    eol=lf
"*.less"   eol=lf
"*.scss"   eol=lf
"*.sass"   eol=lf
"*.styl"   eol=lf
"*.md"     eol=lf


================================================
FILE: .github/workflows/build_docker.yml
================================================
name: build_docker

on:
  push:
    branches: [main]
  release:
    types: [created] # 表示在创建新的 Release 时触发

jobs:
  build_docker:
    name: Build docker
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - run: |
          echo "本次构建的版本为:${GITHUB_REF_NAME} (但是这个变量目前上下文中无法获取到)"
          echo 本次构建的版本为:${{ github.ref_name }}
          env

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      - name: Login to DockerHub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - name: Build and push
        id: docker_build
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          labels: ${{ steps.meta.outputs.labels }}
          platforms: linux/amd64,linux/arm64
          tags: |
            ${{ secrets.DOCKERHUB_USERNAME }}/chatgpt-web:${{ github.ref_name }}
            ${{ secrets.DOCKERHUB_USERNAME }}/chatgpt-web:latest


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches:
      - main

  pull_request:
    branches:
      - main

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set node
        uses: actions/setup-node@v3
        with:
          node-version: 18.x

      - name: Setup
        run: npm i -g @antfu/ni

      - name: Install
        run: nci

      - name: Lint
        run: nr lint:fix

  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set node
        uses: actions/setup-node@v3
        with:
          node-version: 18.x

      - name: Setup
        run: npm i -g @antfu/ni

      - name: Install
        run: nci

      - name: Typecheck
        run: nr type-check


================================================
FILE: .github/workflows/issues_close.yml
================================================
name: Close inactive issues
on:
  schedule:
    - cron: '30 1 * * *'

jobs:
  close-issues:
    runs-on: ubuntu-latest
    permissions:
      issues: write
      pull-requests: write
    steps:
      - uses: actions/stale@v5
        with:
          days-before-issue-stale: 10
          days-before-issue-close: 2
          stale-issue-label: stale
          stale-issue-message: This issue is stale because it has been open for 10 days with no activity.
          close-issue-message: This issue was closed because it has been inactive for 2 days since being marked as stale.
          days-before-pr-stale: -1
          days-before-pr-close: -1
          repo-token: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
.DS_Store
dist
dist-ssr
coverage
*.local

/cypress/videos/
/cypress/screenshots/

# Editor directories and files
.vscode/*
!.vscode/settings.json
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# Environment variables files
/service/.env


================================================
FILE: .husky/commit-msg
================================================
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx --no -- commitlint --edit 


================================================
FILE: .husky/pre-commit
================================================
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged


================================================
FILE: .npmrc
================================================
strict-peer-dependencies=false


================================================
FILE: .vscode/extensions.json
================================================
{
  "recommendations": ["Vue.volar", "dbaeumer.vscode-eslint"]
}


================================================
FILE: .vscode/settings.json
================================================
{
  "prettier.enable": false,
  "editor.formatOnSave": false,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact",
    "vue",
    "html",
    "json",
    "jsonc",
    "json5",
    "yaml",
    "yml",
    "markdown"
  ],
  "cSpell.words": [
    "antfu",
    "axios",
    "bumpp",
    "chatgpt",
    "chenzhaoyu",
    "commitlint",
    "davinci",
    "dockerhub",
    "esno",
    "GPTAPI",
    "highlightjs",
    "hljs",
    "iconify",
    "katex",
    "katexmath",
    "linkify",
    "logprobs",
    "mdhljs",
    "mila",
    "nodata",
    "OPENAI",
    "pinia",
    "Popconfirm",
    "rushstack",
    "Sider",
    "tailwindcss",
    "traptitech",
    "tsup",
    "Typecheck",
    "unplugin",
    "VITE",
    "vueuse",
    "Zhao"
  ],
  "i18n-ally.enabledParsers": [
    "ts"
  ],
  "i18n-ally.sortKeys": true,
  "i18n-ally.keepFulfilled": true,
  "i18n-ally.localesPaths": [
    "src/locales"
  ],
  "i18n-ally.keystyle": "nested"
}


================================================
FILE: CHANGELOG.md
================================================
## v2.11.1

`2023-10-11`

## Enhancement
- 优化打字机光标效果
- 清空聊天历史按钮
- 更新文档

## BugFix
- 修复移动端上的问题
- 修复不规范的引入导致的问题

## v2.11.0

`2023-04-26`

> [chatgpt-web-plus](https://github.com/Chanzhaoyu/chatgpt-web-plus) 新界面、完整用户管理

## Enhancement
- 更新默认 `accessToken` 反代地址为 [[pengzhile](https://github.com/pengzhile)] 的 `https://ai.fakeopen.com/api/conversation` [[24min](https://github.com/Chanzhaoyu/chatgpt-web/pull/1567/files)]
- 添加自定义 `temperature` 和 `top_p` [[quzard](https://github.com/Chanzhaoyu/chatgpt-web/pull/1260)]
- 优化代码 [[shunyue1320](https://github.com/Chanzhaoyu/chatgpt-web/pull/1328)]
- 优化复制代码反馈效果

## BugFix
- 修复余额查询和文案 [[luckywangxi](https://github.com/Chanzhaoyu/chatgpt-web/pull/1174)][[zuoning777](https://github.com/Chanzhaoyu/chatgpt-web/pull/1296)]
- 修复默认语言错误 [[idawnwon](https://github.com/Chanzhaoyu/chatgpt-web/pull/1352)]
- 修复 `onRegenerate` 下问题 [[leafsummer](https://github.com/Chanzhaoyu/chatgpt-web/pull/1188)]

## Other
- 引导用户触发提示词 [[RyanXinOne](https://github.com/Chanzhaoyu/chatgpt-web/pull/1183)]
- 添加韩语翻译 [[Kamilake](https://github.com/Chanzhaoyu/chatgpt-web/pull/1372)]
- 添加俄语翻译 [[aquaratixc](https://github.com/Chanzhaoyu/chatgpt-web/pull/1571)]
- 优化翻译和文本检查 [[PeterDaveHello](https://github.com/Chanzhaoyu/chatgpt-web/pull/1460)]
- 移除无用文件

## v2.10.9

`2023-04-03`

> 更新默认 `accessToken` 反代地址为 [[pengzhile](https://github.com/pengzhile)] 的 `https://ai.fakeopen.com/api/conversation`

## Enhancement
- 添加 `socks5` 代理认证 [[yimiaoxiehou](https://github.com/Chanzhaoyu/chatgpt-web/pull/999)]
- 添加 `socks` 代理用户名密码的配置 [[hank-cp](https://github.com/Chanzhaoyu/chatgpt-web/pull/890)]
- 添加可选日志打印 [[zcong1993](https://github.com/Chanzhaoyu/chatgpt-web/pull/1041)]
- 更新侧边栏按钮本地化[[simonwu53](https://github.com/Chanzhaoyu/chatgpt-web/pull/911)]
- 优化代码块滚动条高度 [[Fog3211](https://github.com/Chanzhaoyu/chatgpt-web/pull/1153)]
## BugFix
- 修复 `PWA` 问题 [[bingo235](https://github.com/Chanzhaoyu/chatgpt-web/pull/807)]
- 修复 `ESM` 错误 [[kidonng](https://github.com/Chanzhaoyu/chatgpt-web/pull/826)]
- 修复反向代理开启时限流失效的问题 [[gitgitgogogo](https://github.com/Chanzhaoyu/chatgpt-web/pull/863)]
- 修复 `docker` 构建时 `.env` 可能被忽略的问题 [[zaiMoe](https://github.com/Chanzhaoyu/chatgpt-web/pull/877)]
- 修复导出异常错误 [[KingTwinkle](https://github.com/Chanzhaoyu/chatgpt-web/pull/938)]
- 修复空值异常 [[vchenpeng](https://github.com/Chanzhaoyu/chatgpt-web/pull/1103)]
- 移动端上的体验问题

## Other
- `Docker` 容器名字名义 [[LOVECHEN](https://github.com/Chanzhaoyu/chatgpt-web/pull/1035)]
- `kubernetes` 部署配置 [[CaoYunzhou](https://github.com/Chanzhaoyu/chatgpt-web/pull/1001)]
- 感谢 [[assassinliujie](https://github.com/Chanzhaoyu/chatgpt-web/pull/962)] 和 [[puppywang](https://github.com/Chanzhaoyu/chatgpt-web/pull/1017)] 的某些贡献
- 更新 `kubernetes/deploy.yaml` [[idawnwon](https://github.com/Chanzhaoyu/chatgpt-web/pull/1085)]
- 文档更新 [[#yi-ge](https://github.com/Chanzhaoyu/chatgpt-web/pull/883)]
- 文档更新 [[weifeng12x](https://github.com/Chanzhaoyu/chatgpt-web/pull/880)]
- 依赖更新

## v2.10.8

`2023-03-23`

如遇问题,请删除 `node_modules` 重新安装依赖。

## Feature
- 显示回复消息原文的选项 [[yilozt](https://github.com/Chanzhaoyu/chatgpt-web/pull/672)]
- 添加单 `IP` 每小时请求限制。环境变量: `MAX_REQUEST_PER_HOUR` [[zhuxindong ](https://github.com/Chanzhaoyu/chatgpt-web/pull/718)]
- 前端添加角色设定,仅 `API` 方式可见 [[quzard](https://github.com/Chanzhaoyu/chatgpt-web/pull/768)]
- `OPENAI_API_MODEL` 变量现在对 `ChatGPTUnofficialProxyAPI` 也生效,注意:`Token` 和 `API` 的模型命名不一致,不能直接填入 `gpt-3.5` 或者 `gpt-4` [[hncboy](https://github.com/Chanzhaoyu/chatgpt-web/pull/632)]
- 添加繁体中文 `Prompts` [[PeterDaveHello](https://github.com/Chanzhaoyu/chatgpt-web/pull/796)]

## Enhancement
- 重置回答时滚动定位至该回答 [[shunyue1320](https://github.com/Chanzhaoyu/chatgpt-web/pull/781)]
- 当 `API` 是 `gpt-4` 时增加可用的 `Max Tokens` [[simonwu53](https://github.com/Chanzhaoyu/chatgpt-web/pull/729)]
- 判断和忽略回复字符 [[liut](https://github.com/Chanzhaoyu/chatgpt-web/pull/474)]
- 切换会话时,自动聚焦输入框 [[JS-an](https://github.com/Chanzhaoyu/chatgpt-web/pull/735)]
- 渲染的链接新窗口打开
- 查询余额可选 `API_BASE_URL` 代理地址
- `config` 接口添加验证防止被无限制调用
- `PWA` 默认不开启,现在需手动修改 `.env` 文件 `VITE_GLOB_APP_PWA` 变量
- 当网络连接时,刷新页面,`500` 错误页自动跳转到主页

## BugFix
- `scrollToBottom` 调回 `scrollToBottomIfAtBottom` [[shunyue1320](https://github.com/Chanzhaoyu/chatgpt-web/pull/771)]
- 重置异常的 `loading` 会话

## Common
- 创建 `start.cmd` 在 `windows` 下也可以运行 [vulgatecnn](https://github.com/Chanzhaoyu/chatgpt-web/pull/656)]
- 添加 `visual-studio-code` 中调试配置 [[ChandlerVer5](https://github.com/Chanzhaoyu/chatgpt-web/pull/296)]
- 修复文档中 `docker` 端口为本地 [[kilvn](https://github.com/Chanzhaoyu/chatgpt-web/pull/802)]
## Other
- 依赖更新


## v2.10.7

`2023-03-17`

## BugFix
- 回退 `chatgpt` 版本,原因:导致 `OPENAI_API_BASE_URL` 代理失效
- 修复缺省状态的 `usingContext` 默认值

## v2.10.6

`2023-03-17`

## Feature
- 显示 `API` 余额 [[pzcn](https://github.com/Chanzhaoyu/chatgpt-web/pull/582)]

## Enhancement
- 美化滚动条样式和 `UI` 保持一致 [[haydenull](https://github.com/Chanzhaoyu/chatgpt-web/pull/617)]
- 优化移动端 `Prompt` 样式 [[CornerSkyless](https://github.com/Chanzhaoyu/chatgpt-web/pull/608)]
- 上下文开关改为全局开关,现在记录在本地缓存中
- 配置信息按接口类型显示

## Perf
- 优化函数方法 [[kirklin](https://github.com/Chanzhaoyu/chatgpt-web/pull/583)]
- 字符错误 [[pdsuwwz](https://github.com/Chanzhaoyu/chatgpt-web/pull/585)]
- 文档描述错误 [[lizhongyuan3](https://github.com/Chanzhaoyu/chatgpt-web/pull/636)]

## BugFix
- 修复 `Prompt` 导入、导出兼容性错误
- 修复 `highlight.js` 控制台兼容性警告

## Other
- 依赖更新

## v2.10.5

`2023-03-13`

更新依赖,`access_token` 默认代理为 [pengzhile](https://github.com/pengzhile) 的 `https://bypass.duti.tech/api/conversation`

## Feature
- `Prompt` 商店在线导入可以导入两种 `recommend.json`里提到的模板 [simonwu53](https://github.com/Chanzhaoyu/chatgpt-web/pull/521)
- 支持 `HTTPS_PROXY` [whatwewant](https://github.com/Chanzhaoyu/chatgpt-web/pull/308)
- `Prompt` 添加查询筛选

## Enhancement
- 调整输入框最大行数 [yi-ge](https://github.com/Chanzhaoyu/chatgpt-web/pull/502)
- 优化 `docker` 打包 [whatwewant](https://github.com/Chanzhaoyu/chatgpt-web/pull/520)
- `Prompt` 添加翻译和优化布局
- 「繁体中文」补全和审阅 [PeterDaveHello](https://github.com/Chanzhaoyu/chatgpt-web/pull/542)
- 语言选择调整为下路框形式
- 权限输入框类型调整为密码形式

## BugFix
- `JSON` 导入检查 [Nothing1024](https://github.com/Chanzhaoyu/chatgpt-web/pull/523)
- 修复 `AUTH_SECRET_KEY` 模式下跨域异常并添加对 `node.js 19` 版本的支持 [yi-ge](https://github.com/Chanzhaoyu/chatgpt-web/pull/499)
- 确定清空上下文时不应该重置会话标题

## Other
- 调整文档
- 更新依赖

## v2.10.4

`2023-03-11`

## Feature
- 感谢 [Nothing1024](https://github.com/Chanzhaoyu/chatgpt-web/pull/268) 添加 `Prompt` 模板和 `Prompt` 商店支持

## Enhancement
- 设置添加关闭按钮[#495]

## Demo

![Prompt](https://camo.githubusercontent.com/6a51af751eb29238cb7ef4f8fbd89f63db837562f97f33273095424e62dc9194/68747470733a2f2f73312e6c6f63696d672e636f6d2f323032332f30332f30342f333036326665633163613562632e676966)

## v2.10.3

`2023-03-10`

> 声明:除 `ChatGPTUnofficialProxyAPI` 使用的非官方代理外,本项目代码包括上游引用包均开源在 `GitHub`,如果你觉得本项目有监控后门或有问题导致你的账号、API被封,那我很抱歉。我可能`BUG`写的多,但我不缺德。此次主要为前端界面调整,周末愉快。

## Feature
- 支持长回复 [[yi-ge](https://github.com/Chanzhaoyu/chatgpt-web/pull/450)][[详情](https://github.com/Chanzhaoyu/chatgpt-web/pull/450)]
- 支持 `PWA` [[chenxch](https://github.com/Chanzhaoyu/chatgpt-web/pull/452)]

## Enhancement
- 调整移动端按钮和优化布局
- 调整 `iOS` 上安全距离
- 简化 `docker-compose` 部署 [[cloudGrin](https://github.com/Chanzhaoyu/chatgpt-web/pull/466)]

## BugFix
- 修复清空会话侧边栏标题不会重置的问题 [[RyanXinOne](https://github.com/Chanzhaoyu/chatgpt-web/pull/453)]
- 修复设置文字过长时导致的设置按钮消失的问题

## Other
- 更新依赖

## v2.10.2

`2023-03-09`

衔接 `2.10.1` 版本[详情](https://github.com/Chanzhaoyu/chatgpt-web/releases/tag/v2.10.1)

## Enhancement
- 移动端下输入框获得焦点时左侧按钮隐藏

## BugFix
- 修复 `2.10.1` 中添加 `OPENAI_API_MODEL` 变量的判断错误,会导致默认模型指定失效,抱歉
- 回退 `2.10.1` 中前端变量影响 `Docker` 打包

## v2.10.1

`2023-03-09`

注意:删除了 `.env` 文件改用 `.env.example` 代替,如果是手动部署的同学现在需要手动创建 `.env` 文件并从 `.env.example` 中复制需要的变量,并且 `.env` 文件现在会在 `Git` 提交中被忽略,原因如下:

- 在项目中添加 `.env` 从一开始就是个错误的示范
- 如果是 `Fork` 项目进行修改测试总是会被 `Git` 修改提示给打扰
- 感谢 [yi-ge](https://github.com/Chanzhaoyu/chatgpt-web/pull/395) 的提醒和修改


这两天开始,官方已经开始对第三方代理进行了拉闸, `accessToken` 即将或已经开始可能会不可使用。异常 `API` 使用也开始封号,封号缘由不明,如果出现使用 `API` 提示错误,请查看后端控制台信息,或留意邮箱。

## Feature
- 感谢 [CornerSkyless](https://github.com/Chanzhaoyu/chatgpt-web/pull/393) 添加是否发送上下文开关功能

## Enhancement
- 感谢 [nagaame](https://github.com/Chanzhaoyu/chatgpt-web/pull/415) 优化`docker`打包镜像文件过大的问题
- 感谢 [xieccc](https://github.com/Chanzhaoyu/chatgpt-web/pull/404) 新增 `API` 模型配置变量 `OPENAI_API_MODEL`
- 感谢 [acongee](https://github.com/Chanzhaoyu/chatgpt-web/pull/394) 优化输出时滚动条问题

## BugFix
- 感谢 [CornerSkyless](https://github.com/Chanzhaoyu/chatgpt-web/pull/392) 修复导出图片会丢失头像的问题
- 修复深色模式导出图片的样式问题


## v2.10.0

`2023-03-07`

- 老规矩,手动部署的同学需要删除 `node_modules` 安装包重新安装降低出错概率,其他部署不受影响,但是可能会有缓存问题。
- 虽然说了更新放缓,但是 `issues` 不看, `PR` 不改我睡不着,我的邮箱从每天早上`8`点到凌晨`12`永远在滴滴滴,所以求求各位,超时的`issues`自己关闭下哈,我真的需要缓冲一下。
- 演示图片请看最后

## Feature
- 添加权限功能,用法:`service/.env` 中的 `AUTH_SECRET_KEY` 变量添加密码
- 感谢 [PeterDaveHello](https://github.com/Chanzhaoyu/chatgpt-web/pull/348) 添加「繁体中文」翻译
- 感谢 [GermMC](https://github.com/Chanzhaoyu/chatgpt-web/pull/369) 添加聊天记录导入、导出、清空的功能
- 感谢 [CornerSkyless](https://github.com/Chanzhaoyu/chatgpt-web/pull/374) 添加会话保存为本地图片的功能


## Enhancement
- 感谢 [CornerSkyless](https://github.com/Chanzhaoyu/chatgpt-web/pull/363) 添加 `ctrl+enter`  发送消息
- 现在新消息只有在结束了之后才滚动到底部,而不是之前的强制性
- 优化部分代码

## BugFix
-	转义状态码前端显示,防止直接暴露 `key`(我可能需要更多的状态码补充)

## Other
- 更新依赖到最新

## 演示
> 不是界面最新效果,有美化改动

权限

![权限](https://user-images.githubusercontent.com/24789441/223438518-80d58d42-e344-4e39-b87c-251ff73925ed.png)

聊天记录导出

![聊天记录导出](https://user-images.githubusercontent.com/57023771/223372153-6d8e9ec1-d82c-42af-b4bd-232e50504a25.gif)

保存图片到本地

![保存图片到本地](https://user-images.githubusercontent.com/13901424/223423555-b69b95ef-8bcf-4951-a7c9-98aff2677e18.gif)

## v2.9.3

`2023-03-06`

## Enhancement
- 感谢 [ChandlerVer5](https://github.com/Chanzhaoyu/chatgpt-web/pull/305) 使用 `markdown-it` 替换 `marked`,解决代码块闪烁的问题
- 感谢 [shansing](https://github.com/Chanzhaoyu/chatgpt-web/pull/277) 改善文档
- 感谢 [nalf3in](https://github.com/Chanzhaoyu/chatgpt-web/pull/293) 添加英文翻译

## BugFix
- 感谢[sepcnt ](https://github.com/Chanzhaoyu/chatgpt-web/pull/279) 修复切换记录时编辑状态未关闭的问题
- 修复复制代码的兼容性报错问题
- 修复部分优化小问题

## v2.9.2

`2023-03-04`

手动部署的同学,务必删除根目录和`service`中的`node_modules`重新安装依赖,降低出现问题的概率,自动部署的不需要做改动。

### Feature
- 感谢 [hyln9](https://github.com/Chanzhaoyu/chatgpt-web/pull/247) 添加对渲染 `LaTex` 数学公式的支持
- 感谢 [ottocsb](https://github.com/Chanzhaoyu/chatgpt-web/pull/227) 添加支持 `webAPP` (苹果添加到主页书签访问)支持
- 添加 `OPENAI_API_BASE_URL` 可选环境变量[#249]
## Enhancement
- 优化在高分屏上主题内容的最大宽度[#257]
- 现在文字按单词截断[#215][#225]
### BugFix
- 修复动态生成时代码块不能被复制的问题[#251][#260]
- 修复 `iOS` 移动端输入框不会被键盘顶起的问题[#256]
- 修复控制台渲染警告
## Other
- 更新依赖至最新
- 修改 `README` 内容

## v2.9.1

`2023-03-02`

### Feature
- 代码块添加当前代码语言显示和复制功能[#197][#196]
- 完善多语言,现在可以切换中英文显示

## Enhancement
- 由[Zo3i](https://github.com/Chanzhaoyu/chatgpt-web/pull/187) 完善 `docker-compose` 部署文档

### BugFix
- 由 [ottocsb](https://github.com/Chanzhaoyu/chatgpt-web/pull/200) 修复头像修改不同步的问题
## Other
- 更新依赖至最新
- 修改 `README` 内容
## v2.9.0

`2023-03-02`

### Feature
- 现在能复制带格式的消息文本
- 新设计的设定页面,可以自定义姓名、描述、头像(链接方式)
- 新增`403`和`404`页面以便扩展

## Enhancement
- 更新 `chatgpt` 使 `ChatGPTAPI` 支持 `gpt-3.5-turbo-0301`(默认)
- 取消了前端超时限制设定

## v2.8.3

`2023-03-01`

### Feature
- 消息已输出内容不会因为中断而消失[#167]
- 添加复制消息按钮[#133]

### Other
- `README` 添加声明内容

## v2.8.2

`2023-02-28`
### Enhancement
- 代码主题调整为 `One Dark - light|dark` 适配深色模式
### BugFix
- 修复普通文本代码渲染和深色模式下的问题[#139][#154]

## v2.8.1

`2023-02-27`

### BugFix
- 修复 `API` 版本不是 `Markdown` 时,普通 `HTML` 代码会被渲染的问题 [#146]

## v2.8.0

`2023-02-27`

- 感谢 [puppywang](https://github.com/Chanzhaoyu/chatgpt-web/commit/628187f5c3348bda0d0518f90699a86525d19018) 修复了 `2.7.0` 版本中关于流输出数据的问题(使用 `nginx` 需要自行配置 `octet-stream` 相关内容)

- 关于为什么使用 `octet-stream` 而不是 `sse`,是因为更好的兼容之前的模式。

- 建议更新到此版本获得比较完整的体验

### Enhancement
- 优化了部份代码和类型提示
- 输入框添加换行提示
- 移动端输入框现在回车为换行,而不是直接提交
- 移动端双击标题返回顶部,箭头返回底部

### BugFix
- 流输出数据下的问题[#122]
- 修复了 `API Key` 下部份代码不换行的问题
- 修复移动端深色模式部份样式问题[#123][#126]
- 修复主题模式图标不一致的问题[#126]

## v2.7.3

`2023-02-25`

### Feature
- 适配系统深色模式 [#118](https://github.com/Chanzhaoyu/chatgpt-web/issues/103)
### BugFix
- 修复用户消息能被渲染为 `HTML` 问题 [#117](https://github.com/Chanzhaoyu/chatgpt-web/issues/117)

## v2.7.2

`2023-02-24`
### Enhancement
- 消息使用 [github-markdown-css](https://www.npmjs.com/package/github-markdown-css) 进行美化,现在支持全语法
- 移除测试无用函数

## v2.7.1

`2023-02-23`

因为消息流在 `accessToken` 中存在解析失败和消息不完整等一系列的问题,调整回正常消息形式

### Feature
- 现在可以中断请求过长没有答复的消息
- 现在可以删除单条消息
- 设置中显示当前版本信息

### BugFix
- 回退 `2.7.0` 的消息不稳定的问题

## v2.7.0

`2023-02-23`

### Feature
- 使用消息流返回信息,反应更迅速

### Enhancement
- 样式的一点小改动

## v2.6.2

`2023-02-22`
### BugFix
- 还原修改代理导致的异常问题

## v2.6.1

`2023-02-22`

### Feature
- 新增 `Railway` 部署模版

### BugFix
- 手动打包 `Proxy` 问题

## v2.6.0

`2023-02-21`
### Feature
- 新增对 `网页 accessToken` 调用 `ChatGPT`,更智能不过不太稳定 [#51](https://github.com/Chanzhaoyu/chatgpt-web/issues/51)
- 前端页面设置按钮显示查看当前后端服务配置

### Enhancement
- 新增 `TIMEOUT_MS` 环境变量设定后端超时时常(单位:毫秒)[#62](https://github.com/Chanzhaoyu/chatgpt-web/issues/62)

## v2.5.2

`2023-02-21`
### Feature
- 增加对 `markdown` 格式的支持 [Demo](https://github.com/Chanzhaoyu/chatgpt-web/pull/77)
### BugFix
- 重载会话时滚动条保持

## v2.5.1

`2023-02-21`

### Enhancement
- 调整路由模式为 `hash`
- 调整新增会话添加到
- 调整移动端样式


## v2.5.0

`2023-02-20`

### Feature
- 会话 `loading` 现在显示为光标动画
- 会话现在可以再次生成回复
- 会话异常可以再次进行请求
- 所有删除选项添加确认操作

### Enhancement
- 调整 `chat` 为路由页面而不是组件形式
- 更新依赖至最新
- 调整移动端体验

### BugFix
- 修复移动端左侧菜单显示不完整的问题

## v2.4.1

`2023-02-18`

### Enhancement
- 调整部份移动端上的样式
- 输入框支持换行

## v2.4.0

`2023-02-17`

### Feature
- 响应式支持移动端
### Enhancement
- 修改部份描述错误

## v2.3.3

`2023-02-16`

### Feature
- 添加 `README` 部份说明和贡献列表
- 添加 `docker` 镜像
- 添加 `GitHub Action` 自动化构建

### BugFix
- 回退依赖更新导致的 [Eslint 报错](https://github.com/eslint/eslint/issues/16896)

## v2.3.2

`2023-02-16`

### Enhancement
- 更新依赖至最新
- 优化部份内容

## v2.3.1

`2023-02-15`

### BugFix
- 修复多会话状态下一些意想不到的问题

## v2.3.0

`2023-02-15`
### Feature
- 代码类型信息高亮显示
- 支持 `node ^16` 版本
- 移动端响应式初步支持
- `vite` 中 `proxy` 代理

### Enhancement
- 调整超时处理范围

### BugFix
- 修复取消请求错误提示会添加到信息中
- 修复部份情况下提交请求不可用
- 修复侧边栏宽度变化闪烁的问题

## v2.2.0

`2023-02-14`
### Feature
- 会话和上下文本地储存
- 侧边栏本地储存

## v2.1.0

`2023-02-14`
### Enhancement
- 更新依赖至最新
- 联想功能移动至前端提交,后端只做转发

### BugFix
- 修复部份项目检测有关 `Bug`
- 修复清除上下文按钮失效

## v2.0.0

`2023-02-13`
### Refactor
重构并优化大部分内容

## v1.0.5

`2023-02-12`

### Enhancement
- 输入框焦点,连续提交

### BugFix
- 修复信息框样式问题
- 修复中文输入法提交问题

## v1.0.4

`2023-02-11`

### Feature
- 支持上下文联想

## v1.0.3

`2023-02-11`

### Enhancement
- 拆分 `service` 文件以便扩展
- 调整 `Eslint` 相关验证

### BugFix
- 修复部份控制台报错

## v1.0.2

`2023-02-10`

### BugFix
- 修复新增信息容器不会自动滚动到问题
- 修复文本过长不换行到问题 [#1](https://github.com/Chanzhaoyu/chatgpt-web/issues/1)


================================================
FILE: CONTRIBUTING.en.md
================================================
# Contribution Guide
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.

## Semantic Versioning
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.

Each major change will be recorded in the `changelog`.

## Submitting Pull Request
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.
2. Install the `pnpm` tool using `npm install pnpm -g`.
3. Install the `Eslint` plugin for `VSCode`, or enable `eslint` functionality for other editors such as `WebStorm`.
4. Execute `pnpm bootstrap` in the root directory.
5. Execute `pnpm install` in the `/service/` directory.
6. Make changes to the codebase. If applicable, ensure that appropriate testing has been done.
7. Execute `pnpm lint:fix` in the root directory to perform a code formatting check.
8. Execute `pnpm type-check` in the root directory to perform a type check.
9. Submit a git commit, following the [Commit Guidelines](#commit-guidelines).
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).

## Commit Guidelines

Commit messages should follow the [conventional-changelog standard](https://www.conventionalcommits.org/en/v1.0.0/):

```bash
<type>[optional scope]: <description>

[optional body]

[optional footer]
```

### Commit Types

The following is a list of commit types:

- feat: New feature or functionality
- fix: Bug fix
- docs: Documentation update
- style: Code style or component style update
- refactor: Code refactoring, no new features or bug fixes introduced
- perf: Performance optimization
- test: Unit test
- chore: Other commits that do not modify src or test files


## License

[MIT](./license)

================================================
FILE: CONTRIBUTING.md
================================================
# 贡献指南
感谢你的宝贵时间。你的贡献将使这个项目变得更好!在提交贡献之前,请务必花点时间阅读下面的入门指南。

## 语义化版本
该项目遵循语义化版本。我们对重要的漏洞修复发布修订号,对新特性或不重要的变更发布次版本号,对重大且不兼容的变更发布主版本号。

每个重大更改都将记录在 `changelog` 中。

## 提交 Pull Request
1. Fork [此仓库](https://github.com/Chanzhaoyu/chatgpt-web),从 `main` 创建分支。新功能实现请发 pull request 到 `feature` 分支。其他更改发到 `main` 分支。
2. 使用 `npm install pnpm -g` 安装 `pnpm` 工具。
3. `vscode` 安装了 `Eslint` 插件,其它编辑器如 `webStorm` 打开了 `eslint` 功能。
4. 根目录下执行 `pnpm bootstrap`。
5. `/service/` 目录下执行 `pnpm install`。
6. 对代码库进行更改。如果适用的话,请确保进行了相应的测试。
7. 请在根目录下执行 `pnpm lint:fix` 进行代码格式检查。
8. 请在根目录下执行 `pnpm type-check` 进行类型检查。
9. 提交 git commit, 请同时遵守 [Commit 规范](#commit-指南)
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)。

## Commit 指南

Commit messages 请遵循[conventional-changelog 标准](https://www.conventionalcommits.org/en/v1.0.0/):

```bash
<类型>[可选 范围]: <描述>

[可选 正文]

[可选 脚注]
```

### Commit 类型

以下是 commit 类型列表:

- feat: 新特性或功能
- fix: 缺陷修复
- docs: 文档更新
- style: 代码风格或者组件样式更新
- refactor: 代码重构,不引入新功能和缺陷修复
- perf: 性能优化
- test: 单元测试
- chore: 其他不修改 src 或测试文件的提交


## License

[MIT](./license)


================================================
FILE: Dockerfile
================================================
# build front-end
FROM node:lts-alpine AS frontend

RUN npm install pnpm -g

WORKDIR /app

COPY ./package.json /app

COPY ./pnpm-lock.yaml /app

RUN pnpm install

COPY . /app

RUN pnpm run build

# build backend
FROM node:lts-alpine as backend

RUN npm install pnpm -g

WORKDIR /app

COPY /service/package.json /app

COPY /service/pnpm-lock.yaml /app

RUN pnpm install

COPY /service /app

RUN pnpm build

# service
FROM node:lts-alpine

RUN npm install pnpm -g

WORKDIR /app

COPY /service/package.json /app

COPY /service/pnpm-lock.yaml /app

RUN pnpm install --production && rm -rf /root/.npm /root/.pnpm-store /usr/local/share/.cache /tmp/*

COPY /service /app

COPY --from=frontend /app/dist /app/public

COPY --from=backend /app/build /app/build

EXPOSE 3002

CMD ["pnpm", "run", "prod"]


================================================
FILE: README.md
================================================
# ChatGPT Web

> Disclaimer: This project is only published on GitHub, based on the MIT license, free and for open source learning usage. And there will be no any form of account selling, paid service, discussion group, discussion group and other behaviors. Beware of being deceived.

[中文](README.zh.md)

![cover](./docs/c1.png)
![cover2](./docs/c2.png)

- [ChatGPT Web](#chatgpt-web)
	- [Introduction](#introduction)
	- [Roadmap](#roadmap)
	- [Prerequisites](#prerequisites)
		- [Node](#node)
		- [PNPM](#pnpm)
		- [Filling in the Key](#filling-in-the-key)
	- [Install Dependencies](#install-dependencies)
		- [Backend](#backend)
		- [Frontend](#frontend)
	- [Run in Test Environment](#run-in-test-environment)
		- [Backend Service](#backend-service)
		- [Frontend Webpage](#frontend-webpage)
	- [Environment Variables](#environment-variables)
	- [Packaging](#packaging)
		- [Use Docker](#use-docker)
			- [Docker Parameter Examples](#docker-parameter-examples)
			- [Docker build \& Run](#docker-build--run)
			- [Docker compose](#docker-compose)
			- [Prevent Crawlers](#prevent-crawlers)
		- [Deploy with Railway](#deploy-with-railway)
			- [Railway Environment Variables](#railway-environment-variables)
		- [Deploy with Sealos](#deploy-with-sealos)
		- [Package Manually](#package-manually)
			- [Backend Service](#backend-service-1)
			- [Frontend Webpage](#frontend-webpage-1)
	- [FAQ](#faq)
	- [Contributing](#contributing)
	- [Acknowledgements](#acknowledgements)
	- [Sponsors](#sponsors)
	- [License](#license)
## Introduction

Supports dual models and provides two unofficial `ChatGPT API` methods

| Method                             | Free? | Reliability | Quality |
| ---------------------------------- | ----- | ----------- | ------- |
| `ChatGPTAPI(gpt-3.5-turbo-0301)`   | No    | Reliable    | Relatively stupid |
| `ChatGPTUnofficialProxyAPI(web accessToken)` | Yes   | Relatively unreliable | Smart |

Comparison:
1. `ChatGPTAPI` uses `gpt-3.5-turbo` through `OpenAI` official `API` to call `ChatGPT`
2. `ChatGPTUnofficialProxyAPI` uses unofficial proxy server to access `ChatGPT`'s backend `API`, bypass `Cloudflare` (dependent on third-party servers, and has rate limits)

Warnings:
1. You should first use the `API` method
2. When using the `API`, if the network is not working, it is blocked in China, you need to build your own proxy, never use someone else's public proxy, which is dangerous.
3. When using the `accessToken` method, the reverse proxy will expose your access token to third parties. This should not have any adverse effects, but please consider the risks before using this method.
4. When using `accessToken`, whether you are a domestic or foreign machine, proxies will be used. The default proxy is [pengzhile](https://github.com/pengzhile)'s `https://ai.fakeopen.com/api/conversation`. This is not a backdoor or monitoring unless you have the ability to flip over `CF` verification yourself. Use beforehand acknowledge. [Community Proxy](https://github.com/transitive-bullshit/chatgpt-api#reverse-proxy) (Note: Only these two are recommended, other third-party sources, please identify for yourself)
5. When publishing the project to public network, you should set the `AUTH_SECRET_KEY` variable to add your password access, you should also modify the `title` in `index. html` to prevent it from being searched by keywords.

Switching methods:
1. Enter the `service/.env.example` file, copy the contents to the `service/.env` file
2. To use `OpenAI API Key`, fill in the `OPENAI_API_KEY` field [(get apiKey)](https://platform.openai.com/overview)
3. To use `Web API`, fill in the `OPENAI_ACCESS_TOKEN` field [(get accessToken)](https://chat.openai.com/api/auth/session)
4. `OpenAI API Key` takes precedence when both exist

Environment variables:

See all parameter variables [here](#environment-variables)

## Roadmap
[✓] Dual models

[✓] Multi-session storage and context logic

[✓] Formatting and beautification of code and other message types

[✓] Access control

[✓] Data import/export

[✓] Save messages as local images

[✓] Multilingual interface

[✓] Interface themes

[✗] More...

## Prerequisites

### Node

`node` requires version `^16 || ^18 || ^19` (`node >= 14` needs [fetch polyfill](https://github.com/developit/unfetch#usage-as-a-polyfill) installation), use [nvm](https://github.com/nvm-sh/nvm) to manage multiple local `node` versions

```shell
node -v
```

### PNPM
If you haven't installed `pnpm`
```shell
npm install pnpm -g
```

### Filling in the Key
Get `Openai Api Key` or `accessToken` and fill in the local environment variables [Go to Introduction](#introduction)

```
# service/.env file

# OpenAI API Key - https://platform.openai.com/overview
OPENAI_API_KEY=

# change this to an `accessToken` extracted from the ChatGPT site's `https://chat.openai.com/api/auth/session` response
OPENAI_ACCESS_TOKEN=
```

## Install Dependencies

> For the convenience of "backend developers" to understand the burden, the front-end "workspace" mode is not adopted, but separate folders are used to store them. If you only need to do secondary development of the front-end page, delete the `service` folder.

### Backend

Enter the folder `/service` and run the following commands

```shell
pnpm install
```

### Frontend
Run the following commands at the root directory
```shell
pnpm bootstrap
```

## Run in Test Environment
### Backend Service

Enter the folder `/service` and run the following commands

```shell
pnpm start
```

### Frontend Webpage
Run the following commands at the root directory
```shell
pnpm dev
```

## Environment Variables

`API` available:

- `OPENAI_API_KEY` and `OPENAI_ACCESS_TOKEN` choose one
- `OPENAI_API_MODEL` Set model, optional, default: `gpt-3.5-turbo`
- `OPENAI_API_BASE_URL` Set interface address, optional, default: `https://api.openai.com`
- `OPENAI_API_DISABLE_DEBUG` Set interface to close debug logs, optional, default: empty does not close

`ACCESS_TOKEN` available:

- `OPENAI_ACCESS_TOKEN` and `OPENAI_API_KEY` choose one, `OPENAI_API_KEY` takes precedence when both exist
- `API_REVERSE_PROXY` Set reverse proxy, optional, default: `https://ai.fakeopen.com/api/conversation`, [Community](https://github.com/transitive-bullshit/chatgpt-api#reverse-proxy) (Note: Only these two are recommended, other third party sources, please identify for yourself)

Common:

- `AUTH_SECRET_KEY` Access permission key, optional
- `MAX_REQUEST_PER_HOUR` Maximum number of requests per hour, optional, unlimited by default
- `TIMEOUT_MS` Timeout, unit milliseconds, optional
- `SOCKS_PROXY_HOST` and `SOCKS_PROXY_PORT` take effect together, optional
- `SOCKS_PROXY_PORT` and `SOCKS_PROXY_HOST` take effect together, optional
- `HTTPS_PROXY` Support `http`, `https`, `socks5`, optional
- `ALL_PROXY` Support `http`, `https`, `socks5`, optional

## Packaging

### Use Docker

#### Docker Parameter Examples

![docker](./docs/docker.png)

#### Docker build & Run

```bash
docker build -t chatgpt-web .

# Foreground running
docker run --name chatgpt-web --rm -it -p 127.0.0.1:3002:3002 --env OPENAI_API_KEY=your_api_key chatgpt-web

# Background running
docker run --name chatgpt-web -d -p 127.0.0.1:3002:3002 --env OPENAI_API_KEY=your_api_key chatgpt-web

# Run address
http://localhost:3002/
```

#### Docker compose

[Hub address](https://hub.docker.com/repository/docker/chenzhaoyu94/chatgpt-web/general)

```yml
version: '3'

services:
  app:
    image: chenzhaoyu94/chatgpt-web # always use latest, pull the tag image again to update
    ports:
      - 127.0.0.1:3002:3002
    environment:
      # choose one
      OPENAI_API_KEY: sk-xxx
      # choose one
      OPENAI_ACCESS_TOKEN: xxx
      # API interface address, optional, available when OPENAI_API_KEY is set
      OPENAI_API_BASE_URL: xxx
      # API model, optional, available when OPENAI_API_KEY is set, https://platform.openai.com/docs/models
      # gpt-4, gpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-4-turbo-preview, gpt-4-0125-preview, gpt-4-1106-preview, gpt-4-0314, gpt-4-0613, gpt-4-32k, gpt-4-32k-0314, gpt-4-32k-0613, gpt-3.5-turbo-16k, gpt-3.5-turbo-16k-0613, gpt-3.5-turbo, gpt-3.5-turbo-0301, gpt-3.5-turbo-0613, text-davinci-003, text-davinci-002, code-davinci-002
      OPENAI_API_MODEL: xxx
      # reverse proxy, optional
      API_REVERSE_PROXY: xxx
      # access permission key, optional
      AUTH_SECRET_KEY: xxx
      # maximum number of requests per hour, optional, unlimited by default
      MAX_REQUEST_PER_HOUR: 0
      # timeout, unit milliseconds, optional
      TIMEOUT_MS: 60000
      # Socks proxy, optional, take effect with SOCKS_PROXY_PORT
      SOCKS_PROXY_HOST: xxx
      # Socks proxy port, optional, take effect with SOCKS_PROXY_HOST
      SOCKS_PROXY_PORT: xxx
      # HTTPS proxy, optional, support http,https,socks5
      HTTPS_PROXY: http://xxx:7890
```

- `OPENAI_API_BASE_URL` Optional, available when `OPENAI_API_KEY` is set
- `OPENAI_API_MODEL` Optional, available when `OPENAI_API_KEY` is set

#### Prevent Crawlers

**nginx**

Fill in the following configuration in the nginx configuration file to prevent crawlers. You can refer to the `docker-compose/nginx/nginx.conf` file to add anti-crawler methods

```
    # Prevent crawlers
    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")
    {
      return 403;
    }
```

### Deploy with Railway

[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template/yytmgc)

#### Railway Environment Variables

| Environment variable name | Required | Remarks |
| --------------------- | ---------------------- | -------------------------------------------------------------------------------------------------- |
| `PORT`                | Required | Default `3002` |
| `AUTH_SECRET_KEY`          | Optional | Access permission key                             |
| `MAX_REQUEST_PER_HOUR`          | Optional | Maximum number of requests per hour, optional, unlimited by default                             |
| `TIMEOUT_MS`          | Optional | Timeout, unit milliseconds                                                                    |
| `OPENAI_API_KEY`      | `OpenAI API` choose one | `apiKey` required for `OpenAI API` [(get apiKey)](https://platform.openai.com/overview)           |
| `OPENAI_ACCESS_TOKEN` | `Web API` choose one | `accessToken` required for `Web API` [(get accessToken)](https://chat.openai.com/api/auth/session) |
| `OPENAI_API_BASE_URL`   | Optional, available when `OpenAI API` | `API` interface address |
| `OPENAI_API_MODEL`   | Optional, available when `OpenAI API` | `API` model |
| `API_REVERSE_PROXY`   | Optional, available when `Web API` | `Web API` reverse proxy address [Details](https://github.com/transitive-bullshit/chatgpt-api#reverse-proxy) |
| `SOCKS_PROXY_HOST`   | Optional, take effect with `SOCKS_PROXY_PORT` | Socks proxy |
| `SOCKS_PROXY_PORT`   | Optional, take effect with `SOCKS_PROXY_HOST` | Socks proxy port |
| `SOCKS_PROXY_USERNAME`   | Optional, take effect with `SOCKS_PROXY_HOST` | Socks proxy username |
| `SOCKS_PROXY_PASSWORD`   | Optional, take effect with `SOCKS_PROXY_HOST` | Socks proxy password |
| `HTTPS_PROXY`   | Optional | HTTPS proxy, support http,https, socks5 |
| `ALL_PROXY`   | Optional | All proxies, support http,https, socks5 |

> Note: Modifying environment variables on `Railway` will re-`Deploy`

### Deploy with Sealos

[![](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dchatgpt-web)

> Environment variables are consistent with Docker environment variables

### Package Manually
#### Backend Service
> If you don't need the `node` interface of this project, you can omit the following operations

Copy the `service` folder to the server where you have the `node` service environment.

```shell
# Install
pnpm install

# Pack
pnpm build

# Run
pnpm prod
```

PS: It is also okay to run `pnpm start` directly on the server without packing

#### Frontend Webpage

1. Modify the `VITE_GLOB_API_URL` field in the `.env` file at the root directory to your actual backend interface address

2. Run the following commands at the root directory, then copy the files in the `dist` folder to the root directory of your website service

[Reference](https://cn.vitejs.dev/guide/static -deploy.html#building-the-app)

```shell
pnpm build
```

## FAQ
Q: Why does `Git` commit always report errors?

A: Because there is a commit message verification, please follow the [Commit Guide](./CONTRIBUTING.md)

Q: Where to change the request interface if only the front-end page is used?

A: The `VITE_GLOB_API_URL` field in the `.env` file at the root directory.

Q: All files explode red when saving?

A: `vscode` please install the recommended plug-ins for the project, or manually install the `Eslint` plug-in.

Q: No typewriter effect on the front end?

A: One possible reason is that after Nginx reverse proxy, buffer is turned on, then Nginx will try to buffer some data from the backend before sending it to the browser. Please try adding `proxy_buffering off; ` after the reverse proxy parameter, then reload Nginx. Other web server configurations are similar.

## Contributing

Please read the [Contributing Guide](./CONTRIBUTING.md) before contributing

Thanks to everyone who has contributed!

<a href="https://github.com/Chanzhaoyu/chatgpt-web/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=Chanzhaoyu/chatgpt-web" />
</a>

## Acknowledgements

Thanks to [JetBrains](https://www.jetbrains.com/) SoftWare for providing free Open Source license for this project.

## Sponsors

If you find this project helpful and can afford it, you can give me a little support. Anyway, thanks for your support~

<div style="display: flex; gap: 20px;">
	<div style="text-align: center">
		<img style="max-width: 100%" src="./docs/wechat.png" alt="WeChat" />
		<p>WeChat Pay</p>
	</div>
	<div style="text-align: center">
		<img style="max-width: 100%" src="./docs/alipay.png" alt="Alipay" />
		<p>Alipay</p>
	</div>
</div>

## License
MIT © [ChenZhaoYu]


================================================
FILE: README.zh.md
================================================
# ChatGPT Web

> 声明:此项目只发布于 GitHub,基于 MIT 协议,免费且作为开源学习使用。并且不会有任何形式的卖号、付费服务、讨论群、讨论组等行为。谨防受骗。

[English](README.md)

![cover](./docs/c1.png)
![cover2](./docs/c2.png)

- [ChatGPT Web](#chatgpt-web)
	- [介绍](#介绍)
	- [待实现路线](#待实现路线)
	- [前置要求](#前置要求)
		- [Node](#node)
		- [PNPM](#pnpm)
		- [填写密钥](#填写密钥)
	- [安装依赖](#安装依赖)
		- [后端](#后端)
		- [前端](#前端)
	- [测试环境运行](#测试环境运行)
		- [后端服务](#后端服务)
		- [前端网页](#前端网页)
	- [环境变量](#环境变量)
	- [打包](#打包)
		- [使用 Docker](#使用-docker)
			- [Docker 参数示例](#docker-参数示例)
			- [Docker build \& Run](#docker-build--run)
			- [Docker compose](#docker-compose)
			- [防止爬虫抓取](#防止爬虫抓取)
		- [使用 Railway 部署](#使用-railway-部署)
			- [Railway 环境变量](#railway-环境变量)
		- [使用 Sealos 部署](#使用-sealos-部署)
		- [手动打包](#手动打包)
			- [后端服务](#后端服务-1)
			- [前端网页](#前端网页-1)
	- [常见问题](#常见问题)
	- [参与贡献](#参与贡献)
	- [致谢](#致谢)
	- [赞助](#赞助)
	- [License](#license)
## 介绍

支持双模型,提供了两种非官方 `ChatGPT API` 方法

| 方式                                          | 免费? | 可靠性     | 质量 |
| --------------------------------------------- | ------ | ---------- | ---- |
| `ChatGPTAPI(gpt-3.5-turbo-0301)`                           | 否     | 可靠       | 相对较笨 |
| `ChatGPTUnofficialProxyAPI(网页 accessToken)` | 是     | 相对不可靠 | 聪明 |

对比:
1. `ChatGPTAPI` 使用 `gpt-3.5-turbo` 通过 `OpenAI` 官方 `API` 调用 `ChatGPT`
2. `ChatGPTUnofficialProxyAPI` 使用非官方代理服务器访问 `ChatGPT` 的后端`API`,绕过`Cloudflare`(依赖于第三方服务器,并且有速率限制)

警告:
1. 你应该首先使用 `API` 方式
2. 使用 `API` 时,如果网络不通,那是国内被墙了,你需要自建代理,绝对不要使用别人的公开代理,那是危险的。
3. 使用 `accessToken` 方式时反向代理将向第三方暴露您的访问令牌,这样做应该不会产生任何不良影响,但在使用这种方法之前请考虑风险。
4. 使用 `accessToken` 时,不管你是国内还是国外的机器,都会使用代理。默认代理为 [pengzhile](https://github.com/pengzhile) 大佬的 `https://ai.fakeopen.com/api/conversation`,这不是后门也不是监听,除非你有能力自己翻过 `CF` 验证,用前请知悉。[社区代理](https://github.com/transitive-bullshit/chatgpt-api#reverse-proxy)(注意:只有这两个是推荐,其他第三方来源,请自行甄别)
5. 把项目发布到公共网络时,你应该设置 `AUTH_SECRET_KEY` 变量添加你的密码访问权限,你也应该修改 `index.html` 中的 `title`,防止被关键词搜索到。

切换方式:
1. 进入 `service/.env.example` 文件,复制内容到 `service/.env` 文件
2. 使用 `OpenAI API Key` 请填写 `OPENAI_API_KEY` 字段 [(获取 apiKey)](https://platform.openai.com/overview)
3. 使用 `Web API` 请填写 `OPENAI_ACCESS_TOKEN` 字段 [(获取 accessToken)](https://chat.openai.com/api/auth/session)
4. 同时存在时以 `OpenAI API Key` 优先

环境变量:

全部参数变量请查看或[这里](#环境变量)

```
/service/.env.example
```

## 待实现路线
[✓] 双模型

[✓] 多会话储存和上下文逻辑

[✓] 对代码等消息类型的格式化美化处理

[✓] 访问权限控制

[✓] 数据导入、导出

[✓] 保存消息到本地图片

[✓] 界面多语言

[✓] 界面主题

[✗] More...

## 前置要求

### Node

`node` 需要 `^16 || ^18 || ^19` 版本(`node >= 14` 需要安装 [fetch polyfill](https://github.com/developit/unfetch#usage-as-a-polyfill)),使用 [nvm](https://github.com/nvm-sh/nvm) 可管理本地多个 `node` 版本

```shell
node -v
```

### PNPM
如果你没有安装过 `pnpm`
```shell
npm install pnpm -g
```

### 填写密钥
获取 `Openai Api Key` 或 `accessToken` 并填写本地环境变量 [跳转](#介绍)

```
# service/.env 文件

# OpenAI API Key - https://platform.openai.com/overview
OPENAI_API_KEY=

# change this to an `accessToken` extracted from the ChatGPT site's `https://chat.openai.com/api/auth/session` response
OPENAI_ACCESS_TOKEN=
```

## 安装依赖

> 为了简便 `后端开发人员` 的了解负担,所以并没有采用前端 `workspace` 模式,而是分文件夹存放。如果只需要前端页面做二次开发,删除 `service` 文件夹即可。

### 后端

进入文件夹 `/service` 运行以下命令

```shell
pnpm install
```

### 前端
根目录下运行以下命令
```shell
pnpm bootstrap
```

## 测试环境运行
### 后端服务

进入文件夹 `/service` 运行以下命令

```shell
pnpm start
```

### 前端网页
根目录下运行以下命令
```shell
pnpm dev
```

## 环境变量

`API` 可用:

- `OPENAI_API_KEY` 和 `OPENAI_ACCESS_TOKEN` 二选一
- `OPENAI_API_MODEL`  设置模型,可选,默认:`gpt-3.5-turbo`
- `OPENAI_API_BASE_URL` 设置接口地址,可选,默认:`https://api.openai.com`
- `OPENAI_API_DISABLE_DEBUG` 设置接口关闭 debug 日志,可选,默认:empty 不关闭

`ACCESS_TOKEN` 可用:

- `OPENAI_ACCESS_TOKEN`  和 `OPENAI_API_KEY` 二选一,同时存在时,`OPENAI_API_KEY` 优先
- `API_REVERSE_PROXY` 设置反向代理,可选,默认:`https://ai.fakeopen.com/api/conversation`,[社区](https://github.com/transitive-bullshit/chatgpt-api#reverse-proxy)(注意:只有这两个是推荐,其他第三方来源,请自行甄别)

通用:

- `AUTH_SECRET_KEY` 访问权限密钥,可选
- `MAX_REQUEST_PER_HOUR` 每小时最大请求次数,可选,默认无限
- `TIMEOUT_MS` 超时,单位毫秒,可选
- `SOCKS_PROXY_HOST` 和 `SOCKS_PROXY_PORT` 一起时生效,可选
- `SOCKS_PROXY_PORT` 和 `SOCKS_PROXY_HOST` 一起时生效,可选
- `HTTPS_PROXY` 支持 `http`,`https`, `socks5`,可选
- `ALL_PROXY` 支持 `http`,`https`, `socks5`,可选

## 打包

### 使用 Docker

#### Docker 参数示例

![docker](./docs/docker.png)

#### Docker build & Run

```bash
docker build -t chatgpt-web .

# 前台运行
docker run --name chatgpt-web --rm -it -p 127.0.0.1:3002:3002 --env OPENAI_API_KEY=your_api_key chatgpt-web

# 后台运行
docker run --name chatgpt-web -d -p 127.0.0.1:3002:3002 --env OPENAI_API_KEY=your_api_key chatgpt-web

# 运行地址
http://localhost:3002/
```

#### Docker compose

[Hub 地址](https://hub.docker.com/repository/docker/chenzhaoyu94/chatgpt-web/general)

```yml
version: '3'

services:
  app:
    image: chenzhaoyu94/chatgpt-web # 总是使用 latest ,更新时重新 pull 该 tag 镜像即可
    ports:
      - 127.0.0.1:3002:3002
    environment:
      # 二选一
      OPENAI_API_KEY: sk-xxx
      # 二选一
      OPENAI_ACCESS_TOKEN: xxx
      # API接口地址,可选,设置 OPENAI_API_KEY 时可用
      OPENAI_API_BASE_URL: xxx
      # API模型,可选,设置 OPENAI_API_KEY 时可用,https://platform.openai.com/docs/models
      # gpt-4, gpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-4-turbo-preview, gpt-4-0125-preview, gpt-4-1106-preview, gpt-4-0314, gpt-4-0613, gpt-4-32k, gpt-4-32k-0314, gpt-4-32k-0613, gpt-3.5-turbo-16k, gpt-3.5-turbo-16k-0613, gpt-3.5-turbo, gpt-3.5-turbo-0301, gpt-3.5-turbo-0613, text-davinci-003, text-davinci-002, code-davinci-002
      OPENAI_API_MODEL: xxx
      # 反向代理,可选
      API_REVERSE_PROXY: xxx
      # 访问权限密钥,可选
      AUTH_SECRET_KEY: xxx
      # 每小时最大请求次数,可选,默认无限
      MAX_REQUEST_PER_HOUR: 0
      # 超时,单位毫秒,可选
      TIMEOUT_MS: 60000
      # Socks代理,可选,和 SOCKS_PROXY_PORT 一起时生效
      SOCKS_PROXY_HOST: xxx
      # Socks代理端口,可选,和 SOCKS_PROXY_HOST 一起时生效
      SOCKS_PROXY_PORT: xxx
      # HTTPS 代理,可选,支持 http,https,socks5
      HTTPS_PROXY: http://xxx:7890
```
- `OPENAI_API_BASE_URL`  可选,设置 `OPENAI_API_KEY` 时可用
- `OPENAI_API_MODEL`  可选,设置 `OPENAI_API_KEY` 时可用

#### 防止爬虫抓取

**nginx**

将下面配置填入nginx配置文件中,可以参考 `docker-compose/nginx/nginx.conf` 文件中添加反爬虫的方法

```
    # 防止爬虫抓取
    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")
    {
      return 403;
    }
```

###  使用 Railway 部署

[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template/yytmgc)

#### Railway 环境变量

| 环境变量名称          | 必填                   | 备注                                                                                               |
| --------------------- | ---------------------- | -------------------------------------------------------------------------------------------------- |
| `PORT`                | 必填                   | 默认 `3002`
| `AUTH_SECRET_KEY`          | 可选                   | 访问权限密钥                                        |
| `MAX_REQUEST_PER_HOUR`          | 可选                   | 每小时最大请求次数,可选,默认无限                                        |
| `TIMEOUT_MS`          | 可选                   | 超时时间,单位毫秒                                                                             |
| `OPENAI_API_KEY`      | `OpenAI API` 二选一    | 使用 `OpenAI API` 所需的 `apiKey` [(获取 apiKey)](https://platform.openai.com/overview)            |
| `OPENAI_ACCESS_TOKEN` | `Web API` 二选一       | 使用 `Web API` 所需的 `accessToken` [(获取 accessToken)](https://chat.openai.com/api/auth/session) |
| `OPENAI_API_BASE_URL`   | 可选,`OpenAI API` 时可用 |  `API`接口地址  |
| `OPENAI_API_MODEL`   | 可选,`OpenAI API` 时可用 |  `API`模型  |
| `API_REVERSE_PROXY`   | 可选,`Web API` 时可用 | `Web API` 反向代理地址 [详情](https://github.com/transitive-bullshit/chatgpt-api#reverse-proxy)    |
| `SOCKS_PROXY_HOST`   | 可选,和 `SOCKS_PROXY_PORT` 一起时生效 | Socks代理    |
| `SOCKS_PROXY_PORT`   | 可选,和 `SOCKS_PROXY_HOST` 一起时生效 | Socks代理端口    |
| `SOCKS_PROXY_USERNAME`   | 可选,和 `SOCKS_PROXY_HOST` 一起时生效 | Socks代理用户名    |
| `SOCKS_PROXY_PASSWORD`   | 可选,和 `SOCKS_PROXY_HOST` 一起时生效 | Socks代理密码    |
| `HTTPS_PROXY`   | 可选 | HTTPS 代理,支持 http,https, socks5    |
| `ALL_PROXY`   | 可选 | 所有代理 代理,支持 http,https, socks5    |

> 注意: `Railway` 修改环境变量会重新 `Deploy`

### 使用 Sealos 部署

[![](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dchatgpt-web)

> 环境变量与 Docker 环境变量一致

### 手动打包
#### 后端服务
> 如果你不需要本项目的 `node` 接口,可以省略如下操作

复制 `service` 文件夹到你有 `node` 服务环境的服务器上。

```shell
# 安装
pnpm install

# 打包
pnpm build

# 运行
pnpm prod
```

PS: 不进行打包,直接在服务器上运行 `pnpm start` 也可

#### 前端网页

1、修改根目录下 `.env` 文件中的 `VITE_GLOB_API_URL` 为你的实际后端接口地址

2、根目录下运行以下命令,然后将 `dist` 文件夹内的文件复制到你网站服务的根目录下

[参考信息](https://cn.vitejs.dev/guide/static-deploy.html#building-the-app)

```shell
pnpm build
```

## 常见问题
Q: 为什么 `Git` 提交总是报错?

A: 因为有提交信息验证,请遵循 [Commit 指南](./CONTRIBUTING.md)

Q: 如果只使用前端页面,在哪里改请求接口?

A: 根目录下 `.env` 文件中的 `VITE_GLOB_API_URL` 字段。

Q: 文件保存时全部爆红?

A: `vscode` 请安装项目推荐插件,或手动安装 `Eslint` 插件。

Q: 前端没有打字机效果?

A: 一种可能原因是经过 Nginx 反向代理,开启了 buffer,则 Nginx 会尝试从后端缓冲一定大小的数据再发送给浏览器。请尝试在反代参数后添加 `proxy_buffering off;`,然后重载 Nginx。其他 web server 配置同理。

## 参与贡献

贡献之前请先阅读 [贡献指南](./CONTRIBUTING.md)

感谢所有做过贡献的人!

<a href="https://github.com/Chanzhaoyu/chatgpt-web/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=Chanzhaoyu/chatgpt-web" />
</a>

## 致谢

感谢 [JetBrains](https://www.jetbrains.com/) 为这个项目提供免费开源许可的软件。

## 赞助

如果你觉得这个项目对你有帮助,并且情况允许的话,可以给我一点点支持,总之非常感谢支持~

<div style="display: flex; gap: 20px;">
	<div style="text-align: center">
		<img style="max-width: 100%" src="./docs/wechat.png" alt="微信" />
		<p>WeChat Pay</p>
	</div>
	<div style="text-align: center">
		<img style="max-width: 100%" src="./docs/alipay.png" alt="支付宝" />
		<p>Alipay</p>
	</div>
</div>

## License
MIT © [ChenZhaoYu](./license)


================================================
FILE: docker-compose/README.md
================================================
### docker-compose Deployment Tutorial
-Put the packaged front-end files in the `nginx/html` directory
- ```shell
  # start up
  docker-compose up -d
  ```
- ```shell
  # Check the running status
  docker ps
  ```
- ```shell
  # end run
  docker-compose down
  ```


================================================
FILE: docker-compose/docker-compose.yml
================================================
version: '3'

services:
  app:
    container_name: chatgpt-web
    image: chenzhaoyu94/chatgpt-web # Always use latest, just pull the tag image again when updating
    ports:
      - 3002:3002
    environment:
      # pick one of two
      OPENAI_API_KEY:
      # pick one of two
      OPENAI_ACCESS_TOKEN:
      # API interface address, optional, available when OPENAI_API_KEY is set
      OPENAI_API_BASE_URL:
      # API model, optional, available when OPENAI_API_KEY is set
      OPENAI_API_MODEL:
      # reverse proxy, optional
      API_REVERSE_PROXY:
      # Access permission key, optional
      AUTH_SECRET_KEY:
      # The maximum number of requests per hour, optional, default unlimited
      MAX_REQUEST_PER_HOUR: 0
      # timeout in milliseconds, optional
      TIMEOUT_MS: 60000
      # Socks proxy, optional, works with SOCKS_PROXY_PORT
      SOCKS_PROXY_HOST:
      # Socks proxy port, optional, effective when combined with SOCKS_PROXY_HOST
      SOCKS_PROXY_PORT:
      # Socks proxy username, optional, effective when combined with SOCKS_PROXY_HOST & SOCKS_PROXY_PORT
      SOCKS_PROXY_USERNAME:
      # Socks proxy password, optional, effective when combined with SOCKS_PROXY_HOST & SOCKS_PROXY_PORT
      SOCKS_PROXY_PASSWORD:
      # HTTPS_PROXY proxy, optional
      HTTPS_PROXY:
  nginx:
    container_name: nginx
    image: nginx:alpine
    ports:
      - '80:80'
    expose:
      - '80'
    volumes:
      - ./nginx/html:/usr/share/nginx/html
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
    links:
      - app


================================================
FILE: docker-compose/nginx/nginx.conf
================================================
server {
	listen 80;
	server_name  localhost;
	charset utf-8;
	error_page   500 502 503 504  /50x.html;
	
	# Prevent crawlers from crawling
	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")
	{
		return 403;
	}
	
	location / {
			root /usr/share/nginx/html;
   		try_files $uri /index.html;
	}

	location /api {
			proxy_set_header   X-Real-IP $remote_addr; #Forward user IP
			proxy_pass http://app:3002;
	}

	proxy_set_header Host $host;
	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header REMOTE-HOST $remote_addr;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}


================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<link rel="icon" type="image/svg+xml" href="/favicon.svg">
	<meta content="yes" name="apple-mobile-web-app-capable"/>
	<link rel="apple-touch-icon" href="/favicon.ico">
	<meta name="viewport"
		content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
	<title>ChatGPT Web</title>
</head>

<body class="dark:bg-black">
	<div id="app">
		<style>
			.loading-wrap {
				display: flex;
				justify-content: center;
				align-items: center;
				height: 100vh;
			}

			.balls {
				width: 4em;
				display: flex;
				flex-flow: row nowrap;
				align-items: center;
				justify-content: space-between;
			}

			.balls div {
				width: 0.8em;
				height: 0.8em;
				border-radius: 50%;
				background-color: #4b9e5f;
			}

			.balls div:nth-of-type(1) {
				transform: translateX(-100%);
				animation: left-swing 0.5s ease-in alternate infinite;
			}

			.balls div:nth-of-type(3) {
				transform: translateX(-95%);
				animation: right-swing 0.5s ease-out alternate infinite;
			}

			@keyframes left-swing {

				50%,
				100% {
					transform: translateX(95%);
				}
			}

			@keyframes right-swing {
				50% {
					transform: translateX(-95%);
				}

				100% {
					transform: translateX(100%);
				}
			}

			@media (prefers-color-scheme: dark) {
				body {
					background: #121212;
				}
			}
		</style>
		<div class="loading-wrap">
			<div class="balls">
				<div></div>
				<div></div>
				<div></div>
			</div>
		</div>
	</div>
	<script type="module" src="/src/main.ts"></script>
</body>

</html>


================================================
FILE: kubernetes/README.md
================================================
## 增加一个Kubernetes的部署方式
```
kubectl apply -f deploy.yaml
```

### 如果需要Ingress域名接入
```
kubectl apply -f ingress.yaml
```


================================================
FILE: kubernetes/deploy.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: chatgpt-web
  labels:
    app: chatgpt-web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: chatgpt-web
  strategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: chatgpt-web
    spec:
      containers:
        - image: chenzhaoyu94/chatgpt-web
          name: chatgpt-web
          imagePullPolicy: Always
          ports:
            - containerPort: 3002
          env:
            - name: OPENAI_API_KEY
              value: sk-xxx
            - name: OPENAI_API_BASE_URL
              value: 'https://api.openai.com'
            - name: OPENAI_API_MODEL
              value: gpt-3.5-turbo
            - name: API_REVERSE_PROXY
              value: https://ai.fakeopen.com/api/conversation
            - name: AUTH_SECRET_KEY
              value: '123456'
            - name: TIMEOUT_MS
              value: '60000'
            - name: SOCKS_PROXY_HOST
              value: ''
            - name: SOCKS_PROXY_PORT
              value: ''
            - name: HTTPS_PROXY
              value: ''
          resources:
            limits:
              cpu: 500m
              memory: 500Mi
            requests:
              cpu: 300m
              memory: 300Mi
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: chatgpt-web
  name: chatgpt-web
spec:
  ports:
    - name: chatgpt-web
      port: 3002
      protocol: TCP
      targetPort: 3002
  selector:
    app: chatgpt-web
  type: ClusterIP


================================================
FILE: kubernetes/ingress.yaml
================================================
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/proxy-connect-timeout: '5'
  name: chatgpt-web
spec:
  rules:
    - host: chatgpt.example.com
      http:
        paths:
          - backend:
              service:
                name: chatgpt-web
                port:
                  number: 3002
            path: /
            pathType: ImplementationSpecific
  tls:
    - secretName: chatgpt-web-tls


================================================
FILE: license
================================================
MIT License

Copyright (c) 2023 ChenZhaoYu

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: package.json
================================================
{
  "name": "chatgpt-web",
  "version": "2.11.1",
  "private": false,
  "description": "ChatGPT Web",
  "author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
  "keywords": [
    "chatgpt-web",
    "chatgpt",
    "chatbot",
    "vue"
  ],
  "scripts": {
    "dev": "vite",
    "build": "run-p type-check build-only",
    "preview": "vite preview",
    "build-only": "vite build",
    "type-check": "vue-tsc --noEmit",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "bootstrap": "pnpm install && pnpm run common:prepare",
    "common:cleanup": "rimraf node_modules && rimraf pnpm-lock.yaml",
    "common:prepare": "husky install"
  },
  "dependencies": {
    "@vscode/markdown-it-katex": "^1.0.3",
    "@vueuse/core": "^9.13.0",
    "highlight.js": "^11.7.0",
    "html-to-image": "^1.11.11",
    "katex": "^0.16.4",
    "markdown-it": "^13.0.1",
    "mermaid-it-markdown": "^1.0.8",
    "naive-ui": "^2.34.3",
    "pinia": "^2.0.33",
    "vue": "^3.2.47",
    "vue-i18n": "^9.2.2",
    "vue-router": "^4.1.6"
  },
  "devDependencies": {
    "@antfu/eslint-config": "^0.35.3",
    "@commitlint/cli": "^17.4.4",
    "@commitlint/config-conventional": "^17.4.4",
    "@iconify/vue": "^4.1.0",
    "@types/crypto-js": "^4.1.1",
    "@types/katex": "^0.16.0",
    "@types/markdown-it": "^12.2.3",
    "@types/markdown-it-link-attributes": "^3.0.1",
    "@types/node": "^18.14.6",
    "@vitejs/plugin-vue": "^4.0.0",
    "autoprefixer": "^10.4.13",
    "axios": "^1.3.4",
    "crypto-js": "^4.1.1",
    "eslint": "^8.35.0",
    "husky": "^8.0.3",
    "less": "^4.1.3",
    "lint-staged": "^13.1.2",
    "markdown-it-link-attributes": "^4.0.1",
    "npm-run-all": "^4.1.5",
    "postcss": "^8.4.21",
    "rimraf": "^4.3.0",
    "tailwindcss": "^3.2.7",
    "typescript": "~4.9.5",
    "vite": "^4.2.0",
    "vite-plugin-pwa": "^0.14.4",
    "vue-tsc": "^1.2.0"
  },
  "lint-staged": {
    "*.{ts,tsx,vue}": [
      "pnpm lint:fix"
    ]
  }
}


================================================
FILE: postcss.config.js
================================================
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}


================================================
FILE: service/.eslintrc.json
================================================
{
  "root": true,
  "ignorePatterns": ["build"],
  "extends": ["@antfu"]
}


================================================
FILE: service/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
.DS_Store
dist
dist-ssr
coverage
*.local

/cypress/videos/
/cypress/screenshots/

# Editor directories and files
.vscode/*
!.vscode/settings.json
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

build


================================================
FILE: service/.npmrc
================================================
enable-pre-post-scripts=true


================================================
FILE: service/.vscode/extensions.json
================================================
{
  "recommendations": ["dbaeumer.vscode-eslint"]
}


================================================
FILE: service/.vscode/settings.json
================================================
{
  "prettier.enable": false,
  "editor.formatOnSave": false,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "eslint.validate": [
    "javascript",
    "typescript",
    "json",
    "jsonc",
    "json5",
    "yaml"
  ],
  "cSpell.words": [
    "antfu",
    "chatgpt",
    "esno",
    "GPTAPI",
    "OPENAI"
  ]
}


================================================
FILE: service/package.json
================================================
{
  "name": "chatgpt-web-service",
  "version": "1.0.0",
  "private": false,
  "description": "ChatGPT Web Service",
  "author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
  "keywords": [
    "chatgpt-web",
    "chatgpt",
    "chatbot",
    "express"
  ],
  "engines": {
    "node": "^16 || ^18 || ^20"
  },
  "scripts": {
    "start": "esno ./src/index.ts",
    "dev": "esno watch ./src/index.ts",
    "prod": "node ./build/index.mjs",
    "build": "pnpm clean && tsup",
    "clean": "rimraf build",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "common:cleanup": "rimraf node_modules && rimraf pnpm-lock.yaml"
  },
  "dependencies": {
    "axios": "^1.3.4",
    "chatgpt": "^5.1.2",
    "dotenv": "^16.0.3",
    "esno": "^4.7.0",
    "express": "^4.18.2",
    "express-rate-limit": "^6.7.0",
    "https-proxy-agent": "^5.0.1",
    "isomorphic-fetch": "^3.0.0",
    "node-fetch": "^3.3.0",
    "socks-proxy-agent": "^7.0.0"
  },
  "devDependencies": {
    "@antfu/eslint-config": "^0.35.3",
    "@types/express": "^4.17.17",
    "@types/node": "^18.14.6",
    "eslint": "^8.35.0",
    "rimraf": "^4.3.0",
    "tsup": "^6.6.3",
    "typescript": "^4.9.5"
  }
}


================================================
FILE: service/src/chatgpt/index.ts
================================================
import * as dotenv from 'dotenv'
import 'isomorphic-fetch'
import type { ChatGPTAPIOptions, ChatMessage, SendMessageOptions } from 'chatgpt'
import { ChatGPTAPI, ChatGPTUnofficialProxyAPI } from 'chatgpt'
import { SocksProxyAgent } from 'socks-proxy-agent'
import httpsProxyAgent from 'https-proxy-agent'
import fetch from 'node-fetch'
import { sendResponse } from '../utils'
import { isNotEmptyString } from '../utils/is'
import type { ApiModel, ChatContext, ChatGPTUnofficialProxyAPIOptions, ModelConfig } from '../types'
import type { RequestOptions, SetProxyOptions, UsageResponse } from './types'

const { HttpsProxyAgent } = httpsProxyAgent

dotenv.config()

const ErrorCodeMessage: Record<string, string> = {
  401: '[OpenAI] 提供错误的API密钥 | Incorrect API key provided',
  403: '[OpenAI] 服务器拒绝访问,请稍后再试 | Server refused to access, please try again later',
  502: '[OpenAI] 错误的网关 |  Bad Gateway',
  503: '[OpenAI] 服务器繁忙,请稍后再试 | Server is busy, please try again later',
  504: '[OpenAI] 网关超时 | Gateway Time-out',
  500: '[OpenAI] 服务器繁忙,请稍后再试 | Internal Server Error',
}

const timeoutMs: number = !isNaN(+process.env.TIMEOUT_MS) ? +process.env.TIMEOUT_MS : 100 * 1000
const disableDebug: boolean = process.env.OPENAI_API_DISABLE_DEBUG === 'true'

let apiModel: ApiModel
const model = isNotEmptyString(process.env.OPENAI_API_MODEL) ? process.env.OPENAI_API_MODEL : 'gpt-3.5-turbo'

if (!isNotEmptyString(process.env.OPENAI_API_KEY) && !isNotEmptyString(process.env.OPENAI_ACCESS_TOKEN))
  throw new Error('Missing OPENAI_API_KEY or OPENAI_ACCESS_TOKEN environment variable')

let api: ChatGPTAPI | ChatGPTUnofficialProxyAPI

(async () => {
  // More Info: https://github.com/transitive-bullshit/chatgpt-api

  if (isNotEmptyString(process.env.OPENAI_API_KEY)) {
    const OPENAI_API_BASE_URL = process.env.OPENAI_API_BASE_URL

    const options: ChatGPTAPIOptions = {
      apiKey: process.env.OPENAI_API_KEY,
      completionParams: { model },
      debug: !disableDebug,
    }

    // increase max token limit if use gpt-4
    if (model.toLowerCase().includes('gpt-4')) {
      // if use 32k model
      if (model.toLowerCase().includes('32k')) {
        options.maxModelTokens = 32768
        options.maxResponseTokens = 8192
      }
      else if (/-4o-mini/.test(model.toLowerCase())) {
        options.maxModelTokens = 128000
        options.maxResponseTokens = 16384
      }
      // if use GPT-4 Turbo or GPT-4o
      else if (/-preview|-turbo|o/.test(model.toLowerCase())) {
        options.maxModelTokens = 128000
        options.maxResponseTokens = 4096
      }
      else {
        options.maxModelTokens = 8192
        options.maxResponseTokens = 2048
      }
    }
    else if (model.toLowerCase().includes('gpt-3.5')) {
      if (/16k|1106|0125/.test(model.toLowerCase())) {
        options.maxModelTokens = 16384
        options.maxResponseTokens = 4096
      }
    }

    if (isNotEmptyString(OPENAI_API_BASE_URL)) {
      // if find /v1 in OPENAI_API_BASE_URL then use it
      if (OPENAI_API_BASE_URL.includes('/v1'))
        options.apiBaseUrl = `${OPENAI_API_BASE_URL}`
      else
        options.apiBaseUrl = `${OPENAI_API_BASE_URL}/v1`
    }

    setupProxy(options)

    api = new ChatGPTAPI({ ...options })
    apiModel = 'ChatGPTAPI'
  }
  else {
    const options: ChatGPTUnofficialProxyAPIOptions = {
      accessToken: process.env.OPENAI_ACCESS_TOKEN,
      apiReverseProxyUrl: isNotEmptyString(process.env.API_REVERSE_PROXY) ? process.env.API_REVERSE_PROXY : 'https://ai.fakeopen.com/api/conversation',
      model,
      debug: !disableDebug,
    }

    setupProxy(options)

    api = new ChatGPTUnofficialProxyAPI({ ...options })
    apiModel = 'ChatGPTUnofficialProxyAPI'
  }
})()

async function chatReplyProcess(options: RequestOptions) {
  const { message, lastContext, process, systemMessage, temperature, top_p } = options
  try {
    let options: SendMessageOptions = { timeoutMs }

    if (apiModel === 'ChatGPTAPI') {
      if (isNotEmptyString(systemMessage))
        options.systemMessage = systemMessage
      options.completionParams = { model, temperature, top_p }
    }

    if (lastContext != null) {
      if (apiModel === 'ChatGPTAPI')
        options.parentMessageId = lastContext.parentMessageId
      else
        options = { ...lastContext }
    }

    const response = await api.sendMessage(message, {
      ...options,
      onProgress: (partialResponse) => {
        process?.(partialResponse)
      },
    })

    return sendResponse({ type: 'Success', data: response })
  }
  catch (error: any) {
    const code = error.statusCode
    global.console.log(error)
    if (Reflect.has(ErrorCodeMessage, code))
      return sendResponse({ type: 'Fail', message: ErrorCodeMessage[code] })
    return sendResponse({ type: 'Fail', message: error.message ?? 'Please check the back-end console' })
  }
}

async function fetchUsage() {
  const OPENAI_API_KEY = process.env.OPENAI_API_KEY
  const OPENAI_API_BASE_URL = process.env.OPENAI_API_BASE_URL

  if (!isNotEmptyString(OPENAI_API_KEY))
    return Promise.resolve('-')

  const API_BASE_URL = isNotEmptyString(OPENAI_API_BASE_URL)
    ? OPENAI_API_BASE_URL
    : 'https://api.openai.com'

  const [startDate, endDate] = formatDate()

  // 每月使用量
  const urlUsage = `${API_BASE_URL}/v1/dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`

  const headers = {
    'Authorization': `Bearer ${OPENAI_API_KEY}`,
    'Content-Type': 'application/json',
  }

  const options = {} as SetProxyOptions

  setupProxy(options)

  try {
    // 获取已使用量
    const useResponse = await options.fetch(urlUsage, { headers })
    if (!useResponse.ok)
      throw new Error('获取使用量失败')
    const usageData = await useResponse.json() as UsageResponse
    const usage = Math.round(usageData.total_usage) / 100
    return Promise.resolve(usage ? `$${usage}` : '-')
  }
  catch (error) {
    global.console.log(error)
    return Promise.resolve('-')
  }
}

function formatDate(): string[] {
  const today = new Date()
  const year = today.getFullYear()
  const month = today.getMonth() + 1
  const lastDay = new Date(year, month, 0)
  const formattedFirstDay = `${year}-${month.toString().padStart(2, '0')}-01`
  const formattedLastDay = `${year}-${month.toString().padStart(2, '0')}-${lastDay.getDate().toString().padStart(2, '0')}`
  return [formattedFirstDay, formattedLastDay]
}

async function chatConfig() {
  const usage = await fetchUsage()
  const reverseProxy = process.env.API_REVERSE_PROXY ?? '-'
  const httpsProxy = (process.env.HTTPS_PROXY || process.env.ALL_PROXY) ?? '-'
  const socksProxy = (process.env.SOCKS_PROXY_HOST && process.env.SOCKS_PROXY_PORT)
    ? (`${process.env.SOCKS_PROXY_HOST}:${process.env.SOCKS_PROXY_PORT}`)
    : '-'
  return sendResponse<ModelConfig>({
    type: 'Success',
    data: { apiModel, reverseProxy, timeoutMs, socksProxy, httpsProxy, usage },
  })
}

function setupProxy(options: SetProxyOptions) {
  if (isNotEmptyString(process.env.SOCKS_PROXY_HOST) && isNotEmptyString(process.env.SOCKS_PROXY_PORT)) {
    const agent = new SocksProxyAgent({
      hostname: process.env.SOCKS_PROXY_HOST,
      port: process.env.SOCKS_PROXY_PORT,
      userId: isNotEmptyString(process.env.SOCKS_PROXY_USERNAME) ? process.env.SOCKS_PROXY_USERNAME : undefined,
      password: isNotEmptyString(process.env.SOCKS_PROXY_PASSWORD) ? process.env.SOCKS_PROXY_PASSWORD : undefined,
    })
    options.fetch = (url, options) => {
      return fetch(url, { agent, ...options })
    }
  }
  else if (isNotEmptyString(process.env.HTTPS_PROXY) || isNotEmptyString(process.env.ALL_PROXY)) {
    const httpsProxy = process.env.HTTPS_PROXY || process.env.ALL_PROXY
    if (httpsProxy) {
      const agent = new HttpsProxyAgent(httpsProxy)
      options.fetch = (url, options) => {
        return fetch(url, { agent, ...options })
      }
    }
  }
  else {
    options.fetch = (url, options) => {
      return fetch(url, { ...options })
    }
  }
}

function currentModel(): ApiModel {
  return apiModel
}

export type { ChatContext, ChatMessage }

export { chatReplyProcess, chatConfig, currentModel }


================================================
FILE: service/src/chatgpt/types.ts
================================================
import type { ChatMessage } from 'chatgpt'
import type fetch from 'node-fetch'

export interface RequestOptions {
  message: string
  lastContext?: { conversationId?: string; parentMessageId?: string }
  process?: (chat: ChatMessage) => void
  systemMessage?: string
  temperature?: number
  top_p?: number
}

export interface SetProxyOptions {
  fetch?: typeof fetch
}

export interface UsageResponse {
  total_usage: number
}


================================================
FILE: service/src/index.ts
================================================
import express from 'express'
import type { RequestProps } from './types'
import type { ChatMessage } from './chatgpt'
import { chatConfig, chatReplyProcess, currentModel } from './chatgpt'
import { auth } from './middleware/auth'
import { limiter } from './middleware/limiter'
import { isNotEmptyString } from './utils/is'

const app = express()
const router = express.Router()

app.use(express.static('public'))
app.use(express.json())

app.all('*', (_, res, next) => {
  res.header('Access-Control-Allow-Origin', '*')
  res.header('Access-Control-Allow-Headers', 'authorization, Content-Type')
  res.header('Access-Control-Allow-Methods', '*')
  next()
})

router.post('/chat-process', [auth, limiter], async (req, res) => {
  res.setHeader('Content-type', 'application/octet-stream')

  try {
    const { prompt, options = {}, systemMessage, temperature, top_p } = req.body as RequestProps
    let firstChunk = true
    await chatReplyProcess({
      message: prompt,
      lastContext: options,
      process: (chat: ChatMessage) => {
        res.write(firstChunk ? JSON.stringify(chat) : `\n${JSON.stringify(chat)}`)
        firstChunk = false
      },
      systemMessage,
      temperature,
      top_p,
    })
  }
  catch (error) {
    res.write(JSON.stringify(error))
  }
  finally {
    res.end()
  }
})

router.post('/config', auth, async (req, res) => {
  try {
    const response = await chatConfig()
    res.send(response)
  }
  catch (error) {
    res.send(error)
  }
})

router.post('/session', async (req, res) => {
  try {
    const AUTH_SECRET_KEY = process.env.AUTH_SECRET_KEY
    const hasAuth = isNotEmptyString(AUTH_SECRET_KEY)
    res.send({ status: 'Success', message: '', data: { auth: hasAuth, model: currentModel() } })
  }
  catch (error) {
    res.send({ status: 'Fail', message: error.message, data: null })
  }
})

router.post('/verify', async (req, res) => {
  try {
    const { token } = req.body as { token: string }
    if (!token)
      throw new Error('Secret key is empty')

    if (process.env.AUTH_SECRET_KEY !== token)
      throw new Error('密钥无效 | Secret key is invalid')

    res.send({ status: 'Success', message: 'Verify successfully', data: null })
  }
  catch (error) {
    res.send({ status: 'Fail', message: error.message, data: null })
  }
})

app.use('', router)
app.use('/api', router)
app.set('trust proxy', 1)

app.listen(3002, () => globalThis.console.log('Server is running on port 3002'))


================================================
FILE: service/src/middleware/auth.ts
================================================
import { isNotEmptyString } from '../utils/is'

const auth = async (req, res, next) => {
  const AUTH_SECRET_KEY = process.env.AUTH_SECRET_KEY
  if (isNotEmptyString(AUTH_SECRET_KEY)) {
    try {
      const Authorization = req.header('Authorization')
      if (!Authorization || Authorization.replace('Bearer ', '').trim() !== AUTH_SECRET_KEY.trim())
        throw new Error('Error: 无访问权限 | No access rights')
      next()
    }
    catch (error) {
      res.send({ status: 'Unauthorized', message: error.message ?? 'Please authenticate.', data: null })
    }
  }
  else {
    next()
  }
}

export { auth }


================================================
FILE: service/src/middleware/limiter.ts
================================================
import { rateLimit } from 'express-rate-limit'
import { isNotEmptyString } from '../utils/is'

const MAX_REQUEST_PER_HOUR = process.env.MAX_REQUEST_PER_HOUR

const maxCount = (isNotEmptyString(MAX_REQUEST_PER_HOUR) && !isNaN(Number(MAX_REQUEST_PER_HOUR)))
  ? parseInt(MAX_REQUEST_PER_HOUR)
  : 0 // 0 means unlimited

const limiter = rateLimit({
  windowMs: 60 * 60 * 1000, // Maximum number of accesses within an hour
  max: maxCount,
  statusCode: 200, // 200 means success,but the message is 'Too many request from this IP in 1 hour'
  message: async (req, res) => {
    res.send({ status: 'Fail', message: 'Too many request from this IP in 1 hour', data: null })
  },
})

export { limiter }


================================================
FILE: service/src/types.ts
================================================
import type { FetchFn } from 'chatgpt'

export interface RequestProps {
  prompt: string
  options?: ChatContext
  systemMessage: string
  temperature?: number
  top_p?: number
}

export interface ChatContext {
  conversationId?: string
  parentMessageId?: string
}

export interface ChatGPTUnofficialProxyAPIOptions {
  accessToken: string
  apiReverseProxyUrl?: string
  model?: string
  debug?: boolean
  headers?: Record<string, string>
  fetch?: FetchFn
}

export interface ModelConfig {
  apiModel?: ApiModel
  reverseProxy?: string
  timeoutMs?: number
  socksProxy?: string
  httpsProxy?: string
  usage?: string
}

export type ApiModel = 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' | undefined


================================================
FILE: service/src/utils/index.ts
================================================
interface SendResponseOptions<T = any> {
  type: 'Success' | 'Fail'
  message?: string
  data?: T
}

export function sendResponse<T>(options: SendResponseOptions<T>) {
  if (options.type === 'Success') {
    return Promise.resolve({
      message: options.message ?? null,
      data: options.data ?? null,
      status: options.type,
    })
  }

  // eslint-disable-next-line prefer-promise-reject-errors
  return Promise.reject({
    message: options.message ?? 'Failed',
    data: options.data ?? null,
    status: options.type,
  })
}


================================================
FILE: service/src/utils/is.ts
================================================
export function isNumber<T extends number>(value: T | unknown): value is number {
  return Object.prototype.toString.call(value) === '[object Number]'
}

export function isString<T extends string>(value: T | unknown): value is string {
  return Object.prototype.toString.call(value) === '[object String]'
}

export function isNotEmptyString(value: any): boolean {
  return typeof value === 'string' && value.length > 0
}

export function isBoolean<T extends boolean>(value: T | unknown): value is boolean {
  return Object.prototype.toString.call(value) === '[object Boolean]'
}

export function isFunction<T extends (...args: any[]) => any | void | never>(value: T | unknown): value is T {
  return Object.prototype.toString.call(value) === '[object Function]'
}


================================================
FILE: service/tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "es2020",
    "lib": [
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": false,
    "forceConsistentCasingInFileNames": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "baseUrl": ".",
    "outDir": "build",
    "noEmit": true
  },
  "exclude": [
    "node_modules",
    "build"
  ],
  "include": [
    "**/*.ts"
  ]
}


================================================
FILE: service/tsup.config.ts
================================================
import { defineConfig } from 'tsup'

export default defineConfig({
  entry: ['src/index.ts'],
  outDir: 'build',
  target: 'es2020',
  format: ['esm'],
  splitting: false,
  sourcemap: true,
  minify: false,
  shims: true,
  dts: false,
})


================================================
FILE: src/App.vue
================================================
<script setup lang="ts">
import { NConfigProvider } from 'naive-ui'
import { NaiveProvider } from '@/components/common'
import { useTheme } from '@/hooks/useTheme'
import { useLanguage } from '@/hooks/useLanguage'

const { theme, themeOverrides } = useTheme()
const { language } = useLanguage()
</script>

<template>
  <NConfigProvider
    class="h-full"
    :theme="theme"
    :theme-overrides="themeOverrides"
    :locale="language"
  >
    <NaiveProvider>
      <RouterView />
    </NaiveProvider>
  </NConfigProvider>
</template>


================================================
FILE: src/api/index.ts
================================================
import type { AxiosProgressEvent, GenericAbortSignal } from 'axios'
import { post } from '@/utils/request'
import { useAuthStore, useSettingStore } from '@/store'

export function fetchChatAPI<T = any>(
  prompt: string,
  options?: { conversationId?: string; parentMessageId?: string },
  signal?: GenericAbortSignal,
) {
  return post<T>({
    url: '/chat',
    data: { prompt, options },
    signal,
  })
}

export function fetchChatConfig<T = any>() {
  return post<T>({
    url: '/config',
  })
}

export function fetchChatAPIProcess<T = any>(
  params: {
    prompt: string
    options?: { conversationId?: string; parentMessageId?: string }
    signal?: GenericAbortSignal
    onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void },
) {
  const settingStore = useSettingStore()
  const authStore = useAuthStore()

  let data: Record<string, any> = {
    prompt: params.prompt,
    options: params.options,
  }

  if (authStore.isChatGPTAPI) {
    data = {
      ...data,
      systemMessage: settingStore.systemMessage,
      temperature: settingStore.temperature,
      top_p: settingStore.top_p,
    }
  }

  return post<T>({
    url: '/chat-process',
    data,
    signal: params.signal,
    onDownloadProgress: params.onDownloadProgress,
  })
}

export function fetchSession<T>() {
  return post<T>({
    url: '/session',
  })
}

export function fetchVerify<T>(token: string) {
  return post<T>({
    url: '/verify',
    data: { token },
  })
}


================================================
FILE: src/assets/recommend.json
================================================
[
  {
    "key": "awesome-chatgpt-prompts-zh",
    "desc": "ChatGPT 中文调教指南",
    "downloadUrl": "https://raw.githubusercontent.com/PlexPt/awesome-chatgpt-prompts-zh/main/prompts-zh.json",
    "url": "https://github.com/PlexPt/awesome-chatgpt-prompts-zh"
  },
  {
    "key": "awesome-chatgpt-prompts-zh-TW",
    "desc": "ChatGPT 中文調教指南 (透過 OpenAI / OpenCC 協助,從簡體中文轉換為繁體中文的版本)",
    "downloadUrl": "https://raw.githubusercontent.com/PlexPt/awesome-chatgpt-prompts-zh/main/prompts-zh-TW.json",
    "url": "https://github.com/PlexPt/awesome-chatgpt-prompts-zh"
  }
]


================================================
FILE: src/components/common/HoverButton/Button.vue
================================================
<script setup lang='ts'>
interface Emit {
  (e: 'click'): void
}

const emit = defineEmits<Emit>()

function handleClick() {
  emit('click')
}
</script>

<template>
  <button
    class="flex items-center justify-center w-10 h-10 transition rounded-full hover:bg-neutral-100 dark:hover:bg-[#414755]"
    @click="handleClick"
  >
    <slot />
  </button>
</template>


================================================
FILE: src/components/common/HoverButton/index.vue
================================================
<script setup lang='ts'>
import { computed } from 'vue'
import type { PopoverPlacement } from 'naive-ui'
import { NTooltip } from 'naive-ui'
import Button from './Button.vue'

interface Props {
  tooltip?: string
  placement?: PopoverPlacement
}

interface Emit {
  (e: 'click'): void
}

const props = withDefaults(defineProps<Props>(), {
  tooltip: '',
  placement: 'bottom',
})

const emit = defineEmits<Emit>()

const showTooltip = computed(() => Boolean(props.tooltip))

function handleClick() {
  emit('click')
}
</script>

<template>
  <div v-if="showTooltip">
    <NTooltip :placement="placement" trigger="hover">
      <template #trigger>
        <Button @click="handleClick">
          <slot />
        </Button>
      </template>
      {{ tooltip }}
    </NTooltip>
  </div>
  <div v-else>
    <Button @click="handleClick">
      <slot />
    </Button>
  </div>
</template>


================================================
FILE: src/components/common/NaiveProvider/index.vue
================================================
<script setup lang="ts">
import { defineComponent, h } from 'vue'
import {
  NDialogProvider,
  NLoadingBarProvider,
  NMessageProvider,
  NNotificationProvider,
  useDialog,
  useLoadingBar,
  useMessage,
  useNotification,
} from 'naive-ui'

function registerNaiveTools() {
  window.$loadingBar = useLoadingBar()
  window.$dialog = useDialog()
  window.$message = useMessage()
  window.$notification = useNotification()
}

const NaiveProviderContent = defineComponent({
  name: 'NaiveProviderContent',
  setup() {
    registerNaiveTools()
  },
  render() {
    return h('div')
  },
})
</script>

<template>
  <NLoadingBarProvider>
    <NDialogProvider>
      <NNotificationProvider>
        <NMessageProvider>
          <slot />
          <NaiveProviderContent />
        </NMessageProvider>
      </NNotificationProvider>
    </NDialogProvider>
  </NLoadingBarProvider>
</template>


================================================
FILE: src/components/common/PromptStore/index.vue
================================================
<script setup lang='ts'>
import type { DataTableColumns } from 'naive-ui'
import { computed, h, ref, watch } from 'vue'
import { NButton, NCard, NDataTable, NDivider, NInput, NList, NListItem, NModal, NPopconfirm, NSpace, NTabPane, NTabs, NThing, useMessage } from 'naive-ui'
import PromptRecommend from '../../../assets/recommend.json'
import { SvgIcon } from '..'
import { usePromptStore } from '@/store'
import { useBasicLayout } from '@/hooks/useBasicLayout'
import { t } from '@/locales'

interface DataProps {
  renderKey: string
  renderValue: string
  key: string
  value: string
}

interface Props {
  visible: boolean
}

interface Emit {
  (e: 'update:visible', visible: boolean): void
}

const props = defineProps<Props>()

const emit = defineEmits<Emit>()

const message = useMessage()

const show = computed({
  get: () => props.visible,
  set: (visible: boolean) => emit('update:visible', visible),
})

const showModal = ref(false)

const importLoading = ref(false)
const exportLoading = ref(false)

const searchValue = ref<string>('')

// 移动端自适应相关
const { isMobile } = useBasicLayout()

const promptStore = usePromptStore()

// Prompt在线导入推荐List,根据部署者喜好进行修改(assets/recommend.json)
const promptRecommendList = PromptRecommend
const promptList = ref<any>(promptStore.promptList)

// 用于添加修改的临时prompt参数
const tempPromptKey = ref('')
const tempPromptValue = ref('')

// Modal模式,根据不同模式渲染不同的Modal内容
const modalMode = ref('')

// 这个是为了后期的修改Prompt内容考虑,因为要针对无uuid的list进行修改,且考虑到不能出现标题和内容的冲突,所以就需要一个临时item来记录一下
const tempModifiedItem = ref<any>({})

// 添加修改导入都使用一个Modal, 临时修改内容占用tempPromptKey,切换状态前先将内容都清楚
const changeShowModal = (mode: 'add' | 'modify' | 'local_import', selected = { key: '', value: '' }) => {
  if (mode === 'add') {
    tempPromptKey.value = ''
    tempPromptValue.value = ''
  }
  else if (mode === 'modify') {
    tempModifiedItem.value = { ...selected }
    tempPromptKey.value = selected.key
    tempPromptValue.value = selected.value
  }
  else if (mode === 'local_import') {
    tempPromptKey.value = 'local_import'
    tempPromptValue.value = ''
  }
  showModal.value = !showModal.value
  modalMode.value = mode
}

// 在线导入相关
const downloadURL = ref('')
const downloadDisabled = computed(() => downloadURL.value.trim().length < 1)
const setDownloadURL = (url: string) => {
  downloadURL.value = url
}

// 控制 input 按钮
const inputStatus = computed (() => tempPromptKey.value.trim().length < 1 || tempPromptValue.value.trim().length < 1)

// Prompt模板相关操作
const addPromptTemplate = () => {
  for (const i of promptList.value) {
    if (i.key === tempPromptKey.value) {
      message.error(t('store.addRepeatTitleTips'))
      return
    }
    if (i.value === tempPromptValue.value) {
      message.error(t('store.addRepeatContentTips', { msg: tempPromptKey.value }))
      return
    }
  }
  promptList.value.unshift({ key: tempPromptKey.value, value: tempPromptValue.value } as never)
  message.success(t('common.addSuccess'))
  changeShowModal('add')
}

const modifyPromptTemplate = () => {
  let index = 0

  // 通过临时索引把待修改项摘出来
  for (const i of promptList.value) {
    if (i.key === tempModifiedItem.value.key && i.value === tempModifiedItem.value.value)
      break
    index = index + 1
  }

  const tempList = promptList.value.filter((_: any, i: number) => i !== index)

  // 搜索有冲突的部分
  for (const i of tempList) {
    if (i.key === tempPromptKey.value) {
      message.error(t('store.editRepeatTitleTips'))
      return
    }
    if (i.value === tempPromptValue.value) {
      message.error(t('store.editRepeatContentTips', { msg: i.key }))
      return
    }
  }

  promptList.value = [{ key: tempPromptKey.value, value: tempPromptValue.value }, ...tempList] as never
  message.success(t('common.editSuccess'))
  changeShowModal('modify')
}

const deletePromptTemplate = (row: { key: string; value: string }) => {
  promptList.value = [
    ...promptList.value.filter((item: { key: string; value: string }) => item.key !== row.key),
  ] as never
  message.success(t('common.deleteSuccess'))
}

const clearPromptTemplate = () => {
  promptList.value = []
  message.success(t('common.clearSuccess'))
}

const importPromptTemplate = (from = 'online') => {
  try {
    const jsonData = JSON.parse(tempPromptValue.value)
    let key = ''
    let value = ''
    // 可以扩展加入更多模板字典的key
    if ('key' in jsonData[0]) {
      key = 'key'
      value = 'value'
    }
    else if ('act' in jsonData[0]) {
      key = 'act'
      value = 'prompt'
    }
    else {
      // 不支持的字典的key防止导入 以免破坏prompt商店打开
      message.warning('prompt key not supported.')
      throw new Error('prompt key not supported.')
    }

    for (const i of jsonData) {
      if (!(key in i) || !(value in i))
        throw new Error(t('store.importError'))
      let safe = true
      for (const j of promptList.value) {
        if (j.key === i[key]) {
          message.warning(t('store.importRepeatTitle', { msg: i[key] }))
          safe = false
          break
        }
        if (j.value === i[value]) {
          message.warning(t('store.importRepeatContent', { msg: i[key] }))
          safe = false
          break
        }
      }
      if (safe)
        promptList.value.unshift({ key: i[key], value: i[value] } as never)
    }
    message.success(t('common.importSuccess'))
  }
  catch {
    message.error('JSON 格式错误,请检查 JSON 格式')
  }
  if (from === 'local')
    showModal.value = !showModal.value
}

// 模板导出
const exportPromptTemplate = () => {
  exportLoading.value = true
  const jsonDataStr = JSON.stringify(promptList.value)
  const blob = new Blob([jsonDataStr], { type: 'application/json' })
  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = 'ChatGPTPromptTemplate.json'
  link.click()
  URL.revokeObjectURL(url)
  exportLoading.value = false
}

// 模板在线导入
const downloadPromptTemplate = async () => {
  try {
    importLoading.value = true
    const response = await fetch(downloadURL.value)
    const jsonData = await response.json()
    if ('key' in jsonData[0] && 'value' in jsonData[0])
      tempPromptValue.value = JSON.stringify(jsonData)
    if ('act' in jsonData[0] && 'prompt' in jsonData[0]) {
      const newJsonData = jsonData.map((item: { act: string; prompt: string }) => {
        return {
          key: item.act,
          value: item.prompt,
        }
      })
      tempPromptValue.value = JSON.stringify(newJsonData)
    }
    importPromptTemplate()
    downloadURL.value = ''
  }
  catch {
    message.error(t('store.downloadError'))
    downloadURL.value = ''
  }
  finally {
    importLoading.value = false
  }
}

// 移动端自适应相关
const renderTemplate = () => {
  const [keyLimit, valueLimit] = isMobile.value ? [10, 30] : [15, 50]

  return promptList.value.map((item: { key: string; value: string }) => {
    return {
      renderKey: item.key.length <= keyLimit ? item.key : `${item.key.substring(0, keyLimit)}...`,
      renderValue: item.value.length <= valueLimit ? item.value : `${item.value.substring(0, valueLimit)}...`,
      key: item.key,
      value: item.value,
    }
  })
}

const pagination = computed(() => {
  const [pageSize, pageSlot] = isMobile.value ? [6, 5] : [7, 15]
  return {
    pageSize, pageSlot,
  }
})

// table相关
const createColumns = (): DataTableColumns<DataProps> => {
  return [
    {
      title: t('store.title'),
      key: 'renderKey',
    },
    {
      title: t('store.description'),
      key: 'renderValue',
    },
    {
      title: t('common.action'),
      key: 'actions',
      width: 100,
      align: 'center',
      render(row) {
        return h('div', { class: 'flex items-center flex-col gap-2' }, {
          default: () => [h(
            NButton,
            {
              tertiary: true,
              size: 'small',
              type: 'info',
              onClick: () => changeShowModal('modify', row),
            },
            { default: () => t('common.edit') },
          ),
          h(
            NButton,
            {
              tertiary: true,
              size: 'small',
              type: 'error',
              onClick: () => deletePromptTemplate(row),
            },
            { default: () => t('common.delete') },
          ),
          ],
        })
      },
    },
  ]
}

const columns = createColumns()

watch(
  () => promptList,
  () => {
    promptStore.updatePromptList(promptList.value)
  },
  { deep: true },
)

const dataSource = computed(() => {
  const data = renderTemplate()
  const value = searchValue.value
  if (value && value !== '') {
    return data.filter((item: DataProps) => {
      return item.renderKey.includes(value) || item.renderValue.includes(value)
    })
  }
  return data
})
</script>

<template>
  <NModal v-model:show="show" style="width: 90%; max-width: 900px;" preset="card">
    <div class="space-y-4">
      <NTabs type="segment">
        <NTabPane name="local" :tab="$t('store.local')">
          <div
            class="flex gap-3 mb-4"
            :class="[isMobile ? 'flex-col' : 'flex-row justify-between']"
          >
            <div class="flex items-center space-x-4">
              <NButton
                type="primary"
                size="small"
                @click="changeShowModal('add')"
              >
                {{ $t('common.add') }}
              </NButton>
              <NButton
                size="small"
                @click="changeShowModal('local_import')"
              >
                {{ $t('common.import') }}
              </NButton>
              <NButton
                size="small"
                :loading="exportLoading"
                @click="exportPromptTemplate()"
              >
                {{ $t('common.export') }}
              </NButton>
              <NPopconfirm @positive-click="clearPromptTemplate">
                <template #trigger>
                  <NButton size="small">
                    {{ $t('common.clear') }}
                  </NButton>
                </template>
                {{ $t('store.clearStoreConfirm') }}
              </NPopconfirm>
            </div>
            <div class="flex items-center">
              <NInput v-model:value="searchValue" style="width: 100%" />
            </div>
          </div>
          <NDataTable
            v-if="!isMobile"
            :max-height="400"
            :columns="columns"
            :data="dataSource"
            :pagination="pagination"
            :bordered="false"
          />
          <NList v-if="isMobile" style="max-height: 400px; overflow-y: auto;">
            <NListItem v-for="(item, index) of dataSource" :key="index">
              <NThing :title="item.renderKey" :description="item.renderValue" />
              <template #suffix>
                <div class="flex flex-col items-center gap-2">
                  <NButton tertiary size="small" type="info" @click="changeShowModal('modify', item)">
                    {{ t('common.edit') }}
                  </NButton>
                  <NButton tertiary size="small" type="error" @click="deletePromptTemplate(item)">
                    {{ t('common.delete') }}
                  </NButton>
                </div>
              </template>
            </NListItem>
          </NList>
        </NTabPane>
        <NTabPane name="download" :tab="$t('store.online')">
          <p class="mb-4">
            {{ $t('store.onlineImportWarning') }}
          </p>
          <div class="flex items-center gap-4">
            <NInput v-model:value="downloadURL" placeholder="" />
            <NButton
              strong
              secondary
              :disabled="downloadDisabled"
              :loading="importLoading"
              @click="downloadPromptTemplate()"
            >
              {{ $t('common.download') }}
            </NButton>
          </div>
          <NDivider />
          <div class="max-h-[360px] overflow-y-auto space-y-4">
            <NCard
              v-for="info in promptRecommendList"
              :key="info.key" :title="info.key"
              :bordered="true"
              embedded
            >
              <p
                class="overflow-hidden text-ellipsis whitespace-nowrap"
                :title="info.desc"
              >
                {{ info.desc }}
              </p>
              <template #footer>
                <div class="flex items-center justify-end space-x-4">
                  <NButton text>
                    <a
                      :href="info.url"
                      target="_blank"
                    >
                      <SvgIcon class="text-xl" icon="ri:link" />
                    </a>
                  </NButton>
                  <NButton text @click="setDownloadURL(info.downloadUrl) ">
                    <SvgIcon class="text-xl" icon="ri:add-fill" />
                  </NButton>
                </div>
              </template>
            </NCard>
          </div>
        </NTabPane>
      </NTabs>
    </div>
  </NModal>

  <NModal v-model:show="showModal" style="width: 90%; max-width: 600px;" preset="card">
    <NSpace v-if="modalMode === 'add' || modalMode === 'modify'" vertical>
      {{ t('store.title') }}
      <NInput v-model:value="tempPromptKey" />
      {{ t('store.description') }}
      <NInput v-model:value="tempPromptValue" type="textarea" />
      <NButton
        block
        type="primary"
        :disabled="inputStatus"
        @click="() => { modalMode === 'add' ? addPromptTemplate() : modifyPromptTemplate() }"
      >
        {{ t('common.confirm') }}
      </NButton>
    </NSpace>
    <NSpace v-if="modalMode === 'local_import'" vertical>
      <NInput
        v-model:value="tempPromptValue"
        :placeholder="t('store.importPlaceholder')"
        :autosize="{ minRows: 3, maxRows: 15 }"
        type="textarea"
      />
      <NButton
        block
        type="primary"
        :disabled="inputStatus"
        @click="() => { importPromptTemplate('local') }"
      >
        {{ t('common.import') }}
      </NButton>
    </NSpace>
  </NModal>
</template>


================================================
FILE: src/components/common/Setting/About.vue
================================================
<script setup lang='ts'>
import { computed, onMounted, ref } from 'vue'
import { NSpin } from 'naive-ui'
import pkg from '../../../../package.json'
import { fetchChatConfig } from '@/api'
import { useAuthStore } from '@/store'

interface ConfigState {
  timeoutMs?: number
  reverseProxy?: string
  apiModel?: string
  socksProxy?: string
  httpsProxy?: string
  usage?: string
}

const authStore = useAuthStore()

const loading = ref(false)

const config = ref<ConfigState>()

const isChatGPTAPI = computed<boolean>(() => !!authStore.isChatGPTAPI)

async function fetchConfig() {
  try {
    loading.value = true
    const { data } = await fetchChatConfig<ConfigState>()
    config.value = data
  }
  finally {
    loading.value = false
  }
}

onMounted(() => {
  fetchConfig()
})
</script>

<template>
  <NSpin :show="loading">
    <div class="p-4 space-y-4">
      <h2 class="text-xl font-bold">
        Version - {{ pkg.version }}
      </h2>
      <div class="p-2 space-y-2 rounded-md bg-neutral-100 dark:bg-neutral-700">
        <p>
          {{ $t("setting.openSource") }}
          <a
            class="text-blue-600 dark:text-blue-500"
            href="https://github.com/Chanzhaoyu/chatgpt-web"
            target="_blank"
          >
            GitHub
          </a>
          {{ $t("setting.freeMIT") }}
        </p>
        <p>
          {{ $t("setting.stars") }}
        </p>
      </div>
      <p>{{ $t("setting.api") }}:{{ config?.apiModel ?? '-' }}</p>
      <p v-if="isChatGPTAPI">
        {{ $t("setting.monthlyUsage") }}:{{ config?.usage ?? '-' }}
      </p>
      <p v-if="!isChatGPTAPI">
        {{ $t("setting.reverseProxy") }}:{{ config?.reverseProxy ?? '-' }}
      </p>
      <p>{{ $t("setting.timeout") }}:{{ config?.timeoutMs ?? '-' }}</p>
      <p>{{ $t("setting.socks") }}:{{ config?.socksProxy ?? '-' }}</p>
      <p>{{ $t("setting.httpsProxy") }}:{{ config?.httpsProxy ?? '-' }}</p>
    </div>
  </NSpin>
</template>


================================================
FILE: src/components/common/Setting/Advanced.vue
================================================
<script lang="ts" setup>
import { ref } from 'vue'
import { NButton, NInput, NSlider, useMessage } from 'naive-ui'
import { useSettingStore } from '@/store'
import type { SettingsState } from '@/store/modules/settings/helper'
import { t } from '@/locales'

const settingStore = useSettingStore()

const ms = useMessage()

const systemMessage = ref(settingStore.systemMessage ?? '')

const temperature = ref(settingStore.temperature ?? 0.5)

const top_p = ref(settingStore.top_p ?? 1)

function updateSettings(options: Partial<SettingsState>) {
  settingStore.updateSetting(options)
  ms.success(t('common.success'))
}

function handleReset() {
  settingStore.resetSetting()
  ms.success(t('common.success'))
  window.location.reload()
}
</script>

<template>
  <div class="p-4 space-y-5 min-h-[200px]">
    <div class="space-y-6">
      <div class="flex items-center space-x-4">
        <span class="flex-shrink-0 w-[120px]">{{ $t('setting.role') }}</span>
        <div class="flex-1">
          <NInput v-model:value="systemMessage" type="textarea" :autosize="{ minRows: 1, maxRows: 4 }" />
        </div>
        <NButton size="tiny" text type="primary" @click="updateSettings({ systemMessage })">
          {{ $t('common.save') }}
        </NButton>
      </div>
      <div class="flex items-center space-x-4">
        <span class="flex-shrink-0 w-[120px]">{{ $t('setting.temperature') }} </span>
        <div class="flex-1">
          <NSlider v-model:value="temperature" :max="2" :min="0" :step="0.1" />
        </div>
        <span>{{ temperature }}</span>
        <NButton size="tiny" text type="primary" @click="updateSettings({ temperature })">
          {{ $t('common.save') }}
        </NButton>
      </div>
      <div class="flex items-center space-x-4">
        <span class="flex-shrink-0 w-[120px]">{{ $t('setting.top_p') }} </span>
        <div class="flex-1">
          <NSlider v-model:value="top_p" :max="1" :min="0" :step="0.1" />
        </div>
        <span>{{ top_p }}</span>
        <NButton size="tiny" text type="primary" @click="updateSettings({ top_p })">
          {{ $t('common.save') }}
        </NButton>
      </div>
      <div class="flex items-center space-x-4">
        <span class="flex-shrink-0 w-[120px]">&nbsp;</span>
        <NButton size="small" @click="handleReset">
          {{ $t('common.reset') }}
        </NButton>
      </div>
    </div>
  </div>
</template>


================================================
FILE: src/components/common/Setting/General.vue
================================================
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { NButton, NInput, NPopconfirm, NSelect, useMessage } from 'naive-ui'
import type { Language, Theme } from '@/store/modules/app/helper'
import { SvgIcon } from '@/components/common'
import { useAppStore, useUserStore } from '@/store'
import type { UserInfo } from '@/store/modules/user/helper'
import { getCurrentDate } from '@/utils/functions'
import { useBasicLayout } from '@/hooks/useBasicLayout'
import { t } from '@/locales'

const appStore = useAppStore()
const userStore = useUserStore()

const { isMobile } = useBasicLayout()

const ms = useMessage()

const theme = computed(() => appStore.theme)

const userInfo = computed(() => userStore.userInfo)

const avatar = ref(userInfo.value.avatar ?? '')

const name = ref(userInfo.value.name ?? '')

const description = ref(userInfo.value.description ?? '')

const language = computed({
  get() {
    return appStore.language
  },
  set(value: Language) {
    appStore.setLanguage(value)
  },
})

const themeOptions: { label: string; key: Theme; icon: string }[] = [
  {
    label: 'Auto',
    key: 'auto',
    icon: 'ri:contrast-line',
  },
  {
    label: 'Light',
    key: 'light',
    icon: 'ri:sun-foggy-line',
  },
  {
    label: 'Dark',
    key: 'dark',
    icon: 'ri:moon-foggy-line',
  },
]

const languageOptions: { label: string; key: Language; value: Language }[] = [
  { label: 'English', key: 'en-US', value: 'en-US' },
  { label: 'Español', key: 'es-ES', value: 'es-ES' },
  { label: '한국어', key: 'ko-KR', value: 'ko-KR' },
  { label: 'Русский язык', key: 'ru-RU', value: 'ru-RU' },
  { label: 'Tiếng Việt', key: 'vi-VN', value: 'vi-VN' },
  { label: '简体中文', key: 'zh-CN', value: 'zh-CN' },
  { label: '繁體中文', key: 'zh-TW', value: 'zh-TW' },
]

function updateUserInfo(options: Partial<UserInfo>) {
  userStore.updateUserInfo(options)
  ms.success(t('common.success'))
}

function handleReset() {
  userStore.resetUserInfo()
  ms.success(t('common.success'))
  window.location.reload()
}

function exportData(): void {
  const date = getCurrentDate()
  const data: string = localStorage.getItem('chatStorage') || '{}'
  const jsonString: string = JSON.stringify(JSON.parse(data), null, 2)
  const blob: Blob = new Blob([jsonString], { type: 'application/json' })
  const url: string = URL.createObjectURL(blob)
  const link: HTMLAnchorElement = document.createElement('a')
  link.href = url
  link.download = `chat-store_${date}.json`
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
}

function importData(event: Event): void {
  const target = event.target as HTMLInputElement
  if (!target || !target.files)
    return

  const file: File = target.files[0]
  if (!file)
    return

  const reader: FileReader = new FileReader()
  reader.onload = () => {
    try {
      const data = JSON.parse(reader.result as string)
      localStorage.setItem('chatStorage', JSON.stringify(data))
      ms.success(t('common.success'))
      location.reload()
    }
    catch (error) {
      ms.error(t('common.invalidFileFormat'))
    }
  }
  reader.readAsText(file)
}

function clearData(): void {
  localStorage.removeItem('chatStorage')
  location.reload()
}

function handleImportButtonClick(): void {
  const fileInput = document.getElementById('fileInput') as HTMLElement
  if (fileInput)
    fileInput.click()
}
</script>

<template>
  <div class="p-4 space-y-5 min-h-[200px]">
    <div class="space-y-6">
      <div class="flex items-center space-x-4">
        <span class="flex-shrink-0 w-[100px]">{{ $t('setting.avatarLink') }}</span>
        <div class="flex-1">
          <NInput v-model:value="avatar" placeholder="" />
        </div>
        <NButton size="tiny" text type="primary" @click="updateUserInfo({ avatar })">
          {{ $t('common.save') }}
        </NButton>
      </div>
      <div class="flex items-center space-x-4">
        <span class="flex-shrink-0 w-[100px]">{{ $t('setting.name') }}</span>
        <div class="w-[200px]">
          <NInput v-model:value="name" placeholder="" />
        </div>
        <NButton size="tiny" text type="primary" @click="updateUserInfo({ name })">
          {{ $t('common.save') }}
        </NButton>
      </div>
      <div class="flex items-center space-x-4">
        <span class="flex-shrink-0 w-[100px]">{{ $t('setting.description') }}</span>
        <div class="flex-1">
          <NInput v-model:value="description" placeholder="" />
        </div>
        <NButton size="tiny" text type="primary" @click="updateUserInfo({ description })">
          {{ $t('common.save') }}
        </NButton>
      </div>
      <div
        class="flex items-center space-x-4"
        :class="isMobile && 'items-start'"
      >
        <span class="flex-shrink-0 w-[100px]">{{ $t('setting.chatHistory') }}</span>

        <div class="flex flex-wrap items-center gap-4">
          <NButton size="small" @click="exportData">
            <template #icon>
              <SvgIcon icon="ri:download-2-fill" />
            </template>
            {{ $t('common.export') }}
          </NButton>

          <input id="fileInput" type="file" style="display:none" @change="importData">
          <NButton size="small" @click="handleImportButtonClick">
            <template #icon>
              <SvgIcon icon="ri:upload-2-fill" />
            </template>
            {{ $t('common.import') }}
          </NButton>

          <NPopconfirm placement="bottom" @positive-click="clearData">
            <template #trigger>
              <NButton size="small">
                <template #icon>
                  <SvgIcon icon="ri:close-circle-line" />
                </template>
                {{ $t('common.clear') }}
              </NButton>
            </template>
            {{ $t('chat.clearHistoryConfirm') }}
          </NPopconfirm>
        </div>
      </div>
      <div class="flex items-center space-x-4">
        <span class="flex-shrink-0 w-[100px]">{{ $t('setting.theme') }}</span>
        <div class="flex flex-wrap items-center gap-4">
          <template v-for="item of themeOptions" :key="item.key">
            <NButton
              size="small"
              :type="item.key === theme ? 'primary' : undefined"
              @click="appStore.setTheme(item.key)"
            >
              <template #icon>
                <SvgIcon :icon="item.icon" />
              </template>
            </NButton>
          </template>
        </div>
      </div>
      <div class="flex items-center space-x-4">
        <span class="flex-shrink-0 w-[100px]">{{ $t('setting.language') }}</span>
        <div class="flex flex-wrap items-center gap-4">
          <NSelect
            style="width: 140px"
            :value="language"
            :options="languageOptions"
            @update-value="value => appStore.setLanguage(value)"
          />
        </div>
      </div>
      <div class="flex items-center space-x-4">
        <span class="flex-shrink-0 w-[100px]">{{ $t('setting.resetUserInfo') }}</span>
        <NButton size="small" @click="handleReset">
          {{ $t('common.reset') }}
        </NButton>
      </div>
    </div>
  </div>
</template>


================================================
FILE: src/components/common/Setting/index.vue
================================================
<script setup lang='ts'>
import { computed, ref } from 'vue'
import { NModal, NTabPane, NTabs } from 'naive-ui'
import General from './General.vue'
import Advanced from './Advanced.vue'
import About from './About.vue'
import { useAuthStore } from '@/store'
import { SvgIcon } from '@/components/common'

interface Props {
  visible: boolean
}

interface Emit {
  (e: 'update:visible', visible: boolean): void
}

const props = defineProps<Props>()

const emit = defineEmits<Emit>()

const authStore = useAuthStore()

const isChatGPTAPI = computed<boolean>(() => !!authStore.isChatGPTAPI)

const active = ref('General')

const show = computed({
  get() {
    return props.visible
  },
  set(visible: boolean) {
    emit('update:visible', visible)
  },
})
</script>

<template>
  <NModal v-model:show="show" :auto-focus="false" preset="card" style="width: 95%; max-width: 640px">
    <div>
      <NTabs v-model:value="active" type="line" animated>
        <NTabPane name="General" tab="General">
          <template #tab>
            <SvgIcon class="text-lg" icon="ri:file-user-line" />
            <span class="ml-2">{{ $t('setting.general') }}</span>
          </template>
          <div class="min-h-[100px]">
            <General />
          </div>
        </NTabPane>
        <NTabPane v-if="isChatGPTAPI" name="Advanced" tab="Advanced">
          <template #tab>
            <SvgIcon class="text-lg" icon="ri:equalizer-line" />
            <span class="ml-2">{{ $t('setting.advanced') }}</span>
          </template>
          <div class="min-h-[100px]">
            <Advanced />
          </div>
        </NTabPane>
        <NTabPane name="Config" tab="Config">
          <template #tab>
            <SvgIcon class="text-lg" icon="ri:list-settings-line" />
            <span class="ml-2">{{ $t('setting.config') }}</span>
          </template>
          <About />
        </NTabPane>
      </NTabs>
    </div>
  </NModal>
</template>


================================================
FILE: src/components/common/SvgIcon/index.vue
================================================
<script setup lang='ts'>
import { computed, useAttrs } from 'vue'
import { Icon } from '@iconify/vue'

interface Props {
  icon?: string
}

defineProps<Props>()

const attrs = useAttrs()

const bindAttrs = computed<{ class: string; style: string }>(() => ({
  class: (attrs.class as string) || '',
  style: (attrs.style as string) || '',
}))
</script>

<template>
  <Icon :icon="icon || ''" v-bind="bindAttrs" />
</template>


================================================
FILE: src/components/common/UserAvatar/index.vue
================================================
<script setup lang='ts'>
import { computed } from 'vue'
import { NAvatar } from 'naive-ui'
import { useUserStore } from '@/store'
import defaultAvatar from '@/assets/avatar.jpg'
import { isString } from '@/utils/is'

const userStore = useUserStore()

const userInfo = computed(() => userStore.userInfo)
</script>

<template>
  <div class="flex items-center overflow-hidden">
    <div class="w-10 h-10 overflow-hidden rounded-full shrink-0">
      <template v-if="isString(userInfo.avatar) && userInfo.avatar.length > 0">
        <NAvatar
          size="large"
          round
          :src="userInfo.avatar"
          :fallback-src="defaultAvatar"
        />
      </template>
      <template v-else>
        <NAvatar size="large" round :src="defaultAvatar" />
      </template>
    </div>
    <div class="flex-1 min-w-0 ml-2">
      <h2 class="overflow-hidden font-bold text-md text-ellipsis whitespace-nowrap">
        {{ userInfo.name ?? 'ChenZhaoYu' }}
      </h2>
      <p class="overflow-hidden text-xs text-gray-500 text-ellipsis whitespace-nowrap">
        <span
          v-if="isString(userInfo.description) && userInfo.description !== ''"
          v-html="userInfo.description"
        />
      </p>
    </div>
  </div>
</template>


================================================
FILE: src/components/common/index.ts
================================================
import HoverButton from './HoverButton/index.vue'
import NaiveProvider from './NaiveProvider/index.vue'
import SvgIcon from './SvgIcon/index.vue'
import UserAvatar from './UserAvatar/index.vue'
import Setting from './Setting/index.vue'
import PromptStore from './PromptStore/index.vue'

export { HoverButton, NaiveProvider, SvgIcon, UserAvatar, Setting, PromptStore }


================================================
FILE: src/components/custom/GithubSite.vue
================================================
<template>
  <div class="text-neutral-400">
    <span>Star on</span>
    <a href="https://github.com/Chanzhaoyu/chatgpt-bot" target="_blank" class="text-blue-500">
      GitHub
    </a>
  </div>
</template>


================================================
FILE: src/components/custom/index.ts
================================================
import GithubSite from './GithubSite.vue'

export { GithubSite }


================================================
FILE: src/hooks/useBasicLayout.ts
================================================
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'

export function useBasicLayout() {
  const breakpoints = useBreakpoints(breakpointsTailwind)
  const isMobile = breakpoints.smaller('sm')

  return { isMobile }
}


================================================
FILE: src/hooks/useIconRender.ts
================================================
import { h } from 'vue'
import { SvgIcon } from '@/components/common'

export const useIconRender = () => {
  interface IconConfig {
    icon?: string
    color?: string
    fontSize?: number
  }

  interface IconStyle {
    color?: string
    fontSize?: string
  }

  const iconRender = (config: IconConfig) => {
    const { color, fontSize, icon } = config

    const style: IconStyle = {}

    if (color)
      style.color = color

    if (fontSize)
      style.fontSize = `${fontSize}px`

    if (!icon)
      window.console.warn('iconRender: icon is required')

    return () => h(SvgIcon, { icon, style })
  }

  return {
    iconRender,
  }
}


================================================
FILE: src/hooks/useLanguage.ts
================================================
import { computed } from 'vue'
import { enUS, esAR, koKR, ruRU, viVN, zhCN, zhTW } from 'naive-ui'
import { useAppStore } from '@/store'
import { setLocale } from '@/locales'

export function useLanguage() {
  const appStore = useAppStore()

  const language = computed(() => {
    setLocale(appStore.language)
    switch (appStore.language) {
      case 'en-US':
        return enUS
      case 'es-ES':
        return esAR
      case 'ko-KR':
        return koKR
      case 'vi-VN':
        return viVN
      case 'ru-RU':
        return ruRU
      case 'zh-CN':
        return zhCN
      case 'zh-TW':
        return zhTW
      default:
        return enUS
    }
  })

  return { language }
}


================================================
FILE: src/hooks/useTheme.ts
================================================
import type { GlobalThemeOverrides } from 'naive-ui'
import { computed, watch } from 'vue'
import { darkTheme, useOsTheme } from 'naive-ui'
import { useAppStore } from '@/store'

export function useTheme() {
  const appStore = useAppStore()

  const OsTheme = useOsTheme()

  const isDark = computed(() => {
    if (appStore.theme === 'auto')
      return OsTheme.value === 'dark'
    else
      return appStore.theme === 'dark'
  })

  const theme = computed(() => {
    return isDark.value ? darkTheme : undefined
  })

  const themeOverrides = computed<GlobalThemeOverrides>(() => {
    if (isDark.value) {
      return {
        common: {},
      }
    }
    return {}
  })

  watch(
    () => isDark.value,
    (dark) => {
      if (dark)
        document.documentElement.classList.add('dark')
      else
        document.documentElement.classList.remove('dark')
    },
    { immediate: true },
  )

  return { theme, themeOverrides }
}


================================================
FILE: src/icons/403.vue
================================================
<template>
  <div class="text-[#142D6E] dark:text-[#3a71ff]">
    <svg viewBox="0 0 400 300" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="31" y="31" width="338" height="238"><path d="M368.9 31H31v238h337.9V31Z" fill="#fff" /></mask><g mask="url(#a)" fill-rule="evenodd" clip-rule="evenodd"><path d="m357.4 219.2 3.4-39.3-58.7-5.1-3.4 39.3 58.7 5.1Z" fill="#fff" /><path d="M299.4 213.9h-.4v-.4l-.5-.1v.9l.8.1.1-.5ZM355.5 218.8l-1.3-.1v.5l1.3.1v-.5ZM353 218.5l-1.3-.1v.5l1.3.1v-.5ZM350.4 218.3l-1.3-.1v.5l1.3.1v-.5ZM347.9 218.1l-1.3-.1v.5l1.3.1v-.5ZM345.4 217.9l-1.3-.1v.5l1.3.1v-.5ZM342.8 217.6l-1.3-.1v.5l1.3.1v-.5ZM340.3 217.4l-1.3-.1v.5l1.3.1v-.5ZM337.7 217.2l-1.3-.1v.5l1.3.1v-.5ZM335.2 217l-1.3-.1v.5l1.3.1v-.5ZM332.7 216.8l-1.3-.1v.5l1.3.1v-.5ZM330.1 216.5l-1.3-.1v.5l1.3.1v-.5ZM327.6 216.3l-1.3-.1v.5l1.3.1v-.5ZM325.1 216.1l-1.3-.1v.5l1.3.1v-.5ZM322.5 215.9l-1.3-.1v.5l1.3.1v-.5ZM320 215.7l-1.3-.1v.5l1.3.1v-.5ZM317.5 215.4l-1.3-.1v.5l1.3.1v-.5ZM314.9 215.2l-1.3-.1v.5l1.3.1v-.5ZM312.4 215l-1.3-.1v.5l1.3.1v-.5ZM309.8 214.8l-1.3-.1v.5l1.3.1v-.5ZM307.3 214.6l-1.3-.1v.5l1.3.1v-.5ZM304.8 214.3l-1.3-.1v.5l1.3.1v-.5ZM302.2 214.1l-1.3-.1v.5l1.3.1v-.5ZM357.7 218.6l-.4-.1-.1.4h-.4v.5h.9v-.8ZM357.9 216h-.5l-.1 1.2.5.1.1-1.3ZM358.1 213.7h-.5l-.1 1.2h.5l.1-1.2ZM358.3 211.2h-.5l-.1 1.2h.5l.1-1.2ZM358.6 208.8h-.5l-.1 1.2h.5l.1-1.2ZM358.8 206.3h-.5l-.1 1.2h.5l.1-1.2ZM359 203.9h-.5l-.1 1.2h.5l.1-1.2ZM359.2 201.4h-.5l-.1 1.2h.5l.1-1.2ZM359.4 199h-.5l-.1 1.2h.5l.1-1.2ZM359.6 196.5h-.5l-.1 1.2h.5l.1-1.2ZM359.8 194.1h-.5l-.1 1.2h.5l.1-1.2ZM360 191.6h-.5l-.1 1.2h.5l.1-1.2ZM360.2 189.1h-.5l-.1 1.2h.5l.1-1.2ZM360.4 186.7h-.5l-.1 1.2h.5l.1-1.2ZM360.6 184.2h-.5l-.1 1.2h.5l.1-1.2ZM360.8 181.8h-.5l-.1 1.2h.5l.1-1.2ZM361 179.7l-.8-.1-.1.5h.4v.4h.5v-.8ZM358.9 179.5l-1.3-.1v.5l1.3.1v-.5ZM356.3 179.3l-1.3-.1v.5l1.3.1v-.5ZM353.8 179l-1.3-.1v.5l1.3.1v-.5ZM351.2 178.8l-1.3-.1v.5l1.3.1v-.5ZM348.7 178.6l-1.3-.1v.5l1.3.1v-.5ZM346.2 178.4l-1.3-.1v.5l1.3.1v-.5ZM343.6 178.1l-1.3-.1v.5l1.3.1v-.5ZM341.1 177.9l-1.3-.1v.5l1.3.1v-.5ZM338.6 177.7l-1.3-.1v.5l1.3.1v-.5ZM336 177.5l-1.3-.1v.5l1.3.1v-.5ZM333.5 177.3l-1.3-.1v.5l1.3.1v-.5ZM330.9 177l-1.3-.1v.5l1.3.1v-.5ZM328.4 176.8l-1.3-.1v.5l1.3.1v-.5ZM325.9 176.6l-1.3-.1v.5l1.3.1v-.5ZM323.3 176.4l-1.3-.1v.5l1.3.1v-.5ZM320.6 176.2l-1.3-.1.2.5 1.3.1-.2-.5ZM318.3 175.9l-1.3-.1v.5l1.3.1v-.5ZM315.7 175.7l-1.3-.1v.5l1.3.1v-.5ZM313.2 175.5l-1.3-.1v.5l1.3.1v-.5ZM310.7 175.3l-1.3-.1v.5l1.3.1v-.5ZM308.1 175.1l-1.3-.1v.5l1.3.1v-.5ZM305.6 174.8l-1.3-.1v.5l1.3.1v-.5ZM302.3 175.1h.4v-.5h-.9v.8l.4.1.1-.4ZM299.2 211.1h-.5l-.1 1.2h.5l.1-1.2ZM299.4 208.6h-.5l-.1 1.2h.5l.1-1.2ZM299.6 206.2h-.5l-.1 1.2h.5l.1-1.2ZM299.8 203.7h-.5l-.1 1.2h.5l.1-1.2ZM300 201.3h-.5l-.1 1.2h.5l.1-1.2ZM300.3 198.8h-.5l-.1 1.2h.5l.1-1.2ZM300.5 196.3h-.5l-.1 1.2h.5l.1-1.2ZM300.7 193.9h-.5l-.1 1.2h.5l.1-1.2ZM300.9 191.4h-.5l-.1 1.2h.5l.1-1.2ZM301.1 189h-.5l-.1 1.2h.5l.1-1.2ZM301.3 186.5h-.5l-.1 1.2h.5l.1-1.2ZM301.5 184.1h-.5l-.1 1.2h.5l.1-1.2ZM301.7 181.6h-.5l-.1 1.2h.5l.1-1.2ZM301.9 179.2h-.5l-.1 1.2h.5l.1-1.2ZM302.1 176.7h-.5l-.1 1.2h.5l.1-1.2Z" fill="#DBDBDB" /><path d="m355.2 216.5 2.9-34.4-53.8-4.6-2.9 34.4 53.8 4.6Z" fill="#EBEBEB" /><path d="M333.895 197.096c.2-2.5-1.7-4.7-4.2-4.9-2.5-.2-4.7 1.7-4.9 4.2-.2 2.5 1.7 4.7 4.2 4.9 2.5.2 4.7-1.7 4.9-4.2Zm-7.79-.491c-.1 1.8 1.3 3.4 3.1 3.5 1.8.1 3.4-1.2 3.5-3 .1-1.9-1.3-3.5-3.1-3.6-1.8-.1-3.4 1.3-3.5 3.1Z" fill="#DBDBDB" /><path d="m316.8 195.802.4.5c3.6 4.2 7.5 6.5 11.7 6.8l.7.1c6.8.1 11.6-4.9 11.8-5.1l.4-.4-.3-.4c-.2-.2-4.2-5.9-10.9-6.8-4.4-.6-8.9 1.1-13.3 4.9l-.5.4Zm12.8 5.999c5.2.1 9.2-3.1 10.4-4.3-1.1-1.3-4.6-5.2-9.7-5.8-3.7-.5-7.7.9-11.7 4.2 3.4 3.8 7.1 5.8 11 5.9Z" fill="#DBDBDB" /><path d="m320.004 205.586 19.368-16.587-.846-.988-19.367 16.588.845.987Z" fill="#DBDBDB" /><path d="m368.8 128.1-7.5-15.8-34-13.2-22.9 58.9 45.9 17.7 18.5-47.6Z" fill="#fff" /><path d="m350.4 175.6-1.2-.5-.2.4 1.2.5.2-.4ZM351.3 174l-.4-.2-.5 1.2.4.2.5-1.2ZM348.1 174.6l-1.2-.5-.2.4 1.2.5.2-.4ZM345.7 173.8l-1.2-.5-.2.4 1.2.5.2-.4ZM343.4 172.8l-1.2-.5-.2.4 1.2.5.2-.4ZM352.2 171.7l-.4-.2-.5 1.2.4.2.5-1.2ZM341 171.9l-1.2-.5-.2.4 1.2.5.2-.4ZM338.6 171l-1.2-.5-.2.4 1.2.5.2-.4ZM353.1 169.3l-.4-.2-.5 1.2.4.2.5-1.2ZM336.2 170.1l-1.2-.5-.2.4 1.2.5.2-.4ZM333.9 169.1l-1.2-.5-.2.4 1.2.5.2-.4ZM331.5 168.2l-1.2-.5-.2.4 1.2.5.2-.4ZM354 167l-.4-.2-.5 1.2.4.2.5-1.2ZM329.1 167.3l-1.2-.5-.2.4 1.2.5.2-.4ZM326.8 166.4l-1.2-.5-.2.4 1.2.5.2-.4ZM324.4 165.5l-1.2-.5-.2.4 1.2.5.2-.4ZM354.9 164.6l-.4-.2-.5 1.2.4.2.5-1.2ZM322.1 164.6l-1.2-.5-.2.4 1.2.5.2-.4ZM319.7 163.6l-1.2-.5-.2.4 1.2.5.2-.4ZM355.8 162.2l-.4-.2-.5 1.2.4.2.5-1.2ZM317.3 162.7l-1.2-.5-.2.4 1.2.5.2-.4ZM315 161.8l-1.2-.5-.2.4 1.2.5.2-.4ZM312.6 160.9l-1.2-.5-.2.4 1.2.5.2-.4ZM356.8 159.8l-.4-.2-.5 1.2.4.2.5-1.2ZM310.2 160l-1.2-.5-.2.4 1.2.5.2-.4ZM307.9 159.1l-1.2-.5-.2.4 1.2.5.2-.4ZM357.7 157.5l-.4-.2-.5 1.2.4.2.5-1.2ZM305.4 158l-.7-.3-.4-.2-.2.5 1.2.5.1-.5ZM305.7 155.4l-.4-.2-.5 1.2.4.2.5-1.2ZM358.6 155.1l-.4-.2-.5 1.2.4.2.5-1.2ZM306.6 153.1l-.4-.2-.5 1.2.4.2.5-1.2ZM359.5 152.8l-.4-.2-.5 1.2.4.2.5-1.2ZM307.5 150.7l-.4-.2-.5 1.2.4.2.5-1.2ZM360.4 150.4l-.4-.2-.5 1.2.4.2.5-1.2ZM308.5 148.3l-.4-.2-.5 1.2.4.2.5-1.2ZM361.3 148l-.4-.2-.5 1.2.4.2.5-1.2ZM309.4 146l-.4-.2-.5 1.2.4.2.5-1.2ZM362.3 145.7l-.4-.2-.5 1.2.4.2.5-1.2ZM310.3 143.6l-.4-.2-.5 1.2.4.2.5-1.2ZM363.2 143.3l-.4-.2-.5 1.2.4.2.5-1.2ZM311.2 141.2l-.4-.2-.5 1.2.4.2.5-1.2ZM364.1 140.9l-.4-.2-.5 1.2.4.2.5-1.2ZM312.1 138.9l-.4-.2-.5 1.2.4.2.5-1.2ZM365 138.6l-.4-.2-.5 1.2.4.2.5-1.2ZM313 136.5l-.4-.2-.5 1.2.4.2.5-1.2ZM365.9 136.2l-.4-.2-.5 1.2.4.2.5-1.2ZM314 134.1l-.4-.2-.5 1.2.4.2.5-1.2ZM366.9 133.8l-.4-.2-.5 1.2.4.2.5-1.2ZM314.9 131.8l-.4-.2-.5 1.2.4.2.5-1.2ZM367.8 131.5l-.4-.2-.5 1.2.4.2.5-1.2ZM315.8 129.4l-.4-.2-.5 1.2.4.2.5-1.2ZM368.7 129.1l-.4-.2-.5 1.2.4.2.5-1.2ZM316.7 127l-.4-.2-.5 1.2.4.2.5-1.2ZM368.9 127.8l-.5-1.1-.4.2.5 1.1.4-.2ZM317.6 124.7l-.4-.2-.5 1.2.4.2.5-1.2ZM367.8 125.5l-.5-1.1-.4.2.5 1.1.4-.2ZM318.4 122.3l-.4-.2-.4 1.2.4.2.4-1.2ZM366.7 123.2l-.5-1.1-.4.2.5 1.1.4-.2ZM319.5 119.9l-.4-.2-.5 1.2.4.2.5-1.2ZM365.7 120.9l-.5-1.1-.4.2.5 1.1.4-.2ZM364.6 118.6l-.5-1.1-.4.2.5 1.1.4-.2ZM320.4 117.6l-.4-.2-.5 1.2.4.2.5-1.2ZM363.5 116.3l-.5-1.1-.4.2.5 1.1.4-.2ZM321.3 115.2l-.4-.2-.5 1.2.4.2.5-1.2ZM362.4 114l-.5-1.1-.4.2.5 1.1.4-.2ZM322.2 112.8l-.4-.2-.5 1.2.4.2.5-1.2ZM361 111.9l-1.2-.5-.2.4 1.2.5.2-.4ZM323.1 110.5l-.4-.2-.5 1.2.4.2.5-1.2ZM358.6 111l-1.2-.5-.2.4 1.2.5.2-.4ZM356.3 110l-1.2-.5-.2.4 1.2.5.2-.4ZM353.9 109.1l-1.2-.5-.2.4 1.2.5.2-.4ZM324.1 108.1l-.4-.2-.5 1.2.4.2.5-1.2ZM351.5 108.2l-1.2-.5-.2.4 1.2.5.2-.4ZM349.2 107.3l-1.2-.5-.2.4 1.2.5.2-.4ZM325 105.7l-.4-.2-.5 1.2.4.2.5-1.2ZM346.8 106.4l-1.2-.5-.2.4 1.2.5.2-.4ZM344.4 105.5l-1.2-.5-.2.4 1.2.5.2-.4ZM342.1 104.5l-1.2-.5-.2.4 1.2.5.2-.4ZM325.9 103.4l-.4-.2-.5 1.2.4.2.5-1.2ZM339.7 103.6l-1.2-.5-.2.4 1.2.5.2-.4ZM337.3 102.7l-1.2-.5-.2.4 1.2.5.2-.4ZM335 101.8l-1.2-.5-.2.4 1.2.5.2-.4ZM326.8 101l-.4-.2-.5 1.2.4.2.5-1.2ZM332.6 100.9l-1.2-.5-.2.4 1.2.5.2-.4ZM330.2 100.1l-1.2-.5-.2.4 1.2.4.2-.3ZM327.4 99.3l.3.1.2-.4-.7-.3-.4.9.4.2.2-.5Z" fill="#DBDBDB" /><path d="m357 123.5 11.8 4.6-7.5-15.8-4.3 11.2ZM342.201 139.101c.9-2.4-.3-5-2.7-5.9-2.4-.9-5 .3-5.9 2.7-.9 2.4.3 5 2.7 5.9 2.4.9 5-.3 5.9-2.7Zm-7.299-2.699c-.6 1.7.2 3.6 1.9 4.2 1.7.6 3.6-.2 4.2-1.9.6-1.7-.2-3.6-1.9-4.2-1.7-.6-3.6.2-4.2 1.9Z" fill="#DBDBDB" /><path d="m349 142.104.5-.3-.2-.5c-.1-.3-2.3-6.9-8.5-9.6-4-1.7-8.8-1.4-14.1 1l-.6.3.3.6c2.3 5.1 5.4 8.4 9.3 9.9.2.1.5.2.5.1 6.5 2.1 12.5-1.3 12.8-1.5Zm-12.2.4c4.9 1.6 9.7-.4 11.2-1.1-.6-1.6-2.9-6.3-7.6-8.3-3.6-1.6-7.8-1.4-12.5.6 2.2 4.6 5.2 7.6 8.9 8.8Z" fill="#DBDBDB" /><path d="m326.581 143.347 23.261-10.449-.533-1.186-23.261 10.45.533 1.185ZM260.796 107.601c-.8-7.3 4-14 10.9-14.8 6.9-.8 13.2 4.5 14.1 11.9l2.3 19.4 5.5-.6-2.3-19.4c-1.2-10.5-10.3-18.1-20.3-17.1-9.9 1.1-16.9 10.7-15.7 21.2l2.3 19.4 5.5-.6-2.3-19.4Z" fill="#DBDBDB" /><path d="m257.7 127.7 5.5-.6-1.4-11.8-5.5.4 1.4 12ZM286.9 113.4l1.2 10.7 5.5-.6-1.2-10.5-5.5.4Z" fill="#A6A6A6" /><path d="m254.43 160.014 49.952-5.91-4.712-39.822-49.951 5.91 4.711 39.822Z" fill="#FFC412" /><path d="M280.4 135.8c1.1-2.2.1-4.9-2.1-5.9-2.2-1.1-4.9-.1-5.9 2.1-1.1 2.2-.1 4.9 2.1 5.9l-.9 10.1 8.9-1-3.3-9.6c.5-.4 1-.9 1.2-1.6Z" fill="#fff" /><path d="M362.5 81.4c0-7-5.7-12.7-12.7-12.7-7 0-12.7 5.7-12.7 12.7 0 7 5.7 12.7 12.7 12.7 7 0 12.7-5.7 12.7-12.7Zm-22.4.1c0 5.4 4.3 9.7 9.7 9.7 5.3 0 9.7-4.3 9.7-9.7 0-5.4-4.3-9.7-9.7-9.7-5.3 0-9.7 4.3-9.7 9.7Z" fill="#03D5B7" /><path d="m238.498 182.298 4.9-12.2c.2-.5-.1-1.1-.6-1.3-.5-.2-1.1.1-1.3.6l-4.9 12.2c-.2.5.1 1.1.6 1.3.1.1.3.1.4.1.4 0 .8-.3.9-.7ZM225 165.2c-.8-1.9-2.3-3.3-4.2-4.1-1.9-.8-4-.8-5.9 0-1.9.8-3.3 2.3-4.1 4.2-.8 1.9-.8 4 0 5.9.8 1.9 2.3 3.3 4.2 4.1 1 .4 1.9.6 2.9.6s2-.2 3-.6c1.9-.8 3.3-2.3 4.1-4.2.8-1.9.8-4 0-5.9Zm-4.903 8.3c1.4-.6 2.5-1.7 3-3.1.5-1.4.6-2.9 0-4.3s-1.7-2.5-3.1-3c-.7-.4-1.4-.5-2.1-.5-.8 0-1.5.2-2.2.5-1.4.6-2.5 1.7-3 3.1-.5 1.4-.6 2.9 0 4.3s1.7 2.5 3.1 3c1.4.6 2.9.6 4.3 0ZM215.795 196.296c.5-.3.5-1 .2-1.4l-21.3-27.9c-.3-.4-1-.5-1.4-.2-.5.3-.5 1-.2 1.4l21.3 27.9c.2.3.5.4.8.4.2 0 .4-.1.6-.2Z" fill="#FFC412" /><path d="m259.6 80.6-12.5-44.9-65.9 14.6 12.6 45 65.8-14.7Z" fill="#DBDBDB" /><path d="m222.5 41.1 24.6-5.4-1.3-4.7-23.3 5.2v4.9Z" fill="#DBDBDB" /><path d="m259.7 80.6-4.5-43.9-65.8 14.6 4.4 43.9 65.9-14.6Z" fill="#EBEBEB" /><path d="m230.6 42.1 24.6-5.4-.4-4.6-23.3 5.2-.9 4.8Z" fill="#EBEBEB" /><path d="M227.398 65.298c-.4-2.1-2.6-3.5-4.7-3-2.1.4-3.5 2.6-3 4.7.4 2.1 2.6 3.5 4.7 3 2.1-.4 3.5-2.6 3-4.7Zm-6.701 1.505c.3 1.5 1.9 2.5 3.4 2.2 1.5-.3 2.5-1.9 2.2-3.4-.3-1.6-1.8-2.5-3.4-2.2-1.5.3-2.5 1.9-2.2 3.4Z" fill="#fff" /><path d="m213.2 67.999-.3.5.5.3c4 2.6 7.9 3.5 11.4 2.7l.5-.1c5.6-1.6 8.4-7 8.5-7.2l.2-.4-.4-.3c-.2-.2-4.9-3.9-10.7-2.9-3.7.6-7 3.1-9.7 7.4Zm11.9 2.3c4.3-1.2 6.8-4.9 7.6-6.2-1.2-.8-5.1-3.1-9.5-2.4-3.3.6-6.2 2.8-8.7 6.5 3.8 2.3 7.3 3 10.6 2.1Z" fill="#fff" /><path d="m218.069 75.787 11.871-18.64-.927-.591-11.872 18.64.928.591Z" fill="#fff" /><path d="M58.195 88.704h-14.8c.9 2.7 2.7 5.1 5.4 6.7l-5.9 28.5 25.5-.1-4.7-21.8h-8.6c-.5 0-1-.5-1-1 0-.6.4-1 1-1h8.2l-1-4.6c1.7-1 3-2.4 4.1-4.1 1.9-3.1 2.3-6.7 1.5-9.9h-14.8c-.6 0-1-.4-1-1s.4-1 1-1h14c-1-2.2-2.7-4.2-4.9-5.6-6-3.7-13.9-1.8-17.6 4.1-1.7 2.8-2.2 5.9-1.7 8.8h15.3c.6 0 1 .4 1 1s-.4 1-1 1ZM324.1 74.197c4.2-.9 6.9-5.1 6-9.3-.9-4.2-5.1-6.9-9.3-6-4.2.9-6.9 5.1-6 9.3l-15 9.5 10.9 11.2 9.9-14.8c1.2.3 2.4.3 3.5.1Z" fill="#F5F5F5" /><path d="M333.7 74.3c.2-.3.2-.7 0-.9l-9.8-10.2c-.3-.2-.7-.2-.9 0-.2.3-.2.7 0 .9l9.8 10.2c.1.1.2.2.4.2.1 0 .3-.1.5-.2ZM322.7 69.5c.2-.3.2-.7 0-.9l-9.8-10.2c-.3-.2-.7-.2-.9 0-.2.3-.2.7 0 .9l9.9 10.2c.1.1.2.2.4.2.1 0 .3-.1.4-.2ZM325.4 83.9c.2-.3.2-.7 0-.9l-9.8-10.2c-.3-.2-.7-.2-.9 0-.2.3-.2.7 0 .9l9.8 10.2c.1.1.2.2.4.2s.3 0 .5-.2Z" fill="#fff" /><path d="M45.7 140.8h-8.4c-.5 0-1 .5-1 1L31 257.7c0 .3.1.5.3.7.2.2.4.3.7.3h8.8c.5 0 .9-.4 1-1l4.9-115.9c0-.2-.1-.5-.3-.7-.2-.2-.4-.3-.7-.3Zm-6 115.9 4.8-113.9h-6.3L33 256.7h6.7Z" fill="currentColor" /><path d="M154.8 268.8h117.5v-4.9H154.8v4.9Z" fill="#00BC9C" /><path d="m327.6 268.8 14-73H234.2l-14.1 73h107.5Z" fill="#00BC9C" /><path d="m331.9 268.8 14.1-73H238.5l-14 73h107.4Z" fill="#03D5B7" /><path d="M289.7 231.9c0-5-4-9-9-9s-9 4-9 9 4 9 9 9c4.9 0 8.9-4 9-9Zm-15.5 0c0 3.5 2.9 6.5 6.4 6.5 3.6 0 6.5-2.9 6.5-6.4 0-3.6-2.9-6.5-6.4-6.5-3.6 0-6.5 2.9-6.5 6.4Z" fill="#fff" /><path d="M280.8 244.502c.5 0 .9-.1 1.3 0 13.2-.7 21.9-11.3 22.3-11.7l.6-.8-.6-.8c-.4-.5-9.1-11-22.3-11.7-8.7-.4-17.1 3.5-25.2 11.6l-.9.9.9.9c7.7 7.7 15.7 11.6 23.9 11.6Zm1.2-2.604c10.1-.5 17.5-7.5 19.8-9.9-2.3-2.4-9.7-9.4-19.8-9.9-7.6-.4-15.1 2.9-22.3 9.9 7.3 7 14.8 10.3 22.3 9.9Z" fill="#fff" /><path d="m263.81 250.562 35.355-35.355-1.768-1.768-35.355 35.355 1.768 1.768Z" fill="#fff" /><path d="M54.3 256.7H33l5.2-113.9H116c.6 0 1-.4 1-1s-.4-1-1-1H37.3c-.5 0-1 .5-1 1L31 257.7c0 .3.1.5.3.7.2.2.4.3.7.3h22.3c.6 0 1-.4 1-1s-.4-1-1-1Z" fill="currentColor" /><path d="m139.404 157.398.7-5.3c3.3 1.7 7.1 2.8 15.9.3 14.4-4.2 17.4-25.4 19.2-38.1l.2-1.3c1-7.1-.5-13.5-4.3-18.5-4.7-6.2-12.8-10.1-24-11.6-21.9-2.9-29.1 22.3-30.2 26.5-3 1-4.3 4.3-4.3 4.4-1 2.2-1.1 4.3-.3 6.3 1.4 3.5 5.3 5.6 7 6.4l-8 30c-.2.6-2.9 10.9 7.6 14.3 1.7.4 3.9.8 6 .8 1.5 0 3-.2 4.2-.7.3-.1.5-.2.8-.3 5.1-2.3 8.8-7.4 9.5-13.2Zm-1.007-6.997c0-.3.2-.6.5-.8.3-.2.6-.1.9 0 3.5 1.9 6.5 3.5 15.7.8 13.2-3.8 16.1-24.3 17.8-36.5l.2-1.4c.9-6.6-.4-12.4-3.9-17-4.4-5.8-12-9.4-22.6-10.8-22-2.9-28.1 25.3-28.2 25.6-.1.4-.4.7-.8.8-2.3.5-3.5 3.4-3.5 3.4-.8 1.7-.9 3.3-.3 4.8 1.5 3.7 6.6 5.6 6.7 5.7.5.2.7.7.6 1.2l-8.2 30.9c-.1.4-2.6 9 6.2 11.8 3.7.9 6.9 1 9 .2.2-.1.4-.2.7-.3 4.5-2.1 7.7-6.5 8.3-11.7l.9-6.7Z" fill="currentColor" /><path d="M158.599 150.3c9.8 2.9 23.4-3 23.4-3l-9-23.8c-1.09 20.34-14.17 26.74-14.4 26.8ZM116.5 141.502l3.1-15.8c-11.4-8.7-5.3-13.1-1.4-14.6 1.4-.5 3.4-.1 3.4-.1.5 10.4 3.2 13.1 7 12.5 4.1-.6 3.4-10 3.4-10 15.6 4.7 21.3-18 21.3-18 7.9 17.7 27.8 14.5 27.8 14.5 1.7-35.5-29.1-36.3-29.3-36.2-43.6-24.3-70.9 47.9-70.9 47.9 7.2 20.6 35.6 19.8 35.6 19.8Z" fill="currentColor" /><path d="M111.671 121.402a5.202 5.202 0 0 0 2.826 6.789 5.202 5.202 0 0 0 6.789-2.826 5.202 5.202 0 0 0-2.826-6.789 5.203 5.203 0 0 0-6.789 2.826Z" fill="#FFC412" /><path d="M163.286 118.3a7.302 7.302 0 0 0 3.967 9.531 7.301 7.301 0 0 0 9.531-3.967 7.302 7.302 0 0 0-3.967-9.531 7.302 7.302 0 0 0-9.531 3.967Z" fill="#fff" /><path d="M173.195 128.805c2.1-.8 3.7-2.4 4.5-4.5.9-2.1.9-4.3 0-6.4-.8-2.1-2.4-3.7-4.5-4.5-2.1-.9-4.3-.9-6.4 0-2.1.8-3.7 2.4-4.5 4.5-.9 2.1-.9 4.3 0 6.4.8 2.1 2.4 3.7 4.5 4.5 1 .4 2.1.6 3.2.6s2.2-.2 3.2-.6ZM167.6 115.2c-1.6.7-2.8 1.9-3.4 3.4-.6 1.6-.6 3.3 0 4.9.7 1.6 1.9 2.8 3.4 3.4 3.2 1.3 6.9-.2 8.3-3.4.6-1.6.6-3.3 0-4.9-.7-1.6-1.9-2.8-3.4-3.4-.9-.3-1.7-.5-2.5-.5s-1.7.2-2.4.5Z" fill="currentColor" /><path d="M169.213 123.476a1.9 1.9 0 1 0 3.126 2.16 1.9 1.9 0 0 0-3.126-2.16ZM178.197 120.397l3.7-1.3c.5-.2.8-.8.6-1.3-.2-.5-.8-.8-1.3-.6l-3.7 1.3c-.5.2-.8.8-.6 1.3.1.4.5.7.9.7.2 0 .3 0 .4-.1ZM176.7 117.5l3.7-3.7c.4-.4.4-1 0-1.4-.4-.4-1-.4-1.4 0l-3.7 3.7c-.4.4-.4 1 0 1.4.2.2.4.3.7.3.2 0 .5-.1.7-.3Z" fill="currentColor" /><path d="M143.062 114.906a7.302 7.302 0 0 0 3.967 9.531 7.303 7.303 0 0 0 9.532-3.967 7.304 7.304 0 0 0-3.967-9.531 7.303 7.303 0 0 0-9.532 3.967Z" fill="#fff" /><path d="M146.595 110.005c-2.09.8-3.7 2.4-4.5 4.5-.9 2.1-.9 4.3 0 6.4.8 2.1 2.41 3.7 4.5 4.5 1 .4 2.11.6 3.21.6s2.2-.2 3.2-.6c2.1-.8 3.7-2.4 4.5-4.5.9-2.1.9-4.3 0-6.4-.8-2.1-2.4-3.7-4.5-4.5-2.11-.9-4.31-.9-6.41 0ZM143.9 115.2c-.6 1.6-.6 3.3 0 4.9.7 1.6 1.9 2.8 3.4 3.4 1.6.6 3.3.6 4.9 0 1.6-.7 2.8-1.9 3.4-3.4.6-1.6.6-3.3 0-4.9-.7-1.6-1.9-2.8-3.4-3.4-.8-.3-1.6-.5-2.4-.5-2.5 0-4.9 1.5-5.9 3.9Z" fill="currentColor" /><path d="M149.763 119.956a1.9 1.9 0 1 0 3.126 2.16 1.9 1.9 0 0 0-3.126-2.16ZM145.302 111.801c.4-.2.6-.8.4-1.3l-1.8-3.5c-.2-.4-.8-.6-1.3-.4-.4.2-.6.8-.4 1.3l1.8 3.5c.2.4.5.6.9.6.1 0 .3 0 .4-.2ZM149.1 110.4v-5.2c0-.5-.4-1-1-1s-1 .4-1 1v5.2c0 .5.4 1 1 1s1-.5 1-1ZM158.701 127.505c-.3.5-.2 1.1.3 1.4 1.2.7 2.2 1 3.2 1h1.1c.9-.3 1.6-.9 2.1-1.8 1-1.9-1-4-2-4.8l.5-6.6c.1-.6-.3-1.1-.9-1.1-.6-.1-1.1.3-1.1.9l-.6 7.1c0 .4.1.7.4.9 1 .7 2.2 2.1 1.9 2.6-.2.4-.5.7-.9.8-.6.1-1.6-.1-2.6-.7-.5-.3-1.1-.2-1.4.3ZM159.297 105.895c.4-.4.5-1 .1-1.4-.4-.4-1-.5-1.4-.1-3.3 3.1-6.9 1.9-7.1 1.8-.5-.2-1.1.1-1.3.6-.2.5.1 1.1.6 1.3 0 0 1.1.4 2.6.4 1.8 0 4.2-.5 6.5-2.6ZM176.603 112.003c0-.5-.4-1-1-1-4.6-.2-6.2-3.6-6.3-3.8-.2-.5-.8-.7-1.3-.5-.5.2-.7.8-.5 1.3.1.2 2.2 4.7 8.1 5 .6 0 1-.4 1-1ZM158.803 136.402c0 .6.4 1 1 1.1.5 0 .9-.4 1.1-1 .2-1.7-.2-3.1-1-4-.7-.8-1.8-1.2-3.1-1.3-3.2-.2-4.9 3.7-5 3.9-.2.5 0 1.1.5 1.3.5.2 1.1 0 1.3-.5 0 0 1.3-2.8 3-2.7.8.1 1.3.3 1.7.7.5.7.6 1.7.5 2.5Z" fill="currentColor" /><path d="m152.4 71.6-4.3-.6v2.3l5.9.6-1.6-2.3Z" fill="#E2A40A" /><path d="M136.403 67.003c-24 24.6-15.3 43.3-15.3 43.3 1.2-31.1 31.3-38.7 31.3-38.7-7.5-9.9-16-4.6-16-4.6Z" fill="#FFC412" /><path d="M151.899 241.999c1.9.9 11.7 5.4 14.9 4 .8-.4 1.1-.9 1.2-1.3 1.2-3.7-2.5-7.2-26.1-15.9-21.6-8-70.4 19.4-72.5 20.6-.5.3-.7.9-.4 1.4.2.3.6.5.9.5.2 0 .3 0 .5-.2.5-.2 50.1-28.1 70.8-20.4 18.3 6.8 25.8 10.8 24.9 13.4-.8.9-7.2-1-13.4-3.9-.5-.2-1.1 0-1.3.5-.2.5 0 1.1.5 1.3Z" fill="currentColor" /><path d="M160.796 258.495c.9-.8 1.3-2 1.3-3.3-.3-6.8-13.9-17.9-15.4-19.1-.4-.3-1.1-.2-1.4.2-.3.4-.2 1.1.2 1.4 4 3.2 14.4 12.6 14.6 17.6 0 .7-.2 1.3-.6 1.7-.6.3-2.8-.1-5.9-5.2-.3-.4-.9-.6-1.4-.3-.4.3-.6.9-.3 1.4 2.2 3.6 4.4 5.7 6.5 6.1.2.1.5.1.7.1.9 0 1.5-.4 1.6-.5 0-.1.1-.1.1-.1Z" fill="currentColor" /><path d="M147.701 262.501c.9.8 1.9 1.2 2.9 1.2.6 0 1.2-.2 1.9-.5 1-.6 3.3-2.8 1.5-9.4-2.2-8.3-13.4-15.1-13.9-15.4-.5-.3-1.1-.2-1.4.3-.3.5-.2 1.1.3 1.4.1.1 11 6.7 13 14.2 1.2 4.3.4 6.6-.6 7.2-.7.4-1.6.2-2.4-.5-.4-.4-1-.3-1.4.1-.4.4-.3 1 .1 1.4Z" fill="currentColor" /><path d="M133.799 241.999c-.3.5-.1 1.1.4 1.4.1 0 12.7 7.2 12.3 15.4-.2 4.5-1.3 5.2-1.6 5.3-.6.2-1.7-.5-2.4-1.3-.4-.4-1-.5-1.4-.1-.4.4-.5 1-.1 1.4.4.5 1.9 2 3.6 2 .3 0 .6 0 1.1-.1 1.8-.6 2.7-2.9 2.9-7.1.4-9.5-12.8-17-13.4-17.3-.5-.3-1.1-.1-1.4.4Z" fill="currentColor" /><path d="M132.403 258.101c.9.6 1.5 2.3 2.1 3.8.9 2.3 1.8 4.5 3.6 4.9 1.6.3 2.6-.2 3.1-.7 1.4-1.2 1.8-3.8 1.2-7.8-.9-6.3-11.5-13.4-12-13.7-.5-.3-1.1-.2-1.4.3-.3.5-.2 1.1.3 1.4 2.9 1.9 10.5 7.9 11.1 12.3.6 4.5-.2 5.8-.6 6.1-.2.2-.5.4-1.3.2s-1.6-2.1-2.2-3.7c-.8-1.8-1.5-3.8-2.8-4.7-2.3-1.7-5.3-2.8-11.5.6-5.4 2.9-21.4 9.9-21.6 10-.5.2-.7.8-.5 1.3.1.4.5.6.9.6.1 0 .2 0 .5-.1.6-.3 16.2-7.1 21.7-10.1s7.6-2 9.4-.7ZM55.5 258.7c0-.6-5.1-56.4-.9-73.1C59 168.4 68.7 160 112 158.5c.6 0 1-.4 1-1s-.4-1-1-1c-44.6 1.5-54.6 10.4-59.2 28.6-4.3 17 .7 71.4.9 73.7.1.5.5.9 1 .9.7-.1 1.1-.6.8-1Z" fill="currentColor" /><path d="m84.6 243 .9-44.8c0-.5-.4-1-1-1-.5 0-1 .4-1 1l-.9 44.8c0 .5.4 1 1 1 .5 0 1-.4 1-1Z" fill="currentColor" /><path d="M83.801 207.201c.6-.2.9-.8.7-1.3-.2-.6-.8-.9-1.3-.7-15.5 4.8-29.8-4.4-29.9-4.5-.5-.3-1.1-.2-1.4.3-.3.5-.2 1.1.3 1.4.5.3 9.6 6.2 21.5 6.2 3.2 0 6.6-.4 10.1-1.4ZM188.004 226.802c0-.3-1.4-28.9-7.3-52.8-6.1-24.8-41.7-18.8-42.1-18.7-.5.1-.9.7-.8 1.2.1.5.7.9 1.2.8.3 0 34.1-5.7 39.8 17.2 5.9 23.8 7.2 52.1 7.2 52.4 0 .6.5 1 1 1 .6 0 1-.5 1-1.1Z" fill="currentColor" /><path d="M167.104 234.804c.5 0 .9-.6.8-1.1l-7.2-46.7c0-.5-.6-.9-1.1-.8-.5 0-.9.6-.8 1.1l7.2 46.7c.1.4.5.8 1 .8h.1ZM168.497 245.701c.5.2 1.1-.2 1.2-.7.2-.8 5.4-20.6 31.8-18.1 14.7 1.4 20.8 7.7 23.3 12.7 2.1 4.2 1.9 7.9 1.5 8.7-1 .4-3.3.8-9-9.6-.3-.5-.9-.7-1.4-.4-.5.3-.7.9-.4 1.4 4.7 8.6 7.6 10.8 9.9 10.8.7 0 1.3-.2 2-.5 1.5-.8 1.3-4.5.9-6.4-.9-5-5.4-16.7-26.6-18.7-28.2-2.7-33.8 19.4-33.9 19.6-.2.5.2 1.1.7 1.2Z" fill="currentColor" /><path d="M186.504 258.995c1.2.1 2.2-.2 3-.9 2.2-2.1 1.9-7.2 1.7-11.2 0-1.3-.1-3 0-3.4 0-.2.4-.4 1.1-.4 2.3-.1 6 2.1 9.5 8.3 3.1 5.5 5.4 8 7.5 8 1.7-.1 2.5-1.7 3.1-2.2 1.2-2.5-2.7-14.6-8.5-19.3-.4-.4-1-.3-1.4.1-.4.4-.3 1.1.1 1.4 5.2 4.3 8.6 15.3 7.9 16.9-.4.7-.7 1.1-1 1.1-.4 0-2.1-.4-5.8-7-5-8.9-9.9-9.5-11.3-9.4-1.4 0-2.4.6-2.9 1.5-.4.7-.3 1.9-.2 4.4.2 3.2.5 8.1-1.1 9.6-.2.2-.6.5-1.4.4-.6 0-1.1-.3-1.5-.9-1.9-2.8-.4-10.6.3-13.4.2-.5-.2-1.1-.7-1.2-.5-.2-1.1.2-1.2.7-.3 1.1-2.9 11-.1 15.1.7 1 1.7 1.7 2.9 1.8Z" fill="currentColor" /><path d="M211.996 255.996c-.5-.2-1.1.2-1.2.7-.1.5.2 1.1.7 1.2 0 0 .5.1 1.1.1.7 0 1.7-.1 2.5-.9 1.3-.9 1.9-2.5 2-4.7.2-5.9-7.3-16.5-7.6-16.9-.3-.4-1-.5-1.4-.2-.4.3-.5 1-.2 1.4.1.1 7.5 10.5 7.3 15.7-.1 1.6-.5 2.7-1.2 3.2-.8.7-2 .4-2 .4Z" fill="currentColor" /><path d="M214.999 234.799c-.3-.5-.9-.7-1.4-.4-.5.3-.7.9-.4 1.4 1.7 3 6.3 11.5 6.4 15 .1 1.4-.5 2.5-1.9 3.1-1.1.5-2.3.6-2.3.6-.6 0-1 .4-1 1s.5 1 1 1c.2 0 3.2-.1 5-2.1.9-.9 1.4-2.2 1.3-3.7-.2-4.7-6.4-15.4-6.7-15.9ZM184.6 195.201c.3-.5.2-1.1-.3-1.4-.5-.3-1.1-.2-1.4.3-5.4 8.5-20.5 6.7-20.7 6.7-.5 0-1 .4-1.1.9 0 .5.4 1 .9 1.1.2 0 1.7.2 3.9.2 5.2 0 14.4-1.1 18.7-7.8Z" fill="currentColor" /><path d="m84.796 37.503-10.1 22.7c-.2.4-.1.8.2 1.1.2.2.4.3.7.3.1 0 .2-.1.3-.1l32.9-12.6c.3-.1.6-.5.6-.9s-.2-.7-.6-.9l-22.7-10.1c-.5-.2-1.1 0-1.3.5ZM77.5 58.8l28.3-10.9-19.6-8.7-8.7 19.6Z" fill="#03D5B7" /></g></svg>
  </div>
</template>


================================================
FILE: src/icons/500.vue
================================================
<template>
  <div class="text-[currentColor] dark:text-[#3a71ff]">
    <svg viewBox="0 0 400 300" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="31" y="32" width="338" height="237"><path d="M368.4 32H31v236.9h337.4V32Z" fill="#fff" /></mask><g mask="url(#a)" fill-rule="evenodd" clip-rule="evenodd"><path d="M79.498 122.599c-3.3-7.4-5.2-17.8-7.9-29.5-2.4-12.9-20.8-28.3-34.1-17.6-6.2 5.1-8 17.7-5.4 28.5 2.3 11 9 20.1 14 23.2 10.5 6.3 26.2.8 29.9 4.4 7.6 6.9 11.2 12.8 11.2 13 .2-.2-2.4-10.3-7.7-22Z" fill="#EBEBEB" /><path d="M64.9 112.503c.8-.1 1.1-1.4.6-2.9-.5-1.5-1.5-2.6-2.4-2.5-.9.1-1.2 1.5-.7 3s1.6 2.6 2.5 2.4ZM56.103 110.604c.8-.2 1.2-1.5.7-3.1s-1.6-2.8-2.5-2.7c-.9.1-1.1 1.6-.7 3.2.5 1.6 1.7 2.8 2.5 2.6ZM47.202 108.504c.9-.2 1.2-1.6.7-3.2s-1.6-2.9-2.6-2.8c-1 .1-1.3 1.7-.8 3.3.5 1.7 1.7 2.9 2.7 2.7Z" fill="#fff" /><path d="M76.002 69.395c1.3-7.3 11.4-9.2 14.7-15.1 3.2-5.9 4.5-20.4-.5-22.1-5.1-1.7-17 7-18.3 14.9-1.3 6.9-1.1 16.4-.3 21.8 1.3 8.7 5.1 14.1 5.1 14.1s-1.9-7.1-.7-13.6Z" fill="#EBEBEB" /><path d="M88.099 47.302c.7-.9 1-2 .9-3.2-.1-1.1-.8-1.5-1.4-.8-.7 1-1.1 2.1-.9 3.2.1 1.1.8 1.5 1.4.8ZM82.399 50.702c.7-.9 1-2.1.8-3.2-.1-1.1-.8-1.5-1.4-.8-.7 1-1 2.1-.8 3.2.2 1.1.8 1.5 1.4.8ZM76.796 54.202c.7-.9.9-2.1.8-3.2-.2-1.1-.8-1.5-1.4-.8-.7.9-.9 2.1-.8 3.2.2 1.1.8 1.5 1.4.8Z" fill="#fff" /><path d="M159.502 190.201c69.4-1.4 71.8-64.6 71.6-77.3-.2-12.8-8-39.3-27.7-56.5-15.6-13.6-35.6-18.9-59.5-16-35.4 4.4-50.8 26.1-57.5 43.6-4.4 11.9-6.5 24.5-6.1 37.1.1 4.9 3.2 22.4 14.9 38.8 9.9 13.9 28.6 30.3 62.1 30.3h2.2Zm-.002-.901c68.601-1.4 71.001-64 70.801-76.5-.2-12.5-7.9-38.8-27.4-55.8-12.7-11-28.301-16.5-46.601-16.5-4.1 0-8.2.3-12.2.8-35 4.3-50.2 25.8-56.8 43-4.4 11.8-6.4 24.3-6.1 36.8.2 6 4.1 23.2 14.8 38.3 9.8 13.8 28.3 29.9 61.4 29.9h2.1Z" fill="#EBEBEB" /><path d="M101.804 66.303c-44.2 46-9.1 89 3.9 101.6 12.7 12.2 67.9 42.4 108.8-10.1 40.9-52.5-11.7-98.5-11.7-98.5-7-6.6-56.8-39-101 7Z" fill="#EBEBEB" /><path d="M159.499 191.898c22.1-.1 39.7-7 52.2-20.6 22.3-24.2 20.9-60.8 20.9-61.1-.1-1.7-3.2-42-30.7-62.5-15.2-11.2-34.8-14.2-58.3-8.9-24.6 5.6-42.3 17.2-52.5 34.4-17 28.8-7.2 63.9-7 64.5 3.3 9.9 8.4 19.1 15.1 27.1 10.3 12.4 29.1 27 60.1 27l.2.1ZM165.098 37.1c-7.1.1-14.3.9-21.2 2.6-24.4 5.5-41.9 17-52 34.1-16.9 28.6-7 63.4-6.9 63.8 3.2 9.8 8.3 18.9 15 26.8 10 12.1 28.6 26.6 59.3 26.6h.3c21.4-.1 38.5-6.6 50.8-19.5 22.7-23.7 21.4-60.9 21.4-61.2-.1-1.7-3.2-41.6-30.4-61.9-10.1-7.6-22.3-11.3-36.3-11.3Z" fill="#EBEBEB" /><path d="M232.696 107.798c.7-24.8-8.4-43.4-27.1-55.3-25.9-16.5-60-13.8-60.1-13.7l-.5.1c-9.3 1.5-41 8.2-57.2 33.5-11 17.1-12.8 39.2-5.4 65.7 6.5 23.4 18.7 39.3 36.2 47.2 9.3 4.3 19.4 5.8 29.1 5.8 25.6 0 48.5-10.8 49.1-11 .4-.2 8.9-4 17.4-14.5 8-9.9 17.6-27.9 18.5-57.8Zm-19.094 57.101c7.9-9.8 17.5-27.6 18.3-57.2.6-21.2-6.1-37.9-19.9-49.6-25.5-21.4-65.301-18.6-66.201-18.5-.2.1-.4.1-.6.1-9.2 1.5-40.6 8.2-56.7 33.1-10.8 16.9-12.6 38.8-5.3 65.1 6.4 23.1 18.5 38.8 35.7 46.6 9.3 4.2 19.2 5.7 28.8 5.7 25.5 0 48.501-10.8 48.801-11 .1 0 8.6-3.7 17.1-14.3Z" fill="#EBEBEB" /><path d="M157.198 192.605c3.4 0 6.8-.2 10.2-.6 53.4-6.6 60.1-50.4 65.1-82.3l.3-1.8c2.5-16.3-8.2-38.1-26.2-52.9-13-10.7-40.6-26.6-79-12.7-33 11.9-43.2 36.7-46 55.4-1.8 12.7-1.1 25.6 1.9 38l.9-.2c-3-12.3-3.7-25.1-1.9-37.7 2.7-18.5 12.8-42.9 45.4-54.7 38-13.8 65.3 1.9 78.1 12.5 17.8 14.7 28.4 36.1 25.9 52.2l-.3 1.8c-4.9 31.7-11.7 75.1-64.4 81.6-27.4 3.4-47-7.6-58.6-17.4-15-12.8-23.4-29.2-23.7-36.1h-.9c.4 7.1 8.9 23.8 24.1 36.7 13.7 11.7 31.1 18.2 49.1 18.2Z" fill="#EBEBEB" /><path d="M203.097 123.1c.6-.3.4-1-.1-.8-5.9 2.1-13.2 2.4-18.4-1.6-4.3-3.3-5.9-9.1-5.1-14.3.4-2.7 1.4-5.4 2.9-7.7 1.9-2.9 4.3-5.2 6.7-7.6 4.5-4.5 9.7-9.9 9.5-16.8-.2-6-5.1-10.7-10.6-12.4-3.9-1.1-8.1-.9-11.9.5-.2-.3-.4-.6-.6-.8-3.3-4.1-8.7-6-13.8-6-5.9.1-11.6 2.1-16.2 5.9-1.1.9-2.1 1.9-3.1 3.1-.3.4.2.9.5.5 3.7-4.4 8.9-7.3 14.5-8.3 5.2-.9 10.8-.2 15 3 1.1.8 2.1 1.8 2.8 2.9-1.3.6-2.5 1.3-3.7 2.1-2.1 1.5-5.1 4.2-3.9 7.1 1 2.1 3.3 3.2 5.6 2.7 2.6-.7 4.1-3.5 4.3-6 .1-1.9-.3-3.8-1.2-5.5 3.6-1.3 7.6-1.5 11.3-.5 5.5 1.6 10.3 6.4 10.1 12.4-.1 3.2-1.5 6.3-3.3 8.9-1.8 2.6-4.2 5-6.5 7.3-2.4 2.2-4.5 4.7-6.4 7.4-1.4 2.2-2.4 4.7-2.8 7.3-.8 4.9.3 10.2 3.7 14 4.1 4.5 10.7 5.5 16.4 4.4 1.5-.3 2.9-.6 4.3-1.2Zm-27.299-59.9c-1.4.6-2.7 1.3-3.9 2.2-1.9 1.5-5 4.4-2.9 6.9.7.9 1.7 1.4 2.8 1.5 1.2.1 2.4-.3 3.3-1.2 1.9-1.8 2.3-4.7 1.6-7.2-.2-.8-.5-1.5-.9-2.2ZM269.1 153.3c-3.2-1.6-6.8-2.1-10.4-2.3-.9 0-1.9-.1-2.9-.1-.2 0-.4.2-.4.4s.2.4.4.4c3.7.1 7.6.2 11.1 1.5 3 1.1 5.7 3 7.3 5.8 1.7 3.1 1.6 6.7 1.1 10.1-.5 3.3-1.4 6.5-1.7 9.8-.3 2.9-.1 6.1 1.3 8.7 1.4 2.6 3.8 4.4 6.7 5.1 3 .7 6 .3 8.8-1 2.9-1.3 5.4-3.4 7.7-5.6 2.4-2.3 4.7-4.7 7.5-6.5 2.8-1.9 5.9-3.3 9.3-3.1.8 0 1.6.2 2.4.5.2 0 .4-.7 0-.9-6.7-2.1-12.9 2.7-17.4 7-4.5 4.3-9.4 9.4-16.1 9.1-3.1-.1-5.9-1.5-7.7-4-1.7-2.5-2-5.7-1.8-8.7.4-6.5 3.7-13.4 1.1-19.8-1.3-2.8-3.5-5.1-6.3-6.4ZM91.7 156c.2-.5.4-1 .5-1.5.4-1.3.4-2.7 0-4-.2-.7-.6-1.2-1.1-1.6-.6-.4-1.3-.3-1.7.3-.8 1.2-.1 3.1.5 4.3.5.9 1.1 1.7 1.8 2.5Zm1.004.804c-.2-.1-.3-.3-.5-.4.3-.6.5-1.2.7-1.9.4-1.4.4-2.9 0-4.3-.4-1.2-1.4-2.5-2.8-2.4-1.7.2-2 2.2-1.8 3.6.3 1.6 1.1 3.1 2.2 4.3.2.3.5.5.8.8-1 1.7-2.5 3.1-4.2 4-2.7 1.2-5.8 1.2-8.6.2-6.5-2.3-10.1-9.2-11.2-15.6-1-6-.1-12.2 2.7-17.7.3-.7.7-1.3 1.1-2 .2-.4-.3-.8-.6-.4-3.5 5.7-5 12.5-4.1 19.1.9 7 4.7 14.6 11.7 17.2 3 1.2 6.3 1.1 9.3-.1 1.9-.9 3.5-2.3 4.5-4.1 2 1.7 4.5 2.7 7.1 2.7 3.3-.1 6.4-1.6 8.5-4.2.5-.6.9-1.3 1.3-2 .2-.5-.5-.7-.7-.3-1.4 2.7-3.9 4.7-6.9 5.4-3 .7-6.1 0-8.5-1.9Zm-28.206-27.7c-.3.3 0 .9.4.6 2-1.6 4.1-3.2 6.2-4.6-.6 2.3-.9 4.7-1 7.2 0 .4.7.4.7 0 0-2.7.4-5.3 1.1-7.8.1-.2 0-.4-.2-.4-.1-.2-.2-.2-.3-.1-2.4 1.5-4.7 3.2-6.9 5.1ZM293.403 83.996c-.4-.3-.9-.5-1.4-.7-1.3-.4-2.7-.5-4-.2-.6.2-1.2.5-1.6 1-.4.5-.3 1.3.2 1.7 1.2.9 3.1.4 4.3-.2.9-.4 1.8-1 2.5-1.6Zm-7.9.903c.1 1.7 2.1 2.2 3.5 2 1.6-.2 3.1-.9 4.4-1.9.3-.2.6-.5.9-.7 1.7 1.1 2.9 2.7 3.7 4.5 1 2.8.8 5.9-.4 8.6-2.7 6.4-9.8 9.5-16.3 10.2-6.1.6-12.2-.7-17.5-3.8-.7-.4-1.3-.8-1.9-1.2-.4-.3-.9.3-.5.6 5.5 3.9 12.1 5.8 18.8 5.3 7.1-.5 14.9-3.7 17.9-10.5 1.4-2.9 1.5-6.2.5-9.3-.8-1.9-2.1-3.6-3.8-4.8 1.8-1.9 2.9-4.3 3.1-6.9.1-3.3-1.2-6.5-3.6-8.7-.6-.5-1.3-1-2-1.4-.5-.4-.8.3-.4.5 2.6 1.6 4.5 4.2 5 7.2s-.4 6.1-2.4 8.3c-.1.2-.3.3-.4.5-.6-.3-1.2-.6-1.8-.8-1.4-.5-2.9-.6-4.3-.3-1.2.3-2.6 1.2-2.5 2.6ZM264.9 109.5c.3.4.9 0 .6-.3-1.5-2.1-2.9-4.3-4.2-6.5 2.3.8 4.7 1.2 7.1 1.4.4 0 .5-.7 0-.7-2.6-.2-5.2-.8-7.7-1.7-.2 0-.4.1-.4.3v.2c1.4 2.5 2.9 5 4.6 7.3ZM275.098 222.3c-.3-.5-.6-.9-.9-1.3-.8-1-2-1.8-3.3-2.2-.6-.2-1.3-.2-1.9 0-.6.3-.9 1-.7 1.6.5 1.4 2.5 1.8 3.8 2 1.1.1 2.1.1 3-.1Zm-22.594 14.095c-5.6-2.5-10.2-6.8-13.1-12.1-.4-.7-.7-1.3-1-2-.2-.4-.9-.2-.7.2 2.8 6.1 7.6 11.1 13.6 14.1 6.3 3.2 14.7 4.3 20.8-.1 2.6-1.8 4.5-4.6 5.1-7.8.3-2.1 0-4.2-.9-6.1 2.5-.7 4.7-2.2 6.2-4.4 1.8-2.8 2.2-6.2 1.3-9.3-.2-.8-.6-1.5-1-2.2-.3-.3-.9.1-.7.5 1.5 2.7 1.7 5.9.7 8.8-1.1 2.8-3.4 5-6.3 5.9-.2.1-.4.1-.6.2-.3-.6-.7-1.1-1.2-1.6-.9-1.1-2.2-1.9-3.6-2.4-1.2-.4-2.9-.3-3.5 1-.8 1.5.7 2.9 2 3.5 1.5.6 3.1.8 4.8.6.4 0 .7-.1 1.1-.2.9 1.8 1.2 3.8.9 5.7-.6 2.9-2.2 5.5-4.7 7.2-5.6 4.1-13.2 3.3-19.2.5Zm-14.902-6.491c0 .4.7.5.7 0-.3-2.6-.4-5.2-.3-7.7 1.6 1.8 3.4 3.4 5.4 4.8.4.3.7-.3.4-.6-2.2-1.5-4.2-3.3-5.9-5.3-.2-.1-.4-.1-.5.1 0 0-.1 0-.1.1-.1 2.9 0 5.8.3 8.6Z" fill="currentColor" /><path d="M210.296 131.103c-.4-.5-.8-.9-1.2-1.3-1.1-1-2.5-1.7-4-1.9-.7-.1-1.5 0-2.1.3h-.1c-.7.3-.9 1.2-.5 1.8.8 1.5 3 1.6 4.5 1.6 1.1-.1 2.2-.3 3.4-.5Zm-.9-1.998c-1.2-1.1-2.7-1.8-4.3-2.1-1.3-.2-3.2.1-3.7 1.6-.6 1.8 1.3 3.1 2.7 3.5 1.8.4 3.6.4 5.3-.1.4-.1.8-.2 1.2-.4 1.3 1.8 1.9 4 1.9 6.2-.2 3.3-1.6 6.4-4 8.7-5.5 5.4-14.1 5.7-21.1 3.7-6.5-1.9-12.3-5.9-16.4-11.3-.5-.7-1-1.3-1.4-2.1-.3-.4-1-.1-.7.4 4 6.3 10.1 11 17.2 13.4 7.5 2.5 16.9 2.4 22.9-3.4 2.6-2.4 4.2-5.8 4.4-9.4 0-2.3-.7-4.6-2-6.5 2.7-1.1 4.8-3.2 6.1-5.8 1.5-3.3 1.5-7.2-.1-10.5-.4-.8-.9-1.6-1.4-2.3-.2-.4-.8.1-.5.6 2.1 2.7 2.8 6.2 2.1 9.5-.7 3.3-3 6.1-6 7.6-.2.1-.4.2-.7.3-.5-.6-1-1.1-1.5-1.6Zm5.504-9.5c-.1.4.6.6.7.1.4-2.41.4-4.8.2-7.21 1.9 1.71 3.7 3.6 5.4 5.5.3.3.7-.2.5-.5-1.9-2.2-3.9-4.2-6.1-6.09-.1-.1-.4-.1-.5 0-.1.09-.1.19-.1.3.3 2.59.3 5.29-.1 7.9ZM238.404 119.804c1.6 3.2 2.3 6.7 2.3 10.3-.2 3.7-1.3 7.4-3.2 10.6-3.5 6.2-9.6 10.5-16.2 13.1l-2.4.9c-.2.1-.2.3-.1.5.1.1.2.2.3.2 6.6-2.2 12.8-5.9 17.1-11.4 4.3-5.9 6.3-13.6 4.3-20.6-1.9-6.7-6.7-12-9.3-18.4-5.6-13.8-.2-29.6 12.6-37.2.7-.4 1.5-.8 2.3-1.2.4-.2.2-.9-.2-.7-13.1 6.3-20 20.9-16.7 35 .9 3.4 2.2 6.7 4.1 9.8 1.7 3 3.6 5.9 5.1 9.1ZM123 189.001c-2.9.3-5.9 0-8.7-.9-5.8-2-10.7-6.1-13.6-11.4-1.8-3.3-3-6.8-3.5-10.5-.1-.5-.8-.4-.7 0 1 6.6 3.9 13 8.9 17.6 4.4 4 10.2 6.2 16.1 6.1 4.2-.2 8.2-1.6 12-3.1.6-.3.4-1-.1-.8-3.3 1.3-6.8 2.6-10.4 3ZM276.797 194.001c-.8 2.8-2.3 5.4-4.2 7.7-4.1 4.6-9.7 7.5-15.8 8.1-3.7.4-7.4.1-11-.8-.5-.1-.7.6-.3.7 6.5 1.7 13.6 1.4 19.7-1.5 5.4-2.5 9.7-7 11.8-12.5 1.5-3.9 1.6-8.2 1.8-12.3.1-.5-.6-.6-.7-.1-.1 3.6-.2 7.3-1.3 10.7Z" fill="currentColor" /><path d="M207.499 90.705c-1.8 1.5-3.3 3.2-4.4 5.2-1.3 2.3-2.3 5.1-1.6 7.7.3 1.2.9 2.2 1.8 3 1.1.9 2.4 1.4 3.8 1.4 2.5.2 5-.6 7-2.2 1.9-1.6 3.1-4 3.1-6.5 0-2.4-1.1-4.7-2.9-6.2-1.4-1.2-3.1-2.2-4.9-2.9 3.6-2.5 7.8-4 12.1-4.5 5.5-.6 11.1-.4 16.6.8 5.6 1.1 11 2.9 16.1 5.5 1.3.6 2.5 1.3 3.7 2 .2.1.5.1.6-.2.3-.21.2-.51 0-.61-5.2-3.1-10.9-5.39-16.7-6.89-5.7-1.51-11.6-2.11-17.5-1.8-5.6.4-11.1 1.9-15.7 5.3l-.2.1c-2.5-.8-5.1-1.3-7.7-1.5-4.9-.6-9.8-.4-14.6.5-9.6 1.7-18.6 6.1-25.8 12.7-.9.8-1.7 1.6-2.5 2.5-.4.4.2 1.1.7.7 6.3-6.7 14.4-11.6 23.3-14 4.4-1.2 9-1.8 13.6-1.8 4.1 0 8.2.5 12.1 1.7Zm1.102.295c-.4.3-.8.7-1.2 1-2 1.8-3.6 4-4.6 6.5-.8 2.4-.9 5.3.9 7.2 1.8 1.9 5 1.8 7.3.9s4.1-2.7 4.9-5.1c.7-2.3.2-4.8-1.3-6.7-1.5-1.7-3.5-3-5.7-3.7l-.3-.1ZM217.004 54.8c-2 1-3.7 2.3-5.3 3.8-1.8 1.9-3.6 4.4-3.4 7.1.1 1.3.6 2.4 1.5 3.3 1 .8 2.3 1.1 3.6.8 2.6-.6 4.1-3 4.7-5.5.6-3.2.3-6.5-1.1-9.5Zm-64.502.804c5.2-1.5 9.9-4.3 14.6-6.9 2.4-1.4 4.8-2.6 7.4-3.7 2.7-1.2 5.6-2 8.5-2.6 5.7-1.1 11.6-1.1 17.2.2 5.1 1.2 10.2 3.5 13.7 7.5 1.1 1.2 2 2.6 2.7 4.1-3.3 1.7-6.3 4.1-8.1 7.4-1.2 2.2-1.5 5 0 7.1 1.4 2 4 2.4 6.2 1.4 2.3-1.1 3.6-3.5 4.1-5.9.5-2.5.4-5.2-.4-7.6-.2-.7-.4-1.4-.7-2.1l.3-.1c5.301-2.5 11.201-2.9 17.001-3h4.6c.2-.1.3-.3.3-.5-.2-.1-.3-.2-.5-.2-6.2-.1-12.7-.2-18.601 1.8-1.2.4-2.4.8-3.5 1.4-4.5-9.1-15.5-12.8-25.3-13-6.1-.1-12.1 1-17.6 3.4-5.3 2.2-10.1 5.4-15.3 7.9-5 2.5-10.3 4-15.9 3.8-5.4-.2-10.7-1.7-15.3-4.6-1.1-.7-2.2-1.5-3.3-2.3-.4-.3-.9.2-.5.5 7.9 6.5 18.5 8.7 28.4 6ZM128.6 55.7c.3.3.9-.1.6-.4-1.8-1.9-3.5-3.8-5-5.9 2.4.4 4.8.6 7.2.4.4 0 .4-.7-.1-.7-2.6.2-5.3 0-7.9-.6-.2 0-.4.1-.4.3v.3c1.7 2.3 3.6 4.5 5.6 6.6Z" fill="currentColor" /><path d="M290.8 174.205c5 1.7 11.6-1.9 13.9-6.4.8-1.5 1-3.4.3-5-.7-1.7-2.3-2.7-4.1-2.5-1.8.2-3.4 1.3-4.7 2.5-1.2 1.1-2.2 2.4-3 3.8-1.2 2.3-2.1 4.9-2.4 7.6Zm-3.795-62.607c2.7 2.3 5 5.2 6.6 8.4 1.7 3.5 2.6 7.3 2.6 11.2 0 3.9-.7 7.9-2 11.6-1.4 3.8-3.1 7.4-5.2 10.8-1.9 3.3-3.8 6.7-4.2 10.5-.3 3.1.4 6.6 2.7 8.9.8.8 1.7 1.4 2.7 1.9-.2 2.3-.2 4.7.1 7 2 15.2 14.5 26.9 29.9 28 1.8.1 3.7.1 5.5-.1.3-.2.3-1-.1-.9-7.3.7-14.6-1.1-20.8-5.1-6.1-4-10.6-10.1-12.7-17-1.2-3.8-1.7-7.8-1.3-11.8 4.8 1.5 10.8-1.5 13.7-5.4 1.1-1.4 1.8-3.1 1.8-4.9-.11-1.5-.7-3-1.9-4-2.9-2.4-6.8 0-9 2.2-3 3-4.6 7-5.1 11.2-.9-.4-1.7-1-2.4-1.7-2.2-2.4-2.8-5.9-2.3-9 .6-3.8 2.6-7.1 4.5-10.4 3.8-6.5 6.6-13.6 6.7-21.3.1-3.7-.6-7.3-2-10.7-1.3-3.1-3.3-5.9-5.6-8.3-4.9-5.1-11.41-8.5-17.9-10.9a75.99 75.99 0 0 0-24.01-4.8c-1.99-.1-4 0-6 .1-.49 0-.49.8 0 .7 8.6-.4 17.2.7 25.41 3.2 7.3 2.2 14.5 5.5 20.3 10.6Zm31.593 94.7c-.4-.1-.7.5-.2.7 2.5.6 5 1.4 7.4 2.2-2.2.9-4.3 2.1-6.3 3.5-.4.3.1.8.4.5 2.2-1.6 4.5-2.8 7-3.8.2-.1.2-.3.1-.5 0 .1-.1 0-.2 0-2.7-1-5.4-1.9-8.2-2.6ZM301.404 246.904c.2 0 0-.6-.5-.5-3.8 1.1-8.8.6-11.3-2.7-2.3-2.9-1.7-6.9 0-9.9.9-1.6 2.1-3 3.5-4.1 1.7-1.3 3.5-2.4 5.3-3.6 1.6-1.2 3.1-2.5 3.9-4.4.8-1.8.7-3.8-.1-5.5-1.9-4.3-6.7-5.7-11-6.2-5-.5-10.1-.4-15.1-.8-4.6-.4-9.2-1.2-13.3-3.2-3.7-1.8-7.1-4.5-9.1-8.2-2-3.9-2.4-8.4-1.2-12.6 1.3-4.5 3.9-8.5 7.6-11.5.9-.8 1.8-1.5 2.8-2.1.4-.3 0-.8-.4-.6-4.1 2.6-7.3 6.3-9.5 10.6-1.9 4-2.4 8.5-1.3 12.8 2.2 8.7 11 13.4 19.2 14.8 5 .9 10.2.9 15.3 1.1 4.7.2 10.6.2 14 4 1.4 1.5 2.1 3.5 1.9 5.5-.3 2.1-1.8 3.7-3.4 5-1.7 1.3-3.6 2.4-5.4 3.7-1.5 1.1-2.8 2.4-3.8 4-1.9 2.9-2.8 6.8-1.2 10 1.8 3.8 6.2 5.2 10.1 4.9 1-.1 2-.3 3-.5Z" fill="currentColor" /><path d="M255.101 171.9c-.4.1-.4.8.1.7 2.5-.5 5.1-.9 7.7-1.2-1.6 1.8-3 3.8-4.1 5.9-.2.4.4.7.6.3 1.2-2.3 2.8-4.5 4.6-6.4.1-.2.1-.4-.1-.5-.2 0-.2-.1-.3-.1-2.9.2-5.7.7-8.5 1.3ZM99.7 69.3c4.6-.5 9.3 1.9 12.6 5 .3.3.8-.2.5-.5-.8-.8-1.7-1.5-2.6-2.1-3.6-2.5-8.4-4.1-12.8-2.7-4 1.3-6.5 5-7.2 9.1-.8 4.4.5 8.8 2.8 12.6 2.7 4.4 6.5 7.9 10 11.699 4 4.3 7.7 8.7 11.2 13.4 3.1 4.2 6.1 8.8 7.2 14.1.9 4.5.2 9.7-2.9 13.3-2.9 3.4-7.5 4.6-11.8 4.2-4.7-.4-8.9-2.8-12.3-6-3.7-3.4-6.5-7.8-9.1-12.1-.3-.5-.9-.1-.7.3.7 1.1 1.3 2.1 2 3.2 2.7 4.1 5.8 8.2 9.8 11.2 3.6 2.8 8 4.3 12.5 4.2 4.3-.1 8.5-2 11-5.6 2.6-3.8 3.1-8.9 2.1-13.5-1.2-5.1-4.2-9.7-7.3-13.9-3.4-4.6-7.2-9.1-11.1-13.3-3.6-3.7-7.4-7.3-10.1-11.8-2.3-3.8-3.5-8.4-2.4-12.8.4-2 1.4-3.8 2.8-5.3 1.6-1.5 3.6-2.5 5.8-2.7ZM269.6 89.305c5.7 4 12.6 5.8 19.3 7.4 6.9 1.7 13.9 3.2 20 6.8 5.5 3.2 10.2 8.3 11.4 14.8.6 3 .3 6-.9 8.8-1.3 3-3.5 5.4-5.9 7.6-4.7 4.4-10.4 8.3-12.3 14.8-.9 2.9-.6 6.1.9 8.7.7 1.3 1.7 2.399 2.8 3.3 1.3.9 2.5 1.7 3.9 2.4 1.4.8 2.4 2 3 3.5.5 1.3.6 2.799.3 4.1-.5 3.199-2.7 5.8-5 7.8-1.2 1-2.5 2-3.8 2.899-.4.301 0 .901.4.601 4.5-3.2 10-7.601 9.1-14-.2-1.3-.7-2.5-1.4-3.5-1-1.1-2.2-2.101-3.6-2.8-1.5-.7-2.8-1.7-3.9-2.9-1-1.1-1.7-2.5-2.1-3.9-2-6.9 3.6-12.9 8.3-17 4.6-4 9.8-8.3 10.9-14.7 1-5.801-1.5-11.801-5.4-16-4.5-4.901-11-7.7-17.3-9.5-7.1-2.1-14.5-3.3-21.4-6-5.7-2.3-11.1-5.9-14.3-11.2-7.2-11.8 1.5-25.7 11.6-32.5 1.4-.9 2.9-1.7 4.4-2.4.4-.2.2-.9-.2-.7-5.9 2.5-11.1 7.2-14.6 12.5-3.3 5.1-5.4 11.3-4.1 17.4 1.2 5.7 5.1 10.4 9.9 13.7Z" fill="currentColor" /><path d="M270.901 46.4c-.4.1-.4.8.1.7 2.5-.5 5.1-.9 7.7-1.1-1.6 1.8-3 3.8-4.2 5.9-.2.4.4.7.6.3 1.3-2.3 2.8-4.5 4.7-6.4.1-.2.1-.4-.1-.5-.2-.1-.3-.1-.3-.1-2.9.2-5.7.6-8.5 1.2ZM181.698 129.399c4.6 0 8.8-2.8 10.4-7.1.5-1.4.9-2.9 1.2-4.4.4-1.5 1.2-2.9 2.5-3.8 1.1-.8 2.5-1.3 3.9-1.5 3.2-.4 6.3.9 8.9 2.6 1.3.9 2.6 1.8 3.9 2.8.4.3.8-.3.5-.6-4.5-3.5-10.3-7.4-16.2-4.7-1.2.5-2.2 1.3-2.9 2.4-.8 1.3-1.4 2.7-1.6 4.2-.3 1.6-.9 3.2-1.7 4.6-.8 1.3-1.9 2.3-3.1 3.2-6 3.9-13.4.2-18.7-3.1-5.2-3.2-10.8-7-17.2-6.2-5.9.7-10.8 4.9-13.8 9.8-3.4 5.8-4.2 12.7-4.2 19.3 0 7.4 1.1 14.8.4 22.3-.6 6.1-2.4 12.3-6.6 16.9-9.2 10.3-25.1 6-34.4-1.7-1.3-1.1-2.5-2.2-3.5-3.5-.3-.4-.9.1-.6.4 4.1 5 10.1 8.5 16.2 10.4 5.8 1.8 12.4 1.9 17.8-1.1 5.2-2.8 8.5-7.9 10.3-13.4 2.1-6.7 1.9-13.7 1.6-20.6-.4-7-.9-14.2.7-21.2 1.5-6.3 5-12.2 10.8-15.2 2.6-1.4 5.7-2 8.7-1.7 3.2.4 6.2 1.8 9 3.4 5.5 3.2 11 7.6 17.7 7.5Z" fill="currentColor" /><path d="M96 182.598c-2.6-.6-5.1-1.4-7.5-2.7-.2-.1-.4 0-.5.1 0 .1 0 .3.1.4 1 2.7 2.3 5.3 3.6 7.8.2.4.9.1.7-.2-1.2-2.3-2.4-4.6-3.3-7 2.2 1 4.5 1.8 6.8 2.3.2 0 .3-.2.3-.4 0-.1-.1-.3-.2-.3ZM150.9 94.798c-3.8-6.4-11.9-7.7-18.2-10.1-3.3-1.3-6.7-3-8.9-5.9-2.2-2.9-2.7-6.8-1.4-10.2.6-1.7 1.7-3.1 3.1-4.2.4-.3-.2-.8-.5-.5-2.7 2.3-4.2 5.8-4 9.3.2 3.5 2.2 6.5 4.9 8.7 5.9 4.6 13.9 4.9 20.1 9 3.2 2.1 5.5 5.2 5.7 9.1.2 4-1.5 7.9-4 10.9-2.8 3.3-6.6 5.6-10.3 7.7-3.7 2.1-7.6 4.1-11.2 6.7-6.8 4.8-12.8 12-12.9 20.8 0 7.2 5.2 14.7 12.9 15 4.5.2 8.7-1.9 12.2-4.5 3.7-2.7 7.1-5.9 11.1-8.2 3.7-2.1 8-3.8 12.3-4 3.9-.2 8.1 1 9.8 4.8 1.7 3.6.7 7.9-1 11.3-1.9 3.9-4.8 7.3-6.5 11.3-1.5 3.6-2.2 8.1-.1 11.7 1.8 2.7 4.8 4.3 8.1 4.2 1.6 0 3.1-.4 4.4-1.2.3-.2-.1-.9-.5-.6-3 1.7-6.6 1.5-9.4-.5-3.3-2.4-3.9-6.7-2.9-10.5 2-8 10-13.7 9.3-22.5-.3-3.6-2.2-6.7-5.7-8-3.6-1.4-7.7-.8-11.3.3-4.1 1.3-8 3.3-11.4 6-3.6 2.7-7 5.9-11.1 7.9-3.8 1.8-8.2 2.4-12.1.4-3.2-1.8-5.6-4.7-6.7-8.3-2.7-8.4 2.4-16.9 8.5-22.3 3.4-2.8 7-5.2 10.9-7.2 3.7-2 7.4-4 10.7-6.6 3.1-2.4 5.5-5.6 6.9-9.3 1.2-3.5 1.1-7.3-.8-10.5ZM266.895 118.002c1.7-.5 3.5-.6 5.2-.3.5.1.5-.6.1-.7-3.5-.7-7.2.3-9.8 2.8-2.6 2.4-3.7 5.8-3.6 9.3.2 7.5 4.9 13.9 5.7 21.3.4 3.7-.6 7.5-3.4 10.2-2.9 2.6-7.1 3.8-11 3.8-4.3-.1-8.5-1.6-12.5-3.1-4-1.6-8-3.3-12.2-4.5-8-2.2-17.4-2.4-24.2 3.1-5.6 4.5-8.1 13.3-3.5 19.4 2.7 3.6 7 5.5 11.1 6.7 4.4 1.2 9 1.8 13.3 3.5 4 1.5 8 3.8 10.9 7 2.6 2.9 4.3 6.9 2.5 10.6-1.8 3.6-5.7 5.5-9.4 6.4-4.2 1-8.7.9-12.9 2.1-3.8 1.1-7.7 3.4-9.1 7.3-1 3.1-.3 6.5 1.8 9 1 1.2 2.3 2.1 3.7 2.7.2.1.4 0 .5-.3-.1-.6-.2-.8-.5-.9-3.2-1.2-5.3-4.2-5.5-7.6-.3-4.1 2.8-7.2 6.3-8.9 7.4-3.5 16.9-.9 23.3-6.9 2.6-2.5 3.8-5.9 2.7-9.4-1.2-3.7-4.2-6.5-7.3-8.6-3.6-2.4-7.6-4.1-11.9-5.1-4.4-1.1-9-1.7-13.2-3.7-3.8-1.8-7.1-4.8-7.9-9.1-.6-3.6.1-7.4 2.2-10.4 4.8-7.3 14.7-8.8 22.6-7.4 4.3.9 8.4 2.2 12.5 3.9 3.9 1.6 7.8 3.3 11.9 4.2 3.8.9 7.8.7 11.5-.5 3.4-1.3 6.4-3.7 7.7-7.2 2.6-6.9-1.5-14-3.6-20.5-1.1-3.4-1.9-7.1-1.1-10.7.9-3.6 3.6-6.4 7.1-7.5ZM110.604 220.504c3.8 4.1 9.2 6.5 14.7 7.2 6.9.9 13.9-.7 20.3-3.2 1.6-.6 3.2-1.3 4.7-2 .4-.2 0-.8-.4-.7-6.8 3.1-14.1 5.6-21.7 5.4-6-.2-12.1-2.4-16.4-6.6-2.2-2.1-3.8-4.7-4.7-7.6-1-3-1.4-6.2-2.4-9.3-1.6-5.8-7-9.8-13-9.5-6 .2-11.2 4.4-13.5 9.8-2.3 5.2-2 12 2.1 16.3.5.5 1 .9 1.5 1.3-.8 2.6-.9 5.3-.5 7.9 1.1 6 5.3 10.9 11 12.9 1.3.5 2.7.8 4.2.9.5 0 .5-.7 0-.7-5.8-.6-10.8-4.1-13.3-9.4-1.5-3.5-1.8-7.5-.7-11.1 3.5 2.1 8.4 2 11.5-.7 2.1-1.8 3.5-5.1 1.4-7.4-.9-1-2.2-1.6-3.5-1.7-1.5-.1-3 .3-4.2 1.1-2.6 1.6-4.4 4.4-5.5 7.2-.1.1-.1.3-.1.4-.3-.2-.5-.4-.8-.7-4.2-3.9-4.6-10.6-2.5-15.7 2.2-5.2 7.2-9.4 13-9.6 2.8-.1 5.5.7 7.8 2.3 2.5 1.7 3.9 4.4 4.7 7.2.9 2.8 1.3 5.9 2.2 8.8.8 2.7 2.2 5.1 4.1 7.2Zm-28.004.9c3.6 2.1 8.8 1.9 11.6-1.3.9-1.2 1.7-2.8 1.3-4.3-.4-1.3-1.4-2.3-2.7-2.7-2.9-1-5.8 1.1-7.6 3.3-1.1 1.5-2 3.1-2.6 5Z" fill="currentColor" /><path d="M91.203 237.597c-.3-.3-.8.2-.5.5 2.1 1.6 4 3.2 5.9 5-2.4-.1-4.8.2-7.2.7-.4.1-.3.8.2.7 2.6-.6 5.2-.8 7.9-.7.2 0 .3-.2.3-.4-.1-.1-.1-.1-.1-.2-2.1-2-4.2-3.9-6.5-5.6ZM239.904 176.903c6 .6 11.6-3.2 13.4-9 1.9-5.7-.3-12-4.5-16.1-4.1-4-10.6-6.1-16-3.7-.6.3-1.2.6-1.7 1-2.1-1.6-4.6-2.7-7.2-3.2-6-1-12 1.2-15.9 5.9-.9 1.1-1.7 2.3-2.3 3.6-.2.4.5.7.7.3 2.5-5.2 7.6-8.8 13.4-9.3 3.8-.2 7.6.9 10.7 3.2-3.2 2.6-4.8 7.1-3.4 11.1.9 2.6 3.6 5 6.5 3.9 1.2-.5 2.3-1.5 2.8-2.7.6-1.4.8-2.9.4-4.4-.6-3-2.6-5.7-4.9-7.7l-.3-.3c.3-.2.6-.4.9-.5 5.1-2.6 11.5-.7 15.6 3.1 4.1 3.9 6.4 10 4.5 15.5-.9 2.6-2.6 4.9-4.9 6.5-2.5 1.7-5.5 2.1-8.4 2-2.9-.2-6-.8-9-.9-2.8-.1-5.6.4-8.2 1.4-5.1 2.2-9.3 6.4-11.8 11.3-3.2 6.1-4.1 13.3-3.9 20.1 0 1.7.1 3.4.3 5.1 0 .5.8.4.7-.1-.6-7.4-.4-15.2 2.4-22.2 2.2-5.6 6.4-10.5 11.9-13.2 2.7-1.3 5.7-1.9 8.7-1.7 3.2.1 6.3.9 9.5 1Zm-8.701-26.803c-3.2 2.6-4.9 7.5-2.8 11.3.7 1.3 1.9 2.6 3.5 2.7 1.3.1 2.6-.5 3.4-1.6 1.9-2.5 1-5.9-.5-8.3-1-1.6-2.2-3-3.6-4.1Z" fill="currentColor" /><path d="M212.9 152.395c.4-.2.1-.9-.3-.6-2.2 1.4-4.4 2.7-6.8 3.8.9-2.2 1.5-4.6 1.8-7 .1-.4-.6-.5-.7-.1-.4 2.6-1.1 5.2-2.1 7.6-.1.2 0 .4.2.4.2.2.3.2.4.1 2.6-1.2 5.1-2.6 7.5-4.2Z" fill="currentColor" /><path d="M64.4 52.8c0-7-5.7-12.7-12.7-12.7-7 0-12.7 5.6-12.7 12.6s5.7 12.7 12.7 12.7c7 0 12.7-5.6 12.7-12.6Zm-22.4 0c0 5.4 4.3 9.7 9.7 9.7 5.4 0 9.7-4.3 9.7-9.7 0-5.4-4.4-9.7-9.7-9.7-5.3 0-9.7 4.3-9.7 9.7Z" fill="#03D5B7" /><path d="m351.898 160.898 12.6-3.9c.6-.2.9-.8.7-1.3-.2-.6-.8-.9-1.3-.7l-12.6 3.9c-.6.2-.9.8-.7 1.3.2.4.6.7 1 .7h.3ZM343.503 144.697c1.2.6 2.4.9 3.6.9.8 0 1.6-.1 2.2-.4 1.9-.7 3.6-2 4.5-3.8.9-1.8 1.2-3.9.5-5.8s-2-3.6-3.8-4.5c-1.8-.9-3.8-1.1-5.8-.5-1.9.7-3.6 2-4.5 3.8-.9 1.8-1.2 3.9-.5 5.8s2 3.5 3.8 4.5Zm-1.407-9.397c-.7 1.3-.8 2.9-.4 4.3s1.4 2.6 2.8 3.3c1.3.7 2.9.8 4.3.4s2.6-1.4 3.3-2.8c.7-1.3.8-2.9.4-4.3s-1.4-2.6-2.8-3.3c-.7-.4-1.6-.7-2.6-.7-.6 0-1.1.1-1.7.3-1.4.4-2.6 1.4-3.3 2.8ZM318.297 126.997l8.2-34.1c.2-.5-.2-1.1-.7-1.2-.5-.2-1.1.2-1.2.7l-8.2 34.1c-.2.5.2 1.1.7 1.2h.2c.4 0 .8-.3 1-.7ZM64.695 188.595l8.8-9.9c.4-.4.3-1-.1-1.4-.4-.4-1-.3-1.4.1l-8.8 9.9c-.4.4-.3 1 .1 1.4.2.2.5.3.7.3.3 0 .6-.1.7-.4ZM53.801 204.1c2.1-.2 3.9-1.1 5.3-2.6 1.3-1.5 2-3.5 1.9-5.5-.2-2.1-1.1-3.9-2.6-5.3-1.5-1.3-3.5-2-5.5-1.9-4.2.2-7.5 3.9-7.2 8.1.2 2.1 1.1 3.9 2.6 5.3 1.4 1.2 3.2 1.9 5.1 1.9h.4Zm-.703-13.3c-3.1.2-5.5 2.9-5.3 6 .1 1.5.8 2.9 1.9 3.9 1.1 1 2.6 1.5 4.1 1.4 1.5-.1 2.9-.8 3.9-1.9 1-1.1 1.5-2.6 1.4-4.1-.1-1.5-.8-2.9-1.9-3.9-1.1-.9-2.4-1.4-3.8-1.4h-.3Z" fill="#FFC412" /><path d="M42.005 258.701c-.1.4.1.8.5 1 .2.1.3.1.5.1s.4 0 .7-.2l26.9-22.5c.3-.3.4-.7.3-1.1-.2-.4-.5-.7-.9-.7l-24.7-2.2c-.2-.1-.5 0-.7.2-.2.2-.4.4-.4.7l-2.2 24.7ZM44.2 256.5l23.2-19.4-21.3-1.9-1.9 21.3Z" fill="#03D5B7" /><path d="M132.097 118.801c.3-.5.1-1.1-.4-1.4-3.2-1.7-9.1-1.2-9.4-1.2-.5.1-1 .6-.9 1.1.1.5.5 1 1.1.9 1.6-.2 6.1-.3 8.3.9.2.1.3.1.5.1.4 0 .7-.2.8-.4ZM100.504 122.2c2.8-4.9 6.9-5.2 7.1-5.2.5 0 .9-.5.9-1-.1-.5-.6-.9-1.1-.9-.2 0-5.3.4-8.7 6.2-.2.5-.1 1.1.4 1.4.2.1.3.1.5.1.3 0 .7-.2.9-.6ZM96.097 118.903c.6-2.2 2.4-5.2 2.4-5.2.2-.5.1-1.1-.4-1.4-.5-.2-1.1-.1-1.4.4-.1.2-1.9 3.3-2.6 5.8-.2.5.2 1.1.7 1.2h.3c.4 0 .8-.3 1-.8ZM368.4 263.1c0-.6-.4-1-1-1H211.6c-.6 0-1 .4-1 1s.4 1 1 1h155.8c.5 0 1-.4 1-1ZM203.1 263.1c0-.6-.4-1-1-1h-14.5c-.6 0-1 .4-1 1s.4 1 1 1h14.5c.6 0 1-.4 1-1Z" fill="currentColor" /><path d="M127.499 62.199c-1 2.8-1.7 5.7-2.2 8.5-2.2 2.5-3.9 5.4-4.9 8.6-1.3 4.4-1.1 12.1.1 20.2-1.6-.9-3-.7-4.5-.4-9.6 1.8-6.9 15 1.5 17.5.9.4 6.9 2.2 7.8 2.1 1.2 3.3 2.6 6 4.1 8-.3 8.1-.7 17-.5 17.3 5.3 8.7 18.2 19 25.4 17 6.9-2 4.5-21.4 4.4-22.1-.1-1.4-.1-2.8 0-4.2 4.1-1.5 16.3-5.9 18.8-10.7 3.5-7-7.3-48.2-9-52.7-2.1-5.6-4.5-9.8-6.9-11.6-9.7-7-24.6-5.3-34.1 2.5Z" fill="#fff" /><path d="M159.7 139.005v-3.5c4.9-1.8 16.2-6 18.7-11 3.8-7.7-7.6-49.7-9-53.5-1.5-4.2-4.1-9.7-7.2-12-9.8-7-25-5.9-35.3 2.6-.1.1-.2.2-.3.4-1 2.7-1.7 5.5-2.2 8.4-2.3 2.6-4 5.6-4.9 8.8-1.1 4.1-1.2 10.9-.1 19-1.2-.3-2.4-.1-3.5.1-4.1.8-6.7 3.7-6.9 7.7-.3 4.5 2.8 10.1 8.2 11.7.8.4 5.4 1.8 7.5 2.1 1.2 3.1 2.5 5.5 3.8 7.4l-.2 4.2c-.5 12.7-.5 12.8-.2 13.3 4.8 7.7 16.1 17.7 24.2 17.7.8 0 1.6-.1 2.2-.2 7-2.1 5.8-18.3 5.2-23.2Zm-38.001-39.302c.1.4-.1.7-.4 1-.3.2-.7.2-1 .1-1.3-.7-2.5-.6-3.9-.3-3.2.6-5.1 2.7-5.3 5.8-.2 3.7 2.3 8.4 6.8 9.7h.1c1 .4 6.5 2 7.4 2 .4 0 .9.3 1 .6 1.3 3.3 2.6 6 3.9 7.8.1.2.2.4.2.6l-.2 4.6c-.2 5.1-.4 11-.4 12.2 5.5 8.8 17.9 18.2 24.1 16.4 4.7-1.3 4.5-13.8 3.7-21v-.1c-.1-1.3-.1-2.7 0-4.3 0-.4.3-.8.7-.9l.6-.2c4.2-1.6 15.5-5.7 17.6-10 3.3-6.5-7.3-47.3-9-51.9-2.1-5.6-4.4-9.5-6.5-11.1-8.9-6.4-23.1-5.3-32.6 2.3-.9 2.6-1.6 5.3-2.1 8.1 0 .2-.1.4-.2.5-2.2 2.5-3.8 5.2-4.7 8.3-1.2 4-1.1 11.3.2 19.8Z" fill="currentColor" /><path d="M149.8 101.295c3.3-.5 2-7.8-1.1-7.3-3.6.6-2.2 7.8 1.1 7.3ZM167.598 97.405c3.3-.9 1.7-8.1-1.9-7.1-3 .8-1.3 8 1.9 7.1ZM157.9 119.7c.6-.1.9-.7.7-1.2 0-.2-1.1-3.6-6.9-4.3-.5 0-1 .4-1.1.9 0 .5.4 1 .9 1.1 4.4.6 5.3 2.8 5.3 2.9.2.4.6.7 1 .7.1 0 .2-.1.1-.1Z" fill="currentColor" /><path d="M162.1 109.8s5.1-.7 4.9-3.3c-.2-2.6-5.3-2.1-5.3-2.1l-2.4-15.3 2.8 20.7Z" fill="#fff" /><path d="M167.804 106.504c-.1-.8-.4-1.4-1-1.9-1.2-1.1-3.3-1.2-4.5-1.2l-2.2-14.4c0-.5-.6-.9-1.1-.8-.5 0-.9.6-.8 1.1l2.4 15.3c.1.5.6.9 1.1.8 1.1-.1 3.2 0 4 .7.2.1.3.3.3.5.1 1.2-2.8 2-4.1 2.2-.5 0-.9.6-.8 1.1.1.5.5.9 1 .9 0 0 .1 0-.1.1.6-.1 6.1-1 5.8-4.4ZM142.003 92.1c2.8-4 7.8-4.1 7.9-4.1.6 0 1-.4 1-1s-.5-1-1-1c-.2 0-6.1.1-9.5 4.9-.4.5-.3 1.1.2 1.4.2.1.4.2.6.2.3 0 .6-.1.8-.4ZM169.9 85.1c0-.5-.4-1-1-1 0 0-3.5-.1-6-2.5-.4-.4-1-.4-1.4 0-.4.4-.4 1 0 1.4 3.1 2.9 7.1 3 7.3 3 .6 0 1-.4 1.1-.9Z" fill="currentColor" /><path d="M151.998 108.698c5.4-2.4 7.7-8.7 5.3-14-1.2-2.5-3.3-4.5-5.9-5.5-2.7-1-5.5-.9-8.1.3-2.5 1.2-4.5 3.3-5.5 5.9-1 2.7-.9 5.5.3 8.1 1.7 3.9 5.6 6.2 9.6 6.2 1.5 0 3-.3 4.3-1ZM144.204 91.4c-4.3 2-6.2 7-4.3 11.3 2 4.3 7 6.2 11.3 4.3 4.3-2 6.2-7 4.3-11.3-.9-2.1-2.6-3.7-4.8-4.5-.9-.4-2-.6-3-.6-1.2 0-2.4.3-3.5.8ZM171.704 104.302c4.6-.7 7.6-5.6 6.8-11-.4-2.6-1.6-4.9-3.4-6.5-1.8-1.6-4.1-2.3-6.3-2-2.2.3-4.1 1.7-5.4 3.8-1.2 2.1-1.7 4.6-1.3 7.2.7 5 4.5 8.6 8.6 8.6.4 0 .7 0 1-.1ZM169.103 86.9c-1.6.2-3.1 1.3-4 2.9-1 1.7-1.4 3.7-1.1 5.8.6 4.3 4 7.4 7.4 6.8 3.4-.5 5.7-4.4 5.1-8.7-.3-2.1-1.3-4-2.7-5.3-1.1-1.1-2.5-1.6-3.9-1.6-.3 0-.5 0-.8.1Z" fill="#FFC412" /><path d="M138 101.1c.6 0 1-.4 1-1s-.5-1-1-1l-13.1.4c-.6 0-1 .4-1 1s.5 1 1 1l13.1-.4ZM157.395 97.502c.1-.1 2.5-2.1 5.2-.9.5.2 1.1 0 1.3-.5.2-.5 0-1.1-.5-1.3-3.8-1.8-7.2 1.1-7.3 1.2-.4.4-.5 1-.1 1.4.2.2.5.3.8.3.3 0 .5-.1.6-.2Z" fill="#FFC412" /><path d="M168.601 36.9c-3-1.5-6.9.7-8.6 5.1l-.2-.2c-2.5-1.8-5.9-1.3-7.7 1.2-1.9 2.4-3.1 5.3-3.7 8.3 0 0-10.2-.7-18.3.7-1.1.2-2.3.3-3.4.4-1.1.1-2.2.4-3.3.9-4.8 2.2-7.2 7.4-5.9 12.3-.9.1-1.8.4-2.7.8-4.9 2.2-6.6 7.2-5.8 12.7 1.2 10.1 9.1 31.301 16.7 29.701 5.8-1.2 3.4-23.5 3.4-23.5 6.3-.9 11.2-5.8 12.2-12 0 0 20.6 9.4 31.4-2.3 9-9.7 1.4-31.4-4.1-34.1Z" fill="currentColor" /><path d="M158.803 64.096c-1.5 1.6 14.9 9.9 14.9 9.9l.7 10.6 1.7.7 2.1 1.3 39 35-47.5 28.3s16.5 32.6 17.5 33.3l54.4-44.8c4.8-4.1 7.4-10.2 7.1-16.5-.4-6.7-4.2-12.8-10.1-16.1l-51.7-28.8-10.3-9.6-2.9-7.2c.5-2.4-1.1-4.8-3.5-5.2-.1 0-.2 0-.3-.1l-8.8-.6s-4.2-.2-3.8 2.4c.1.5.4.9.9 1l-.9 1.5c-.1 2.4 3 2.7 3 2.7l7.3.7 1.5 3.9s-8.8-4-10.3-2.4Z" fill="#fff" /><path d="m177.495 87.398 38 34-46.3 27.6c-.5.3-.6.8-.4 1.3 6.2 12.2 16.8 32.8 17.7 33.6.1.1.4.2.6.2.2 0 .5-.1.6-.2l54.4-44.8c5.1-4.2 7.9-10.7 7.5-17.3-.4-7.1-4.4-13.4-10.6-16.9l-51.3-28.7-10.1-9.3-2.7-6.7c.4-2.8-1.5-5.5-4.3-6.1-.2-.1-.3-.1-.5-.1l-8.9-.6c-.3 0-2.9-.1-4.2 1.3-.6.6-.8 1.4-.7 2.3 0 .4.2.8.5 1.1l-.4.7c-.1.2-.1.3-.1.5-.1 2.1 1.6 3.1 3 3.5-.5.1-.9.3-1.2.6-.3.3-.4.8-.3 1.2.4 2.2 9.3 7.1 15 10l.6 10.1c0 .4.2.8.6.9l1.6.6 1.9 1.2ZM175.6 67.8l-2.9-7.2c-.1-.2-.1-.4-.1-.6.4-1.9-.8-3.7-2.7-4.1h-.2l-8.8-.6c-.6 0-2.1.1-2.6.6-.1.1-.2.3-.2.7 0 .1.1.2.2.2.3.1.5.3.6.6.1.3.1.6-.1.9l-.7 1.3c.1 1.2 1.9 1.4 2.1 1.4l7.4.7c.4 0 .7.3.8.6l1.5 3.9c.1.4 0 .8-.3 1.1-.3.3-.7.3-1.1.2-3.5-1.6-7.1-2.7-8.6-2.8 1.7 1.7 8.2 5.4 14.1 8.4.3.2.5.5.5.8l.6 10 1.1.4c.1 0 .1.1.2.1l2.1 1.3.1.1 39 35c.2.2.4.5.3.8 0 .3-.2.6-.5.8L171 150.2c5.8 11.4 14.1 27.6 16.4 31.5l53.5-44.1c4.6-3.8 7.2-9.6 6.8-15.6-.4-6.4-4-12.2-9.6-15.3l-51.7-28.8c-.1 0-.1-.1-.2-.1l-10.3-9.6c-.1-.1-.2-.2-.3-.4Zm-8.3-3.1-.4-1.1-3.7-.3c1.3.4 2.8.9 4.1 1.4Z" fill="currentColor" /><path d="m113.702 65.5-1.1.7-8.9 6.8s-43.9 33.2-52.7 42.5c-4.1 4.3-5.6 15.7-.6 20.8 13 13.3 39.3 37.2 39.3 37.2l22-25.1-30.8-27.3 25.9-26 .1.1 16.7-17.2 2.7.8c1.4.2 2.9-.2 4-1.2 1-.9 2-1.9 2.8-2.9.6-.8 1.9-2.9 2.1-3.2.6-.6 1.4-1.1 2.2-1.5 0 0 13.6-.2 14.7-.8 1-.6 2.3-2.6-1-3.2l-12.9-2 7.5-3.8 3.6-.1c-.5-2.3-2.4-3.9-4.7-4-4-.3-4.7.6-4.7.6s-2-4.2-3-4.5c-1.5-.5-3.1-.7-4.7-.8-1.3.5-2.4 1.2-3.4 2.2 0 0-4.8-2.6-5.9-1-1.1 2.1-2 4.3-2.6 6.5l-6.6 6.4Z" fill="#fff" /><path d="m82.398 121 24.7-24.8c.2-.1.4-.2.5-.3l16.3-16.8 2.1.6h.1c1.8.3 3.6-.2 4.8-1.4 1-.9 2-2 2.9-3.1.4-.4.9-1.2 1.3-1.9.2-.4.6-1 .7-1.1.5-.5 1.1-.9 1.7-1.2 6.3-.1 14-.4 15-1 .9-.6 1.8-1.7 1.6-2.9-.2-.8-.8-1.7-3-2.1l-9.8-1.6 4.5-2.3 3.4-.1c.3 0 .6-.2.8-.4.2-.2.3-.5.2-.8-.5-2.7-2.9-4.7-5.6-4.8-2.1-.2-3.5 0-4.3.2-1-1.9-2.2-3.8-3.2-4.1-1.6-.5-3.3-.8-5-.8-.2 0-.3.1-.4.1-1.2.4-2.2 1.1-3.2 1.9-1.8-.8-5.3-2.2-6.6-.3-.1 0-.1.1-.1.1-1.1 2.1-1.9 4.3-2.6 6.5l-6.3 6.1-1 .6c0 .1-.1.1-.1.1l-8.6 6.7c-.1 0-.1.1-.2.1-1.8 1.3-44 33.3-52.9 42.5-4.3 4.5-6.3 16.5-.6 22.2 12.9 13.2 39.1 37.1 39.4 37.3.2.2.5.3.7.3.4 0 .6-.2.8-.4l22-25.1c.3-.4.3-1-.1-1.4l-29.9-26.6Zm55.001-52c6.3-.1 13.4-.4 14.3-.7.3-.2.6-.7.6-.8 0 0-.2-.3-1.3-.5l-13-2.1c-.4-.1-.8-.4-.8-.8-.1-.4.2-.9.5-1l7.5-3.8c.1-.1.3-.1.4-.1l2.2-.1c-.7-1.2-1.9-2-3.3-2.1-2.9-.2-3.8.2-4 .3-.2.2-.5.3-.8.3-.3 0-.6-.3-.8-.6-.9-1.9-2-3.7-2.5-4-1.4-.4-2.8-.7-4.2-.7-1.1.4-2.1 1.1-2.9 1.9-.3.3-.8.4-1.2.2-2.1-1.1-4.2-1.6-4.6-1.3-1 2-1.9 4.1-2.5 6.2 0 .2-.1.3-.3.4l-6.6 6.4c-.1.1-.1.1-.2.1l-.9.8-8.7 6.8c-.1 0-.2.1-.3.1-3.2 2.4-44 33.4-52.4 42.2-3.6 3.9-5.3 14.7-.6 19.5 11.6 11.9 34.1 32.5 38.5 36.5l20.7-23.5-30-26.7c-.2-.2-.3-.4-.3-.7 0-.3.1-.5.3-.7l25.9-26c.1-.1.3-.2.4-.3l16.4-16.8c.3-.3.6-.4 1-.3l2.7.8c1.1.2 2.2-.2 3.1-1 1-.9 1.9-1.8 2.7-2.8.3-.4.8-1.1 1.2-1.8.6-1 .8-1.3 1-1.5.7-.7 1.5-1.2 2.4-1.7.1-.1.3-.1.4-.1Z" fill="currentColor" /><path d="M192.1 206c1.2 16.7 6.5 20.9 6.5 20.9l-97.2 23.7-3.2-55.3L93 190l-24.6-26.4c11.6-8.9 18-21.2 18.4-38.5l13.5 13c2.3 2.2 5.2 3.7 8.3 4.4 3.8.8 7.8 1 11.8.7l6.2-.5c11.1 13.3 31.7 15.5 35-.8l4.7.6c2.3.3 4.7-.5 6.4-2.1l1-.9h4.2l24.9-9.8s16.3 21.1 17.6 31.4L191.2 184l.9 22Z" fill="#fff" /><path d="M67.4 163.196c0 .3.1.6.3.8l24.6 26.5 5.2 5.3c.4.4 1 .4 1.4 0 .4-.4.4-1 0-1.4l-5.2-5.2-23.8-25.6c11.1-8.9 17-20.8 17.8-36.3l11.8 11.6c2.4 2.4 5.5 4 8.8 4.7 4 .8 8 1.1 12.1.7l5.6-.5c7 8.1 17.5 12.2 25.8 10.1 5.3-1.4 9-5.2 10.4-10.9l3.8.5c2.6.3 5.3-.5 7.2-2.4l.7-.7h3.8c.2 0 .3-.1.4-.1l24.2-9.5c2.6 3.4 15.3 20.6 16.9 29.8l-28.7 22.6c-.3.2-.4.5-.4.8l.9 22c.9 12.2 4 18 5.7 20.3l-95.6 23.3c-.5.1-.8.7-.7 1.2.1.5.5.8 1 .8h.2l97.2-23.7c.3-.1.6-.4.7-.8.1-.4 0-.8-.3-1 0-.1-5-4.3-6.1-20.2l-.8-21.5 28.8-22.6c.3-.2.4-.6.4-.9-1.3-10.5-17.1-31-17.8-31.9-.3-.3-.8-.5-1.2-.3l-24.7 9.7h-4c-.3 0-.5.1-.7.3l-1 .9c-1.5 1.4-3.5 2.1-5.6 1.8l-4.7-.6c-.5-.1-1 .3-1.1.8-1 5.4-4.2 9-9.1 10.2-7.6 1.9-17.7-2.3-24.1-9.9-.3-.3-.6-.4-.9-.4l-6.2.5c-3.8.3-7.7.1-11.5-.7-3-.7-5.7-2.1-7.9-4.2l-13.5-13c-.3-.3-.7-.4-1.1-.2-.3.1-.6.5-.6.9-.4 16.3-6.3 28.7-18 37.7-.2.2-.4.4-.4.7Z" fill="currentColor" /><path d="M103.905 268.198c.2.4.6.7 1 .7h.2c.1 0 15.4-4.5 17.9-23.3 0-.5-.4-1-.9-1.1-.5 0-1 .4-1.1.9-1.9 15-12.3 20.1-15.5 21.3-12.9-45.2-3.9-83-3.8-83.4.2-.5-.2-1.1-.7-1.2-.5-.2-1.1.2-1.2.7-.1.4-9.4 39.2 4.1 85.4ZM143.104 250.405c.6 0 1-.6.9-1.1l-2.3-16.1c0-.6-.6-1-1.1-.9-.6 0-1 .6-.9 1.1l2.3 16.1c.1.5.5.9 1 .9h.1ZM152.005 249.205c.6 0 1-.6.9-1.1l-2.3-16.7c0-.6-.6-1-1.1-.9-.6 0-1 .6-.9 1.1l2.3 16.7c.1.5.5.9 1 .9h.1Z" fill="currentColor" /><path d="m99.6 243.801 94.401-23.9c.5-.1.8-.7.7-1.2-.1-.5-.7-.8-1.2-.7l-94.4 23.9c-.5.1-.8.7-.7 1.2.1.5.6.8 1 .8.1 0 .2 0 .2-.1Z" fill="currentColor" /><path d="M368.198 43.5c.6-3.2-.6-6.4-3.1-8.5-2.5-2.1-5.9-2.7-9-1.6l-49.2 17.9c-1.5.5-2.8 1.5-3.8 2.7-3.2 3.8-2.8 9.6 1.1 12.8l40.1 33.599c1.2 1 2.7 1.7 4.2 2 5 .9 9.8-2.3 10.6-7.3l9.1-51.6Zm-2.603-.404c.4-2.3-.4-4.5-2.2-6-1.8-1.5-4.2-1.9-6.3-1.1l-49.2 17.9c-3.3 1.2-5 4.9-3.8 8.2.4 1.1 1.1 2 1.9 2.7l40.1 33.7c2.7 2.2 6.7 1.9 9-.8.7-.9 1.2-1.9 1.4-3l9.1-51.6Z" fill="#E0E0E0" /><path d="m348.5 49.3-12.2 16.3 3.3 2.7 13.8-14.9-4.9-4.1ZM335.602 69.902l-.1-.1c-1.4-1.2-3.5-1-4.7.4l-.1.1c-1.1 1.5-.9 3.6.6 4.8l.1.1c1.4 1.2 3.6 1 4.7-.5 0-.2.1-.2.1-.2 1.1-1.4.8-3.5-.6-4.6Z" fill="#E0E0E0" /><path d="M261.3 248s3.2 9.9-6.8 15.1h47.3s-11.3-3.9-8.4-15.1h49.1c2.3 0 4.1-1.8 4.1-4.1v-93.1c.1-2.3-1.8-4.1-4.1-4.1H210.7c-2.3 0-4.1 1.8-4.1 4.1v93.1c0 2.3 1.8 4.1 4.1 4.1h50.6Z" fill="currentColor" /><path d="M278.203 238c-1-.8-2.4-.8-3.3 0-1.1.9-1.2 2.5-.3 3.6 0 .1.1.1.2.2 1 .8 2.4.8 3.3 0 1.1-.9 1.2-2.5.3-3.6-.1 0-.2-.1-.2-.2Z" fill="#455A64" /><path d="M208.8 231v-79.7c0-1.6 1.4-2.9 3-2.9h129.6c1.6 0 3 1.3 3 3V231H208.8Z" fill="#03D5B7" /><path d="M208.8 151.6v3.7h135.6V151c0-1.5-1.2-2.6-2.6-2.6H211.9c-1.7 0-3.1 1.4-3.1 3.2Z" fill="#EBEBEB" /><path d="M213.1 151c-.6 0-1 .4-1 1s.5 1 1 1c.6 0 1-.5 1-1 0-.6-.4-1-1-1ZM215.9 151c-.6 0-1 .4-1 1s.5 1 1 1c.6 0 1-.5 1-1 0-.6-.4-1-1-1Z" fill="#E0E0E0" /><path d="M218.6 151c-.6 0-1 .4-1 1s.5 1 1 1c.6 0 1-.5 1-1 0-.6-.4-1-1-1Z" fill="#03D5B7" /><path d="M277.1 248c-23.1 0-41.9 0-41.9.1s18.7.1 41.9.1c23.1 0 41.9-.1 41.9-.1 0-.1-18.8-.1-41.9-.1Z" fill="#455A64" /><path d="M245.4 182.8c-.8 0-1.4.1-2 .1l1-7.3h10.8v-3.9h-14.1l-1.9 14.9c1-.1 2.1-.2 3.7-.2 5.5 0 8.2 2.4 8.2 6.4s-3.1 6.4-6.6 6.4c-2.5 0-4.8-.9-6-1.6l-1.1 3.6c1.4.9 4.2 1.7 7.2 1.7 6.7 0 11.2-4.6 11.2-10.5 0-6.3-4.8-9.6-10.4-9.6ZM268.5 175.1c3.9 0 5.3 2.6 5.3 5.6 0 4.6-3.6 8.8-10.1 15.5l-3.2 3.4v2.9h18.8v-4h-12.4v-.1l2.5-2.6c5.4-5.4 9.2-10.1 9.2-15.7 0-4.6-2.8-8.9-9.1-8.9-3.4 0-6.4 1.3-8.3 2.9l1.4 3.4c1.4-1.1 3.5-2.4 5.9-2.4ZM293 203c6.3 0 10.1-5.7 10.1-16.1 0-9.3-3.3-15.6-9.8-15.6-6.4 0-10.2 5.9-10.2 15.9 0 9.4 3.4 15.7 9.9 15.8Zm.2-28c-3.1 0-5.2 4.3-5.2 12 0 7.6 1.9 12.1 5.1 12.1 3.9 0 5.2-5.7 5.2-12.1 0-7.3-1.6-12-5.1-12ZM231.995 202.4c.9 0 1.7-.5 2.2-1.2.5-.7.5-1.7 0-2.5l-7.2-12.6c-.4-.8-1.3-1.3-2.19-1.3-.91 0-1.7.5-2.2 1.2l-7.3 12.5c-.51.8-.51 1.7 0 2.5.4.8 1.3 1.3 2.19 1.3l14.5.1Zm-7.1-16.8c-.6 0-1.2.4-1.5.9l-7.3 12.5c-.5.8-.2 1.9.6 2.4.3.1.6.2.9.2l14.5.1c1 0 1.7-.8 1.7-1.8 0-.3 0-.6-.2-.9l-7.2-12.6c-.3-.5-.9-.8-1.5-.8Z" fill="#fff" /><path d="M225.7 190.8h-1.8l.3 5.6h1.2l.3-5.6ZM223.8 198.404c0 .5.4.9 1 .9.5 0 .9-.4.9-.9v-.1c0-.5-.4-.9-.9-.8-.6 0-1 .4-1 .9ZM338.795 198.7l-7.2-12.6c-.39-.8-1.3-1.3-2.19-1.3-.9 0-1.7.5-2.2 1.2l-7.3 12.5c-.51.8-.51 1.7 0 2.5.39.8 1.3 1.3 2.19 1.3l14.5.1c.9 0 1.7-.5 2.2-1.2.5-.7.5-1.7 0-2.5Zm-10.799-12.2-7.3 12.5c-.5.8-.2 1.9.6 2.4.3.1.6.2.9.2l14.5.1c1 0 1.7-.8 1.7-1.8 0-.3 0-.6-.2-.9l-7.2-12.6c-.3-.5-.9-.8-1.5-.8s-1.2.4-1.5.9Z" fill="#fff" /><path d="M330.3 190.8h-1.8l.3 5.6h1.2l.3-5.6ZM328.4 198.404c0 .5.4.9 1 .9.5 0 .9-.4.9-.9v-.1c0-.5-.4-.9-.9-.8-.6 0-1 .4-1 .9ZM240.7 215c1.9 0 3.2-1.5 3.2-3.9s-1.3-3.9-3.2-3.9c-1.9 0-3.2 1.5-3.2 3.9s1.4 3.9 3.2 3.9Zm0-7c-1.4 0-2.3 1.2-2.3 3.1 0 1.9.9 3.2 2.3 3.2 1.4-.1 2.3-1.3 2.3-3.2 0-1.9-.9-3.1-2.3-3.1ZM252 214.8v-4.4c0-.6.1-1.5.1-2.2l-.6 1.7-1.5 4.1h-.6l-1.5-4.1-.6-1.7c0 .7.1 1.5.1 2.2v4.4h-.8v-7.5h1l1.5 4.1c.2.5.4 1.1.5 1.6h.1c.2-.5.3-1.1.5-1.6l1.5-4.1h1v7.5h-.7ZM258.8 208c.8 0 1.3.3 1.7.7l.5-.6c-.4-.4-1.1-.9-2.1-.9-2 0-3.4 1.5-3.4 3.9s1.3 3.9 3.2 3.9c1 0 1.8-.4 2.3-.9V211h-2.4v.7h1.6v2.1c-.3.3-.8.5-1.4.5-1.7 0-2.6-1.3-2.6-3.2 0-1.9 1-3.1 2.6-3.1ZM266.4 214.7c0 .1-.1.2-.2.2h-1.1c.1.2.2.5.2.7.7 0 1.1 0 1.4-.1.2-.1.3-.3.3-.7v-8.2h-2.9v3.7c0 1.5-.1 3.5-.8 5 .2 0 .5.2.6.3.5-1 .7-2.3.8-3.5h1.7v2.6Zm0-5h-1.7V211.5h1.7v-1.8Zm-1.7-.6h1.7v-1.8h-1.7v1.8Zm5.6.2c.8 0 1.3 0 1.6-.1.3-.1.4-.2.4-.6v-2H268v9h.6v-4.9h.3c.3 1 .8 2.1 1.4 2.9-.5.6-1 1.1-1.6 1.4.1.1.3.3.4.5.6-.3 1.1-.7 1.6-1.3.5.6 1.1 1.1 1.7 1.4.1-.1.2-.4.4-.5-.7-.3-1.3-.8-1.8-1.4.7-1 1.2-2.1 1.5-3.5l-.4-.2h-3.5v-2.8h3v1.3c0 .1 0 .2-.2.2h-1.3c.1.2.2.4.2.6Zm.4 3.9c.5-.7.9-1.5 1.1-2.4h-2.3c.3.9.7 1.7 1.2 2.4ZM278.9 206.3l-.7-.1c-.5.9-1.6 1.9-3 2.7.1.1.3.3.4.5.6-.3 1.1-.7 1.5-1 .4.5 1 1 1.6 1.3-1.3.4-2.7.7-4 .8.1.1.3.4.3.6 1.5-.1 3.1-.5 4.5-1 1.2.5 2.6.8 4.2.9.1-.2.3-.4.4-.6-1.4 0-2.7-.2-3.8-.6 1.1-.6 2.1-1.3 2.7-2.2l-.5-.4h-4.3c.3-.3.5-.6.7-.9Zm-3.8 9.4c2.1-.5 3.4-1.3 4-3h3.2c-.1 1.5-.4 2.1-.6 2.3-.1.1-.2.1-.4.1s-.9 0-1.5-.1c.1.2.2.4.2.6h1.5c.4 0 .6-.1.8-.3.3-.3.6-1.1.8-3v-.3.1h-3.9c.1-.3.1-.7.2-1l-.7-.1c0 .3-.1.7-.2 1h-3.1v.7h2.9c-.6 1.3-1.7 2.1-3.6 2.4.2.2.3.4.4.6Zm4.4-6.2c1-.4 1.9-.9 2.5-1.6h-4.2l-.2.2c.4.6 1.1 1 1.9 1.4ZM292.4 211.1c.12.1.25.2.38.3h2.32v-.6h-1.8l.2-.3c-.3-.3-1-.6-1.5-.8l-.4.4c.4.1 1 .4 1.3.6h-3c.3-.4.5-.8.7-1.1l-.7-.1c-.2.4-.4.8-.7 1.1H286v.6h2.6c-.7.6-1.6 1.2-2.8 1.6.2.1.3.3.4.5.3-.1.6-.2.8-.3v2.5h.6v-.3h1.8v.3h.7v-3.1h-2.2c.7-.4 1.2-.8 1.7-1.3h2c.5.5 1.1.9 1.8 1.3h-2.2v3.1h.6v-.3h1.9v.3h.7v-2.6c.2.1.4.1.6.2.1-.1.2-.4.4-.5-.94-.26-1.87-.66-2.62-1.2h-.38v-.3Zm.38.3c-.13-.1-.26-.2-.38-.3v.3h.38Zm-6.08-4.6v2.7h3.2v-2.7h-3.2Zm.8.5v1.5h1.9v-1.5h-1.9Zm.2 5.8v1.6h1.8v-1.6h-1.8Zm6.8-6.3h-3.3v2.7h3.3v-2.7Zm-.8 6.3h-1.9v1.6h1.9v-1.6Zm-1.8-5.8v1.5h2v-1.5h-2ZM298.7 213.2h2.1v1.5c0 .1-.1.2-.2.2h-1c.1.2.2.4.2.6.6 0 1.1 0 1.3-.1.2-.1.3-.3.3-.6h.1v-5.5h-3.3v2.2c0 1.1-.1 2.6-.9 3.7.2 0 .4.3.5.4.5-.7.8-1.6.9-2.4Zm-.6-6.6v2h8v-2h-.7v1.4h-3v-1.8h-.7v1.8h-2.9v-1.4h-.7Zm2.7 5h-2c0 .3 0 .7-.1 1.1h2.1v-1.1Zm-2-.6h2v-1.1h-2v1.1Zm2.9 4.3c.2 0 .4.3.5.4.5-.7.8-1.6.9-2.4h2.3v1.5c0 .2-.1.2-.2.2h-1.1c.1.2.2.4.2.6.7 0 1.1 0 1.4-.1.2-.1.3-.3.3-.7h.1v-5.4h-3.5v2.5c0 1.1-.1 2.4-.9 3.4Zm3.7-3.8h-2.2v1.2h2.2v-1.2Zm-2.2-.5h2.2v-1.1h-2.2v1.1ZM312.7 214.8h-1.9c.1.2.3.5.3.7 1.1 0 1.7 0 2.1-.1.4-.1.5-.3.5-.8v-4.5c1.3-.6 2.7-1.7 3.6-2.7l-.4-.5H309.3v.7h6.7c-.8.8-2 1.6-3 2.1v4.9c0 .1-.1.2-.3.2Z" fill="#fff" /><path d="M356.5 234.3h-22.2l3.6 29.9c4.9 2.6 10.1 2.3 15.4 0l3.2-29.9Z" fill="#FDFEFF" /><path d="M357.4 234.4c0-.3-.2-.6-.3-.8-.1-.2-.4-.3-.7-.3h-22.2c-.2 0-.5.1-.7.3-.1.2-.2.5-.2.8l3.6 29.9c0 .4.2.6.5.8 2.5 1.3 5.1 1.9 7.8 1.9s5.6-.6 8.4-1.9c.3-.1.6-.4.6-.8l3.2-29.9Zm-5.1 29.2 3.1-28.3h-20l3.4 28.3c4.1 2 8.6 2 13.5 0Z" fill="currentColor" /><path d="M333.4 232.401v3.9c8.4 1.5 16.4 1.6 24.1 0v-3.9l-2.4.1-.6-2.1-16.8-2.1c-.5-.1-.9.2-1 .6l-1.2 3.5h-2.1Z" fill="#FDFEFF" /><path d="M358.3 232.397c0-.3-.1-.5-.3-.7-.2-.2-.4-.3-.7-.3h-1.5l-.4-1.4c-.1-.4-.4-.7-.8-.7l-16.8-2.1c-.9-.1-1.8.4-2.1 1.3l-1 2.9h-1.4c-.6 0-1 .4-1 1v3.9c0 .5.3.9.8 1 4.4.8 8.6 1.2 12.7 1.2s8-.4 11.7-1.2c.5-.1.8-.5.8-1v-3.9Zm-24 3.103c7.9 1.3 15.3 1.3 22.1 0v-2h-1.3c-.4 0-.8-.3-1-.7l-.5-1.5-16.1-2-1.2 3.5c-.1.4-.5.7-.9.7h-1.1v2Z" fill="currentColor" /><path d="M356 245.2c-7 .8-14 .8-21 0l.9 9.1c6.8.8 13.2.9 19 0l1.1-9.1Z" fill="#FDFEFF" /><path d="M335 244.1c-.3 0-.6.1-.8.3-.2.2-.3.5-.3.8l.9 9.1c.1.5.4.8.9.9 3.8.5 7.2.7 10.4.7s6.2-.2 8.9-.7c.4-.1.7-.5.8-.9l1.1-9.1c0-.3-.1-.6-.3-.8-.2-.2-.5-.3-.8-.3-6.9.8-13.9.8-20.8 0Zm19 9.3.9-7.1c-6.2.6-12.5.6-18.8 0l.7 7.1c6.6.7 12.2.7 17.2 0Z" fill="currentColor" /><path d="m343.396 250.7.6 1.3c.4.9 1.5 1.4 2.5 1.1l-2.3-5c-.9.5-1.2 1.6-.8 2.6ZM346.9 252.802c.9-.5 1.2-1.6.8-2.6l-.6-1.3c-.5-.8-1.5-1.3-2.5-1l2.3 4.9Z" fill="#03D5B7" /></g></svg>
  </div>
</template>


================================================
FILE: src/locales/en-US.ts
================================================
export default {
  common: {
    add: 'Add',
    addSuccess: 'Add Success',
    edit: 'Edit',
    editSuccess: 'Edit Success',
    delete: 'Delete',
    deleteSuccess: 'Delete Success',
    save: 'Save',
    saveSuccess: 'Save Success',
    reset: 'Reset',
    action: 'Action',
    export: 'Export',
    exportSuccess: 'Export Success',
    import: 'Import',
    importSuccess: 'Import Success',
    clear: 'Clear',
    clearSuccess: 'Clear Success',
    yes: 'Yes',
    no: 'No',
    confirm: 'Confirm',
    download: 'Download',
    noData: 'No Data',
    wrong: 'Something went wrong, please try again later.',
    success: 'Success',
    failed: 'Failed',
    verify: 'Verify',
    unauthorizedTips: 'Unauthorized, please verify first.',
    stopResponding: 'Stop Responding',
  },
  chat: {
    newChatButton: 'New Chat',
    newChatTitle: 'New Chat',
    placeholder: 'Ask me anything...(Shift + Enter = line break, "/" to trigger prompts)',
    placeholderMobile: 'Ask me anything...',
    copy: 'Copy',
    copied: 'Copied',
    copyCode: 'Copy Code',
    copyFailed: 'Copy Failed',
    clearChat: 'Clear Chat',
    clearChatConfirm: 'Are you sure to clear this chat?',
    exportImage: 'Export Image',
    exportImageConfirm: 'Are you sure to export this chat to png?',
    exportSuccess: 'Export Success',
    exportFailed: 'Export Failed',
    usingContext: 'Context Mode',
    turnOnContext: 'In the current mode, sending messages will carry previous chat records.',
    turnOffContext: 'In the current mode, sending messages will not carry previous chat records.',
    deleteMessage: 'Delete Message',
    deleteMessageConfirm: 'Are you sure to delete this message?',
    deleteHistoryConfirm: 'Are you sure to clear this history?',
    clearHistoryConfirm: 'Are you sure to clear chat history?',
    preview: 'Preview',
    showRawText: 'Show as raw text',
    thinking: 'Thinking...',
  },
  setting: {
    setting: 'Setting',
    general: 'General',
    advanced: 'Advanced',
    config: 'Config',
    avatarLink: 'Avatar Link',
    name: 'Name',
    description: 'Description',
    role: 'Role',
    temperature: 'Temperature',
    top_p: 'Top_p',
    resetUserInfo: 'Reset UserInfo',
    chatHistory: 'ChatHistory',
    theme: 'Theme',
    language: 'Language',
    api: 'API',
    reverseProxy: 'Reverse Proxy',
    timeout: 'Timeout',
    socks: 'Socks',
    httpsProxy: 'HTTPS Proxy',
    balance: 'API Balance',
    monthlyUsage: 'Monthly Usage',
    openSource: 'This project is open sourced at',
    freeMIT: 'free and based on the MIT license, without any form of paid behavior!',
    stars: 'If you find this project helpful, please give me a Star on GitHub or give a little sponsorship, thank you!',
  },
  store: {
    siderButton: 'Prompt Store',
    local: 'Local',
    online: 'Online',
    title: 'Title',
    description: 'Description',
    clearStoreConfirm: 'Whether to clear the data?',
    importPlaceholder: 'Please paste the JSON data here',
    addRepeatTitleTips: 'Title duplicate, please re-enter',
    addRepeatContentTips: 'Content duplicate: {msg}, please re-enter',
    editRepeatTitleTips: 'Title conflict, please revise',
    editRepeatContentTips: 'Content conflict {msg} , please re-modify',
    importError: 'Key value mismatch',
    importRepeatTitle: 'Title repeatedly skipped: {msg}',
    importRepeatContent: 'Content is repeatedly skipped: {msg}',
    onlineImportWarning: 'Note: Please check the JSON file source!',
    downloadError: 'Please check the network status and JSON file validity',
  },
}


================================================
FILE: src/locales/es-ES.ts
================================================
export default {
  common: {
    add: 'Agregar',
    addSuccess: 'Agregado con éxito',
    edit: 'Editar',
    editSuccess: 'Edición exitosa',
    delete: 'Borrar',
    deleteSuccess: 'Borrado con éxito',
    save: 'Guardar',
    saveSuccess: 'Guardado con éxito',
    reset: 'Reiniciar',
    action: 'Acción',
    export: 'Exportar',
    exportSuccess: 'Exportación exitosa',
    import: 'Importar',
    importSuccess: 'Importación exitosa',
    clear: 'Limpiar',
    clearSuccess: 'Limpieza exitosa',
    yes: 'Sí',
    no: 'No',
    confirm: 'Confirmar',
    download: 'Descargar',
    noData: 'Sin datos',
    wrong: 'Algo salió mal, inténtalo de nuevo más tarde.',
    success: 'Exitoso',
    failed: 'Fallido',
    verify: 'Verificar',
    unauthorizedTips: 'No autorizado, por favor verifique primero.',
    stopResponding: 'No responde',
  },
  chat: {
    newChatButton: 'Nueva conversación',
    newChatTitle: 'Nueva conversación',
    placeholder: 'Pregúntame lo que sea...(Shift + Enter = salto de línea, "/" para activar avisos)',
    placeholderMobile: 'Pregúntame lo que sea...',
    copy: 'Copiar',
    copied: 'Copiado',
    copyCode: 'Copiar código',
    copyFailed: 'Copia fallida',
    clearChat: 'Limpiar chat',
    clearChatConfirm: '¿Estás seguro de borrar este chat?',
    exportImage: 'Exportar imagen',
    exportImageConfirm: '¿Estás seguro de exportar este chat a png?',
    exportSuccess: 'Exportación exitosa',
    exportFailed: 'Exportación fallida',
    usingContext: 'Modo de contexto',
    turnOnContext: 'En el modo actual, el envío de mensajes llevará registros de chat anteriores.',
    turnOffContext: 'En el modo actual, el envío de mensajes no incluirá registros de conversaciones anteriores.',
    deleteMessage: 'Borrar mensaje',
    deleteMessageConfirm: '¿Estás seguro de eliminar este mensaje?',
    deleteHistoryConfirm: '¿Estás seguro de borrar esta historia?',
    clearHistoryConfirm: '¿Estás seguro de borrar el historial de chat?',
    preview: 'Avance',
    showRawText: 'Mostrar como texto sin formato',
  },
  setting: {
    setting: 'Configuración',
    general: 'General',
    advanced: 'Avanzado',
    config: 'Configurar',
    avatarLink: 'Enlace de avatar',
    name: 'Nombre',
    description: 'Descripción',
    role: 'Rol',
    temperature: 'Temperatura',
    top_p: 'Top_p',
    resetUserInfo: 'Restablecer información de usuario',
    chatHistory: 'Historial de chat',
    theme: 'Tema',
    language: 'Idioma',
    api: 'API',
    reverseProxy: 'Reverse Proxy',
    timeout: 'Tiempo de espera',
    socks: 'Socks',
    httpsProxy: 'HTTPS Proxy',
    balance: 'Saldo de API',
    monthlyUsage: 'Uso mensual de API',
    openSource: 'Este proyecto es de código abierto en',
    freeMIT: 'gratis y basado en la licencia MIT, ¡sin ningún tipo de comportamiento de pago!',
    stars: 'Si encuentras este proyecto útil, por favor dame una Estrella en GitHub o da un pequeño patrocinio, ¡gracias!',
  },
  store: {
    siderButton: 'Tienda rápida',
    local: 'Local',
    online: 'En línea',
    title: 'Título',
    description: 'Descripción',
    clearStoreConfirm: '¿Estás seguro de borrar los datos?',
    importPlaceholder: 'Pegue los datos JSON aquí',
    addRepeatTitleTips: 'Título duplicado, vuelva a ingresar',
    addRepeatContentTips: 'Contenido duplicado: {msg}, por favor vuelva a entrar',
    editRepeatTitleTips: 'Conflicto de título, revíselo',
    editRepeatContentTips: 'Conflicto de contenido {msg} , por favor vuelva a modificar',
    importError: 'Discrepancia de valor clave',
    importRepeatTitle: 'Título saltado repetidamente: {msg}',
    importRepeatContent: 'El contenido se salta repetidamente: {msg}',
    onlineImportWarning: 'Nota: ¡Compruebe la fuente del archivo JSON!',
    downloadError: 'Verifique el estado de la red y la validez del archivo JSON',
  },
}


================================================
FILE: src/locales/index.ts
================================================
import type { App } from 'vue'
import { createI18n } from 'vue-i18n'
import enUS from './en-US'
import esES from './es-ES'
import koKR from './ko-KR'
import ruRU from './ru-RU'
import viVN from './vi-VN'
import zhCN from './zh-CN'
import zhTW from './zh-TW'
import { useAppStoreWithOut } from '@/store/modules/app'
import type { Language } from '@/store/modules/app/helper'

const appStore = useAppStoreWithOut()

const defaultLocale = appStore.language || 'zh-CN'

const i18n = createI18n({
  locale: defaultLocale,
  fallbackLocale: 'en-US',
  allowComposition: true,
  messages: {
    'en-US': enUS,
    'es-ES': esES,
    'ko-KR': koKR,
    'ru-RU': ruRU,
    'vi-VN': viVN,
    'zh-CN': zhCN,
    'zh-TW': zhTW,
  },
})

export const t = i18n.global.t

export function setLocale(locale: Language) {
  i18n.global.locale = locale
}

export function setupI18n(app: App) {
  app.use(i18n)
}

export default i18n


================================================
FILE: src/locales/ko-KR.ts
================================================
export default {
  common: {
    add: '추가',
    addSuccess: '추가 성공',
    edit: '편집',
    editSuccess: '편집 성공',
    delete: '삭제',
    deleteSuccess: '삭제 성공',
    save: '저장',
    saveSuccess: '저장 성공',
    reset: '초기화',
    action: '액션',
    export: '내보내기',
    exportSuccess: '내보내기 성공',
    import: '가져오기',
    importSuccess: '가져오기 성공',
    clear: '비우기',
    clearSuccess: '비우기 성공',
    yes: '예',
    no: '아니오',
    confirm: '확인',
    download: '다운로드',
    noData: '데이터 없음',
    wrong: '문제가 발생했습니다. 나중에 다시 시도하십시오.',
    success: '성공',
    failed: '실패',
    verify: '검증',
    unauthorizedTips: '인증되지 않았습니다. 먼저 확인하십시오.',
    stopResponding: '응답 중지',
  },
  chat: {
    newChatButton: '새로운 채팅',
    newChatTitle: '새로운 채팅',
    placeholder: '무엇이든 물어보세요...(Shift + Enter = 줄바꿈, "/"를 눌러서 힌트를 보세요)',
    placeholderMobile: '무엇이든 물어보세요...',
    copy: '복사',
    copied: '복사됨',
    copyCode: '코드 복사',
    copyFailed: '복사 실패',
    clearChat: '채팅 비우기',
    clearChatConfirm: '이 채팅을 비우시겠습니까?',
    exportImage: '이미지 내보내기',
    exportImageConfirm: '이 채팅을 png로 내보내시겠습니까?',
    exportSuccess: '내보내기 성공',
    exportFailed: '내보내기 실패',
    usingContext: '컨텍스트 모드',
    turnOnContext: '현재 모드에서는 이전 대화 기록을 포함하여 메시지를 보낼 수 있습니다.',
    turnOffContext: '현재 모드에서는 이전 대화 기록을 포함하지 않고 메시지를 보낼 수 있습니다.',
    deleteMessage: '메시지 삭제',
    deleteMessageConfirm: '이 메시지를 삭제하시겠습니까?',
    deleteHistoryConfirm: '이 기록을 삭제하시겠습니까?',
    clearHistoryConfirm: '채팅 기록을 삭제하시겠습니까?',
    preview: '미리보기',
    showRawText: '원본 텍스트로 보기',
    thinking: '생각 중...',
  },
  setting: {
    setting: '설정',
    general: '일반',
    advanced: '고급',
    config: '설정',
    avatarLink: '아바타 링크',
    name: '이름',
    description: '설명',
    role: '역할',
    temperature: '온도',
    top_p: 'Top_p',
    resetUserInfo: '사용자 정보 초기화',
    chatHistory: '채팅 기록',
    theme: '테마',
    language: '언어',
    api: 'API',
    reverseProxy: '리버스 프록시',
    timeout: '타임아웃',
    socks: 'Socks',
    httpsProxy: 'HTTPS 프록시',
    balance: 'API 잔액',
    monthlyUsage: '월 사용량',
    openSource: '이 프로젝트는 다음에서 오픈 소스로 제공됩니다:',
    freeMIT: '무료이며 MIT 라이선스에 기반하며, 어떠한 형태의 유료 행동도 없습니다!',
    stars: '이 프로젝트가 도움이 되었다면, GitHub에서 별을 주거나 조금의 후원을 해주시면 감사하겠습니다!',
  },
  store: {
    siderButton: '프롬프트 저장소',
    local: '로컬',
    online: '온라인',
    title: '제목',
    description: '설명',
    clearStoreConfirm: '데이터를 삭제하시겠습니까?',
    importPlaceholder: '여기에 JSON 데이터를 붙여넣으십시오',
    addRepeatTitleTips: '제목 중복됨, 다시 입력하십시오',
    addRepeatContentTips: '내용 중복됨: {msg}, 다시 입력하십시오',
    editRepeatTitleTips: '제목 충돌, 수정하십시오',
    editRepeatContentTips: '내용 충돌 {msg} , 수정하십시오',
    importError: '키 값 불일치',
    importRepeatTitle: '제목이 반복되어 건너뜀: {msg}',
    importRepeatContent: '내용이 반복되어 건너뜀: {msg}',
    onlineImportWarning: '참고: JSON 파일 소스를 확인하십시오!',
  },
}


================================================
FILE: src/locales/ru-RU.ts
================================================
export default {
  common: {
    add: 'Добавить',
    addSuccess: 'Добавлено успешно',
    edit: 'Редактировать',
    editSuccess: 'Изменено успешно',
    delete: 'Удалить',
    deleteSuccess: 'Удалено успешно',
    save: 'Сохранить',
    saveSuccess: 'Сохранено успешно',
    reset: 'Сбросить',
    action: 'Действие',
    export: 'Экспортировать',
    exportSuccess: 'Экспорт выполнен успешно',
    import: 'Импортировать',
    importSuccess: 'Импорт выполнен успешно',
    clear: 'Очистить',
    clearSuccess: 'Очищено успешно',
    yes: 'Да',
    no: 'Нет',
    confirm: 'Подтвердить',
    download: 'Загрузить',
    noData: 'Нет данных',
    wrong: 'Что-то пошло не так, пожалуйста, повторите попытку позже.',
    success: 'Успех',
    failed: 'Не удалось',
    verify: 'Проверить',
    unauthorizedTips: 'Не авторизован, сначала подтвердите свою личность.',
    stopResponding: 'Прекращение отклика',
  },
  chat: {
    newChatButton: 'Новый чат',
    newChatTitle: 'Новый чат',
    placeholder: 'Спросите меня о чем-нибудь ... (Shift + Enter = перенос строки, "/" для вызова подсказок)',
    placeholderMobile: 'Спросите меня о чем-нибудь ...',
    copy: 'Копировать',
    copied: 'Скопировано',
    copyCode: 'Копировать код',
    copyFailed: 'Не удалось скопировать',
    clearChat: 'Очистить чат',
    clearChatConfirm: 'Вы уверены, что хотите очистить этот чат?',
    exportImage: 'Экспорт в изображение',
    exportImageConfirm: 'Вы уверены, что хотите экспортировать этот чат в формате PNG?',
    exportSuccess: 'Экспортировано успешно',
    exportFailed: 'Не удалось выполнить экспорт',
    usingContext: 'Режим контекста',
    turnOnContext: 'В текущем режиме отправка сообщений будет включать предыдущие записи чата.',
    turnOffContext: 'В текущем режиме отправка сообщений не будет включать предыдущие записи чата.',
    deleteMessage: 'Удалить сообщение',
    deleteMessageConfirm: 'Вы уверены, что хотите удалить это сообщение?',
    deleteHistoryConfirm: 'Вы уверены, что хотите очистить эту историю?',
    clearHistoryConfirm: 'Вы уверены, что хотите очистить историю чата?',
    preview: 'Предварительный просмотр',
    showRawText: 'Показать как обычный текст',
    thinking: 'Думаю...',
  },
  setting: {
    setting: 'Настройки',
    general: 'Общее',
    advanced: 'Дополнительно',
    config: 'Конфигурация',
    avatarLink: 'Ссылка на аватар',
    name: 'Имя',
    description: 'Описание',
    role: 'Роль',
    temperature: 'Температура',
    top_p: 'Top_p',
    resetUserInfo: 'Сбросить информацию о пользователе',
    chatHistory: 'История чата',
    theme: 'Тема',
    language: 'Язык',
    api: 'API',
    reverseProxy: 'Обратный прокси-сервер',
    timeout: 'Время ожидания',
    socks: 'Socks',
    httpsProxy: 'HTTPS-прокси',
    balance: 'Баланс API',
    monthlyUsage: 'Ежемесячное использование',
    openSource: 'Этот проект опубликован в открытом доступе на',
    freeMIT: 'бесплатно и основан на лицензии MIT, без каких-либо форм оплаты!',
    stars: 'Если вы считаете этот проект полезным, пожалуйста, поставьте мне звезду на GitHub или сделайте небольшое пожертвование, спасибо!',
  },
  store: {
    siderButton: 'Хранилище подсказок',
    local: 'Локальное',
    online: 'Онлайн',
    title: 'Название',
    description: 'Описание',
    clearStoreConfirm: 'Вы действительно хотите очистить данные?',
    importPlaceholder: 'Пожалуйста, вставьте здесь JSON-данные',
    addRepeatTitleTips: 'Дубликат названия, пожалуйста, введите другое название',
    addRepeatContentTips: 'Дубликат содержимого: {msg}, пожалуйста, введите другой текст',
    editRepeatTitleTips: 'Конфликт названий, пожалуйста, измените название',
    editRepeatContentTips: 'Конфликт содержимого {msg}, пожалуйста, измените текст',
    importError: 'Не совпадает ключ-значение',
    importRepeatTitle: 'Название повторяющееся, пропускается: {msg}',
    importRepeatContent: 'Содержание повторяющееся, пропускается: {msg}',
    onlineImportWarning: 'Внимание! Проверьте источник JSON-файла!',
    downloadError: 'Проверьте состояние сети и правильность JSON-файла',
  },
}


================================================
FILE: src/locales/vi-VN.ts
================================================
export default {
  common: {
    add: 'Thêm',
    addSuccess: 'Thêm thành công',
    edit: 'Sửa',
    editSuccess: 'Sửa thành công',
    delete: 'Xóa',
    deleteSuccess: 'Xóa thành công',
    save: 'Lưu',
    saveSuccess: 'Lưu thành công',
    reset: 'Đặt lại',
    action: 'Hành động',
    export: 'Xuất',
    exportSuccess: 'Xuất thành công',
    import: 'Nhập',
    importSuccess: 'Nhập thành công',
    clear: 'Dọn dẹp',
    clearSuccess: 'Dọn dẹp thành công',
    yes: 'Có',
    no: 'Không',
    confirm: 'Xác nhận',
    download: 'Tải xuống',
    noData: 'Không có dữ liệu',
    wrong: 'Đã xảy ra lỗi, vui lòng thử lại sau.',
    success: 'Thành công',
    failed: 'Thất bại',
    verify: 'Xác minh',
    unauthorizedTips: 'Không được ủy quyền, vui lòng xác minh trước.',
  },
  chat: {
    newChatButton: 'Tạo hội thoại',
    newChatTitle: 'Tạo hội thoại',
    placeholder: 'Hỏi tôi bất cứ điều gì...(Shift + Enter = ngắt dòng, "/" to trigger prompts)',
    placeholderMobile: 'Hỏi tôi bất cứ điều gì...',
    copy: 'Sao chép',
    copied: 'Đã sao chép',
    copyCode: 'Sao chép Code',
    copyFailed: 'Sao chép thất bại',
    clearChat: 'Clear Chat',
    clearChatConfirm: 'Bạn có chắc chắn xóa cuộc trò chuyện này?',
    exportImage: 'Xuất hình ảnh',
    exportImageConfirm: 'Bạn có chắc chắn xuất cuộc trò chuyện này sang png không?',
    exportSuccess: 'Xuất thành công',
    exportFailed: 'Xuất thất bại',
    usingContext: 'Context Mode',
    turnOnContext: 'Ở chế độ hiện tại, việc gửi tin nhắn sẽ mang theo các bản ghi trò chuyện trước đó.',
    turnOffContext: 'Ở chế độ hiện tại, việc gửi tin nhắn sẽ không mang theo các bản ghi trò chuyện trước đó.',
    deleteMessage: 'Xóa tin nhắn',
    deleteMessageConfirm: 'Bạn có chắc chắn xóa tin nhắn này?',
    deleteHistoryConfirm: 'Bạn có chắc chắn để xóa lịch sử này?',
    clearHistoryConfirm: 'Bạn có chắc chắn để xóa lịch sử trò chuyện?',
    preview: 'Xem trước',
    showRawText: 'Hiển thị dưới dạng văn bản thô',
    thinking: 'Đang suy nghĩ...',
  },
  setting: {
    setting: 'Cài đặt',
    general: 'Chung',
    advanced: 'Nâng cao',
    config: 'Cấu hình',
    avatarLink: 'Avatar Link',
    name: 'Tên',
    description: 'Miêu tả',
    role: 'Vai trò',
    temperature: 'Nhiệt độ',
    top_p: 'Top_p',
    resetUserInfo: 'Đặt lại thông tin người dùng',
    chatHistory: 'Lịch sử trò chuyện',
    theme: 'Giao diện',
    language: 'Ngôn ngữ',
    api: 'API',
    reverseProxy: 'Reverse Proxy',
    timeout: 'Timeout',
    socks: 'Socks',
    httpsProxy: 'HTTPS Proxy',
    balance: 'API Balance',
    monthlyUsage: 'Sử dụng hàng tháng',
    openSource: 'Dự án này được mở nguồn tại',
    freeMIT: 'miễn phí và dựa trên giấy phép MIT, không có bất kỳ hình thức hành vi trả phí nào!',
    stars: 'Nếu bạn thấy dự án này hữu ích, vui lòng cho tôi một Star trên GitHub hoặc tài trợ một chút, cảm ơn bạn!',
  },
  store: {
    siderButton: 'Prompt Store',
    local: 'Local',
    online: 'Online',
    title: 'Tiêu đề',
    description: 'Miêu tả',
    clearStoreConfirm: 'Cho dù để xóa dữ liệu?',
    importPlaceholder: 'Vui lòng dán dữ liệu JSON vào đây',
    addRepeatTitleTips: 'Tiêu đề trùng lặp, vui lòng nhập lại',
    addRepeatContentTips: 'Nội dung trùng lặp: {msg}, vui lòng nhập lại',
    editRepeatTitleTips: 'Xung đột tiêu đề, vui lòng sửa lại',
    editRepeatContentTips: 'Xung đột nội dung {msg} , vui lòng sửa đổi lại',
    importError: 'Key value mismatch',
    importRepeatTitle: 'Tiêu đề liên tục bị bỏ qua: {msg}',
    importRepeatContent: 'Nội dung liên tục bị bỏ qua: {msg}',
    onlineImportWarning: 'Lưu ý: Vui lòng kiểm tra nguồn tệp JSON!',
    downloadError: 'Vui lòng kiểm tra trạng thái mạng và tính hợp lệ của tệp JSON',
  },
}


================================================
FILE: src/locales/zh-CN.ts
================================================
export default {
  common: {
    add: '添加',
    addSuccess: '添加成功',
    edit: '编辑',
    editSuccess: '编辑成功',
    delete: '删除',
    deleteSuccess: '删除成功',
    save: '保存',
    saveSuccess: '保存成功',
    reset: '重置',
    action: '操作',
    export: '导出',
    exportSuccess: '导出成功',
    import: '导入',
    importSuccess: '导入成功',
    clear: '清空',
    clearSuccess: '清空成功',
    yes: '是',
    no: '否',
    confirm: '确定',
    download: '下载',
    noData: '暂无数据',
    wrong: '好像出错了,请稍后再试。',
    success: '操作成功',
    failed: '操作失败',
    verify: '验证',
    unauthorizedTips: '未经授权,请先进行验证。',
    stopResponding: '停止响应',
  },
  chat: {
    newChatButton: '新建聊天',
    newChatTitle: '新建聊天',
    placeholder: '来说点什么吧...(Shift + Enter = 换行,"/" 触发提示词)',
    placeholderMobile: '来说点什么...',
    copy: '复制',
    copied: '复制成功',
    copyCode: '复制代码',
    copyFailed: '复制失败',
    clearChat: '清空会话',
    clearChatConfirm: '是否清空会话?',
    exportImage: '保存会话到图片',
    exportImageConfirm: '是否将会话保存为图片?',
    exportSuccess: '保存成功',
    exportFailed: '保存失败',
    usingContext: '上下文模式',
    turnOnContext: '当前模式下, 发送消息会携带之前的聊天记录',
    turnOffContext: '当前模式下, 发送消息不会携带之前的聊天记录',
    deleteMessage: '删除消息',
    deleteMessageConfirm: '是否删除此消息?',
    deleteHistoryConfirm: '确定删除此记录?',
    clearHistoryConfirm: '确定清空记录?',
    preview: '预览',
    showRawText: '显示原文',
    thinking: '思考中...',
  },
  setting: {
    setting: '设置',
    general: '总览',
    advanced: '高级',
    config: '配置',
    avatarLink: '头像链接',
    name: '名称',
    description: '描述',
    role: '角色设定',
    temperature: 'Temperature',
    top_p: 'Top_p',
    resetUserInfo: '重置用户信息',
    chatHistory: '聊天记录',
    theme: '主题',
    language: '语言',
    api: 'API',
    reverseProxy: '反向代理',
    timeout: '超时',
    socks: 'Socks',
    httpsProxy: 'HTTPS Proxy',
    balance: 'API余额',
    monthlyUsage: '本月使用量',
    openSource: '此项目开源于',
    freeMIT: '免费且基于 MIT 协议,没有任何形式的付费行为',
    stars: '如果你觉得此项目对你有帮助,请在 GitHub 上给我一个星星或者给予一点赞助,谢谢!',
  },
  store: {
    siderButton: '提示词商店',
    local: '本地',
    online: '在线',
    title: '标题',
    description: '描述',
    clearStoreConfirm: '是否清空数据?',
    importPlaceholder: '请粘贴 JSON 数据到此处',
    addRepeatTitleTips: '标题重复,请重新输入',
    addRepeatContentTips: '内容重复:{msg},请重新输入',
    editRepeatTitleTips: '标题冲突,请重新修改',
    editRepeatContentTips: '内容冲突{msg} ,请重新修改',
    importError: '键值不匹配',
    importRepeatTitle: '标
Download .txt
gitextract_4kuwrg29/

├── .commitlintrc.json
├── .devcontainer/
│   └── devcontainer.json
├── .dockerignore
├── .editorconfig
├── .eslintignore
├── .eslintrc.cjs
├── .gitattributes
├── .github/
│   └── workflows/
│       ├── build_docker.yml
│       ├── ci.yml
│       └── issues_close.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   └── pre-commit
├── .npmrc
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── CHANGELOG.md
├── CONTRIBUTING.en.md
├── CONTRIBUTING.md
├── Dockerfile
├── README.md
├── README.zh.md
├── docker-compose/
│   ├── README.md
│   ├── docker-compose.yml
│   └── nginx/
│       └── nginx.conf
├── index.html
├── kubernetes/
│   ├── README.md
│   ├── deploy.yaml
│   └── ingress.yaml
├── license
├── package.json
├── postcss.config.js
├── service/
│   ├── .eslintrc.json
│   ├── .gitignore
│   ├── .npmrc
│   ├── .vscode/
│   │   ├── extensions.json
│   │   └── settings.json
│   ├── package.json
│   ├── src/
│   │   ├── chatgpt/
│   │   │   ├── index.ts
│   │   │   └── types.ts
│   │   ├── index.ts
│   │   ├── middleware/
│   │   │   ├── auth.ts
│   │   │   └── limiter.ts
│   │   ├── types.ts
│   │   └── utils/
│   │       ├── index.ts
│   │       └── is.ts
│   ├── tsconfig.json
│   └── tsup.config.ts
├── src/
│   ├── App.vue
│   ├── api/
│   │   └── index.ts
│   ├── assets/
│   │   └── recommend.json
│   ├── components/
│   │   ├── common/
│   │   │   ├── HoverButton/
│   │   │   │   ├── Button.vue
│   │   │   │   └── 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
│   ├── hooks/
│   │   ├── useBasicLayout.ts
│   │   ├── useIconRender.ts
│   │   ├── useLanguage.ts
│   │   └── useTheme.ts
│   ├── icons/
│   │   ├── 403.vue
│   │   └── 500.vue
│   ├── locales/
│   │   ├── en-US.ts
│   │   ├── es-ES.ts
│   │   ├── index.ts
│   │   ├── ko-KR.ts
│   │   ├── ru-RU.ts
│   │   ├── vi-VN.ts
│   │   ├── zh-CN.ts
│   │   └── zh-TW.ts
│   ├── main.ts
│   ├── plugins/
│   │   ├── assets.ts
│   │   ├── index.ts
│   │   └── scrollbarStyle.ts
│   ├── router/
│   │   ├── index.ts
│   │   └── permission.ts
│   ├── store/
│   │   ├── helper.ts
│   │   ├── index.ts
│   │   └── modules/
│   │       ├── app/
│   │       │   ├── helper.ts
│   │       │   └── index.ts
│   │       ├── auth/
│   │       │   ├── 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
│   ├── utils/
│   │   ├── copy.ts
│   │   ├── functions/
│   │   │   ├── debounce.ts
│   │   │   └── index.ts
│   │   ├── is/
│   │   │   └── index.ts
│   │   ├── request/
│   │   │   ├── axios.ts
│   │   │   └── index.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
├── start.cmd
├── start.sh
├── tailwind.config.js
├── tsconfig.json
└── vite.config.ts
Download .txt
SYMBOL INDEX (146 symbols across 42 files)

FILE: service/src/chatgpt/index.ts
  function chatReplyProcess (line 105) | async function chatReplyProcess(options: RequestOptions) {
  function fetchUsage (line 141) | async function fetchUsage() {
  function formatDate (line 181) | function formatDate(): string[] {
  function chatConfig (line 191) | async function chatConfig() {
  function setupProxy (line 204) | function setupProxy(options: SetProxyOptions) {
  function currentModel (line 232) | function currentModel(): ApiModel {

FILE: service/src/chatgpt/types.ts
  type RequestOptions (line 4) | interface RequestOptions {
  type SetProxyOptions (line 13) | interface SetProxyOptions {
  type UsageResponse (line 17) | interface UsageResponse {

FILE: service/src/middleware/limiter.ts
  constant MAX_REQUEST_PER_HOUR (line 4) | const MAX_REQUEST_PER_HOUR = process.env.MAX_REQUEST_PER_HOUR

FILE: service/src/types.ts
  type RequestProps (line 3) | interface RequestProps {
  type ChatContext (line 11) | interface ChatContext {
  type ChatGPTUnofficialProxyAPIOptions (line 16) | interface ChatGPTUnofficialProxyAPIOptions {
  type ModelConfig (line 25) | interface ModelConfig {
  type ApiModel (line 34) | type ApiModel = 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' | undefined

FILE: service/src/utils/index.ts
  type SendResponseOptions (line 1) | interface SendResponseOptions<T = any> {
  function sendResponse (line 7) | function sendResponse<T>(options: SendResponseOptions<T>) {

FILE: service/src/utils/is.ts
  function isNumber (line 1) | function isNumber<T extends number>(value: T | unknown): value is number {
  function isString (line 5) | function isString<T extends string>(value: T | unknown): value is string {
  function isNotEmptyString (line 9) | function isNotEmptyString(value: any): boolean {
  function isBoolean (line 13) | function isBoolean<T extends boolean>(value: T | unknown): value is bool...
  function isFunction (line 17) | function isFunction<T extends (...args: any[]) => any | void | never>(va...

FILE: src/api/index.ts
  function fetchChatAPI (line 5) | function fetchChatAPI<T = any>(
  function fetchChatConfig (line 17) | function fetchChatConfig<T = any>() {
  function fetchChatAPIProcess (line 23) | function fetchChatAPIProcess<T = any>(
  function fetchSession (line 55) | function fetchSession<T>() {
  function fetchVerify (line 61) | function fetchVerify<T>(token: string) {

FILE: src/hooks/useBasicLayout.ts
  function useBasicLayout (line 3) | function useBasicLayout() {

FILE: src/hooks/useIconRender.ts
  type IconConfig (line 5) | interface IconConfig {
  type IconStyle (line 11) | interface IconStyle {

FILE: src/hooks/useLanguage.ts
  function useLanguage (line 6) | function useLanguage() {

FILE: src/hooks/useTheme.ts
  function useTheme (line 6) | function useTheme() {

FILE: src/locales/index.ts
  function setLocale (line 34) | function setLocale(locale: Language) {
  function setupI18n (line 38) | function setupI18n(app: App) {

FILE: src/main.ts
  function bootstrap (line 8) | async function bootstrap() {

FILE: src/plugins/assets.ts
  function naiveStyleOverride (line 8) | function naiveStyleOverride() {
  function setupAssets (line 14) | function setupAssets() {

FILE: src/router/index.ts
  function setupRouter (line 49) | async function setupRouter(app: App) {

FILE: src/router/permission.ts
  function setupPageGuard (line 4) | function setupPageGuard(router: Router) {

FILE: src/store/index.ts
  function setupStore (line 4) | function setupStore(app: App) {

FILE: src/store/modules/app/helper.ts
  constant LOCAL_NAME (line 3) | const LOCAL_NAME = 'appSetting'
  type Theme (line 5) | type Theme = 'light' | 'dark' | 'auto'
  type Language (line 7) | type Language = 'en-US' | 'es-ES' | 'ko-KR' | 'ru-RU' | 'vi-VN' | 'zh-CN...
  type AppState (line 25) | interface AppState {
  function defaultSetting (line 31) | function defaultSetting(): AppState {
  function getLocalSetting (line 36) | function getLocalSetting(): AppState {
  function setLocalSetting (line 41) | function setLocalSetting(setting: AppState): void {

FILE: src/store/modules/app/index.ts
  method setSiderCollapsed (line 9) | setSiderCollapsed(collapsed: boolean) {
  method setTheme (line 14) | setTheme(theme: Theme) {
  method setLanguage (line 19) | setLanguage(language: Language) {
  method recordState (line 26) | recordState() {
  function useAppStoreWithOut (line 32) | function useAppStoreWithOut() {

FILE: src/store/modules/auth/helper.ts
  constant LOCAL_NAME (line 3) | const LOCAL_NAME = 'SECRET_TOKEN'
  function getToken (line 5) | function getToken() {
  function setToken (line 9) | function setToken(token: string) {
  function removeToken (line 13) | function removeToken() {

FILE: src/store/modules/auth/index.ts
  type SessionResponse (line 6) | interface SessionResponse {
  type AuthState (line 11) | interface AuthState {
  method isChatGPTAPI (line 23) | isChatGPTAPI(state): boolean {
  method getSession (line 29) | async getSession() {
  method setToken (line 40) | setToken(token: string) {
  method removeToken (line 45) | removeToken() {
  function useAuthStoreWithout (line 52) | function useAuthStoreWithout() {

FILE: src/store/modules/chat/helper.ts
  constant LOCAL_NAME (line 4) | const LOCAL_NAME = 'chatStorage'
  function defaultState (line 6) | function defaultState(): Chat.ChatState {
  function getLocalState (line 16) | function getLocalState(): Chat.ChatState {
  function setLocalState (line 21) | function setLocalState(state: Chat.ChatState) {

FILE: src/store/modules/chat/index.ts
  method getChatHistoryByCurrentActive (line 10) | getChatHistoryByCurrentActive(state: Chat.ChatState) {
  method getChatByUuid (line 17) | getChatByUuid(state: Chat.ChatState) {
  method setUsingContext (line 27) | setUsingContext(context: boolean) {
  method addHistory (line 32) | addHistory(history: Chat.History, chatData: Chat.Chat[] = []) {
  method updateHistory (line 39) | updateHistory(uuid: number, edit: Partial<Chat.History>) {
  method deleteHistory (line 47) | async deleteHistory(index: number) {
  method setActive (line 79) | async setActive(uuid: number) {
  method getChatByUuidAndIndex (line 84) | getChatByUuidAndIndex(uuid: number, index: number) {
  method addChatByUuid (line 96) | addChatByUuid(uuid: number, chat: Chat.Chat) {
  method updateChatByUuid (line 122) | updateChatByUuid(uuid: number, index: number, chat: Chat.Chat) {
  method updateChatSomeByUuid (line 138) | updateChatSomeByUuid(uuid: number, index: number, chat: Partial<Chat.Cha...
  method deleteChatByUuid (line 154) | deleteChatByUuid(uuid: number, index: number) {
  method clearChatByUuid (line 170) | clearChatByUuid(uuid: number) {
  method clearHistory (line 186) | clearHistory() {
  method reloadRoute (line 191) | async reloadRoute(uuid?: number) {
  method recordState (line 196) | recordState() {

FILE: src/store/modules/prompt/helper.ts
  constant LOCAL_NAME (line 3) | const LOCAL_NAME = 'promptStore'
  type PromptList (line 5) | type PromptList = []
  type PromptStore (line 7) | interface PromptStore {
  function getLocalPromptList (line 11) | function getLocalPromptList(): PromptStore {
  function setLocalPromptList (line 16) | function setLocalPromptList(promptStore: PromptStore): void {

FILE: src/store/modules/prompt/index.ts
  method updatePromptList (line 9) | updatePromptList(promptList: []) {
  method getPromptList (line 13) | getPromptList() {

FILE: src/store/modules/settings/helper.ts
  constant LOCAL_NAME (line 3) | const LOCAL_NAME = 'settingsStorage'
  type SettingsState (line 5) | interface SettingsState {
  function defaultSetting (line 11) | function defaultSetting(): SettingsState {
  function getLocalState (line 19) | function getLocalState(): SettingsState {
  function setLocalState (line 24) | function setLocalState(setting: SettingsState): void {
  function removeLocalState (line 28) | function removeLocalState() {

FILE: src/store/modules/settings/index.ts
  method updateSetting (line 8) | updateSetting(settings: Partial<SettingsState>) {
  method resetSetting (line 13) | resetSetting() {
  method recordState (line 18) | recordState() {

FILE: src/store/modules/user/helper.ts
  constant LOCAL_NAME (line 3) | const LOCAL_NAME = 'userStorage'
  type UserInfo (line 5) | interface UserInfo {
  type UserState (line 11) | interface UserState {
  function defaultSetting (line 15) | function defaultSetting(): UserState {
  function getLocalState (line 25) | function getLocalState(): UserState {
  function setLocalState (line 30) | function setLocalState(setting: UserState): void {

FILE: src/store/modules/user/index.ts
  method updateUserInfo (line 8) | updateUserInfo(userInfo: Partial<UserInfo>) {
  method resetUserInfo (line 13) | resetUserInfo() {
  method recordState (line 18) | recordState() {

FILE: src/typings/chat.d.ts
  type Chat (line 3) | interface Chat {
  type History (line 13) | interface History {
  type ChatState (line 19) | interface ChatState {
  type ConversationRequest (line 26) | interface ConversationRequest {
  type ConversationResponse (line 31) | interface ConversationResponse {

FILE: src/typings/env.d.ts
  type ImportMetaEnv (line 3) | interface ImportMetaEnv {

FILE: src/typings/global.d.ts
  type Window (line 1) | interface Window {

FILE: src/utils/copy.ts
  function copyToClip (line 1) | function copyToClip(text: string) {

FILE: src/utils/functions/debounce.ts
  type CallbackFunc (line 1) | type CallbackFunc<T extends unknown[]> = (...args: T) => void
  function debounce (line 3) | function debounce<T extends unknown[]>(

FILE: src/utils/functions/index.ts
  function getCurrentDate (line 1) | function getCurrentDate() {

FILE: src/utils/is/index.ts
  function isNumber (line 1) | function isNumber<T extends number>(value: T | unknown): value is number {
  function isString (line 5) | function isString<T extends string>(value: T | unknown): value is string {
  function isBoolean (line 9) | function isBoolean<T extends boolean>(value: T | unknown): value is bool...
  function isNull (line 13) | function isNull<T extends null>(value: T | unknown): value is null {
  function isUndefined (line 17) | function isUndefined<T extends undefined>(value: T | unknown): value is ...
  function isObject (line 21) | function isObject<T extends object>(value: T | unknown): value is object {
  function isArray (line 25) | function isArray<T extends any[]>(value: T | unknown): value is T {
  function isFunction (line 29) | function isFunction<T extends (...args: any[]) => any | void | never>(va...
  function isDate (line 33) | function isDate<T extends Date>(value: T | unknown): value is T {
  function isRegExp (line 37) | function isRegExp<T extends RegExp>(value: T | unknown): value is T {
  function isPromise (line 41) | function isPromise<T extends Promise<any>>(value: T | unknown): value is...
  function isSet (line 45) | function isSet<T extends Set<any>>(value: T | unknown): value is T {
  function isMap (line 49) | function isMap<T extends Map<any, any>>(value: T | unknown): value is T {
  function isFile (line 53) | function isFile<T extends File>(value: T | unknown): value is T {

FILE: src/utils/request/index.ts
  type HttpOption (line 5) | interface HttpOption {
  type Response (line 16) | interface Response<T = any> {
  function http (line 22) | function http<T = any>(
  function get (line 55) | function get<T = any>(
  function post (line 69) | function post<T = any>(

FILE: src/utils/storage/index.ts
  type StorageData (line 1) | interface StorageData<T = any> {
  function createLocalStorage (line 6) | function createLocalStorage(options?: { expire?: number | null }) {

FILE: src/views/chat/hooks/useChat.ts
  function useChat (line 3) | function useChat() {

FILE: src/views/chat/hooks/useScroll.ts
  type ScrollElement (line 4) | type ScrollElement = HTMLDivElement | null
  type ScrollReturn (line 6) | interface ScrollReturn {
  function useScroll (line 13) | function useScroll(): ScrollReturn {

FILE: src/views/chat/hooks/useUsingContext.ts
  function useUsingContext (line 6) | function useUsingContext() {

FILE: vite.config.ts
  function setupPlugins (line 7) | function setupPlugins(env: ImportMetaEnv): PluginOption[] {
Condensed preview — 136 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (315K chars).
[
  {
    "path": ".commitlintrc.json",
    "chars": 53,
    "preview": "{\n  \"extends\": [\"@commitlint/config-conventional\"]\n}\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "chars": 909,
    "preview": "// For format details, see https://aka.ms/devcontainer.json. For config options, see the\n// README at: https://github.co"
  },
  {
    "path": ".dockerignore",
    "chars": 69,
    "preview": "**/node_modules\n*/node_modules\nnode_modules\nDockerfile\n.*\n*/.*\n!.env\n"
  },
  {
    "path": ".editorconfig",
    "chars": 198,
    "preview": "# Editor configuration, see http://editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = tab\nindent_size = 2"
  },
  {
    "path": ".eslintignore",
    "chars": 26,
    "preview": "docker-compose\nkubernetes\n"
  },
  {
    "path": ".eslintrc.cjs",
    "chars": 58,
    "preview": "module.exports = {\n  root: true,\n  extends: ['@antfu'],\n}\n"
  },
  {
    "path": ".gitattributes",
    "chars": 306,
    "preview": "\"*.vue\"    eol=lf\n\"*.js\"     eol=lf\n\"*.ts\"     eol=lf\n\"*.jsx\"    eol=lf\n\"*.tsx\"    eol=lf\n\"*.cjs\"    eol=lf\n\"*.cts\"    e"
  },
  {
    "path": ".github/workflows/build_docker.yml",
    "chars": 1139,
    "preview": "name: build_docker\n\non:\n  push:\n    branches: [main]\n  release:\n    types: [created] # 表示在创建新的 Release 时触发\n\njobs:\n  buil"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 753,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n      - main\n\n  pull_request:\n    branches:\n      - main\n\njobs:\n  lint:\n    runs-on:"
  },
  {
    "path": ".github/workflows/issues_close.yml",
    "chars": 697,
    "preview": "name: Close inactive issues\non:\n  schedule:\n    - cron: '30 1 * * *'\n\njobs:\n  close-issues:\n    runs-on: ubuntu-latest\n "
  },
  {
    "path": ".gitignore",
    "chars": 370,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\n.DS_Stor"
  },
  {
    "path": ".husky/commit-msg",
    "chars": 84,
    "preview": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpx --no -- commitlint --edit \n"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 69,
    "preview": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
  },
  {
    "path": ".npmrc",
    "chars": 31,
    "preview": "strict-peer-dependencies=false\n"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 65,
    "preview": "{\n  \"recommendations\": [\"Vue.volar\", \"dbaeumer.vscode-eslint\"]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 1060,
    "preview": "{\n  \"prettier.enable\": false,\n  \"editor.formatOnSave\": false,\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\""
  },
  {
    "path": "CHANGELOG.md",
    "chars": 14554,
    "preview": "## v2.11.1\n\n`2023-10-11`\n\n## Enhancement\n- 优化打字机光标效果\n- 清空聊天历史按钮\n- 更新文档\n\n## BugFix\n- 修复移动端上的问题\n- 修复不规范的引入导致的问题\n\n## v2.11."
  },
  {
    "path": "CONTRIBUTING.en.md",
    "chars": 2281,
    "preview": "# Contribution Guide\nThank you for your valuable time. Your contributions will make this project better! Before submitti"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1198,
    "preview": "# 贡献指南\n感谢你的宝贵时间。你的贡献将使这个项目变得更好!在提交贡献之前,请务必花点时间阅读下面的入门指南。\n\n## 语义化版本\n该项目遵循语义化版本。我们对重要的漏洞修复发布修订号,对新特性或不重要的变更发布次版本号,对重大且不兼容的"
  },
  {
    "path": "Dockerfile",
    "chars": 794,
    "preview": "# build front-end\nFROM node:lts-alpine AS frontend\n\nRUN npm install pnpm -g\n\nWORKDIR /app\n\nCOPY ./package.json /app\n\nCOP"
  },
  {
    "path": "README.md",
    "chars": 14409,
    "preview": "# ChatGPT Web\n\n> Disclaimer: This project is only published on GitHub, based on the MIT license, free and for open sourc"
  },
  {
    "path": "README.zh.md",
    "chars": 9867,
    "preview": "# ChatGPT Web\n\n> 声明:此项目只发布于 GitHub,基于 MIT 协议,免费且作为开源学习使用。并且不会有任何形式的卖号、付费服务、讨论群、讨论组等行为。谨防受骗。\n\n[English](README.md)\n\n![cov"
  },
  {
    "path": "docker-compose/README.md",
    "chars": 265,
    "preview": "### docker-compose Deployment Tutorial\n-Put the packaged front-end files in the `nginx/html` directory\n- ```shell\n  # st"
  },
  {
    "path": "docker-compose/docker-compose.yml",
    "chars": 1553,
    "preview": "version: '3'\n\nservices:\n  app:\n    container_name: chatgpt-web\n    image: chenzhaoyu94/chatgpt-web # Always use latest, "
  },
  {
    "path": "docker-compose/nginx/nginx.conf",
    "chars": 888,
    "preview": "server {\n\tlisten 80;\n\tserver_name  localhost;\n\tcharset utf-8;\n\terror_page   500 502 503 504  /50x.html;\n\t\n\t# Prevent cra"
  },
  {
    "path": "index.html",
    "chars": 1608,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\">\n\t<meta"
  },
  {
    "path": "kubernetes/README.md",
    "chars": 119,
    "preview": "## 增加一个Kubernetes的部署方式\n```\nkubectl apply -f deploy.yaml\n```\n\n### 如果需要Ingress域名接入\n```\nkubectl apply -f ingress.yaml\n```\n"
  },
  {
    "path": "kubernetes/deploy.yaml",
    "chars": 1505,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: chatgpt-web\n  labels:\n    app: chatgpt-web\nspec:\n  replicas: 1\n  "
  },
  {
    "path": "kubernetes/ingress.yaml",
    "chars": 501,
    "preview": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  annotations:\n    kubernetes.io/ingress.class: nginx\n    nginx"
  },
  {
    "path": "license",
    "chars": 1067,
    "preview": "MIT License\n\nCopyright (c) 2023 ChenZhaoYu\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "package.json",
    "chars": 1947,
    "preview": "{\n  \"name\": \"chatgpt-web\",\n  \"version\": \"2.11.1\",\n  \"private\": false,\n  \"description\": \"ChatGPT Web\",\n  \"author\": \"ChenZ"
  },
  {
    "path": "postcss.config.js",
    "chars": 82,
    "preview": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n}\n"
  },
  {
    "path": "service/.eslintrc.json",
    "chars": 75,
    "preview": "{\n  \"root\": true,\n  \"ignorePatterns\": [\"build\"],\n  \"extends\": [\"@antfu\"]\n}\n"
  },
  {
    "path": "service/.gitignore",
    "chars": 332,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\n.DS_Stor"
  },
  {
    "path": "service/.npmrc",
    "chars": 29,
    "preview": "enable-pre-post-scripts=true\n"
  },
  {
    "path": "service/.vscode/extensions.json",
    "chars": 52,
    "preview": "{\n  \"recommendations\": [\"dbaeumer.vscode-eslint\"]\n}\n"
  },
  {
    "path": "service/.vscode/settings.json",
    "chars": 338,
    "preview": "{\n  \"prettier.enable\": false,\n  \"editor.formatOnSave\": false,\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\""
  },
  {
    "path": "service/package.json",
    "chars": 1175,
    "preview": "{\n  \"name\": \"chatgpt-web-service\",\n  \"version\": \"1.0.0\",\n  \"private\": false,\n  \"description\": \"ChatGPT Web Service\",\n  \""
  },
  {
    "path": "service/src/chatgpt/index.ts",
    "chars": 8117,
    "preview": "import * as dotenv from 'dotenv'\nimport 'isomorphic-fetch'\nimport type { ChatGPTAPIOptions, ChatMessage, SendMessageOpti"
  },
  {
    "path": "service/src/chatgpt/types.ts",
    "chars": 428,
    "preview": "import type { ChatMessage } from 'chatgpt'\nimport type fetch from 'node-fetch'\n\nexport interface RequestOptions {\n  mess"
  },
  {
    "path": "service/src/index.ts",
    "chars": 2448,
    "preview": "import express from 'express'\nimport type { RequestProps } from './types'\nimport type { ChatMessage } from './chatgpt'\ni"
  },
  {
    "path": "service/src/middleware/auth.ts",
    "chars": 608,
    "preview": "import { isNotEmptyString } from '../utils/is'\n\nconst auth = async (req, res, next) => {\n  const AUTH_SECRET_KEY = proce"
  },
  {
    "path": "service/src/middleware/limiter.ts",
    "chars": 696,
    "preview": "import { rateLimit } from 'express-rate-limit'\nimport { isNotEmptyString } from '../utils/is'\n\nconst MAX_REQUEST_PER_HOU"
  },
  {
    "path": "service/src/types.ts",
    "chars": 702,
    "preview": "import type { FetchFn } from 'chatgpt'\n\nexport interface RequestProps {\n  prompt: string\n  options?: ChatContext\n  syste"
  },
  {
    "path": "service/src/utils/index.ts",
    "chars": 539,
    "preview": "interface SendResponseOptions<T = any> {\n  type: 'Success' | 'Fail'\n  message?: string\n  data?: T\n}\n\nexport function sen"
  },
  {
    "path": "service/src/utils/is.ts",
    "chars": 764,
    "preview": "export function isNumber<T extends number>(value: T | unknown): value is number {\n  return Object.prototype.toString.cal"
  },
  {
    "path": "service/tsconfig.json",
    "chars": 497,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"lib\": [\n      \"esnext\"\n    ],\n    \"allowJs\": true,\n    \"skipLibChe"
  },
  {
    "path": "service/tsup.config.ts",
    "chars": 240,
    "preview": "import { defineConfig } from 'tsup'\n\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  outDir: 'build',\n  targe"
  },
  {
    "path": "src/App.vue",
    "chars": 534,
    "preview": "<script setup lang=\"ts\">\nimport { NConfigProvider } from 'naive-ui'\nimport { NaiveProvider } from '@/components/common'\n"
  },
  {
    "path": "src/api/index.ts",
    "chars": 1468,
    "preview": "import type { AxiosProgressEvent, GenericAbortSignal } from 'axios'\nimport { post } from '@/utils/request'\nimport { useA"
  },
  {
    "path": "src/assets/recommend.json",
    "chars": 563,
    "preview": "[\n  {\n    \"key\": \"awesome-chatgpt-prompts-zh\",\n    \"desc\": \"ChatGPT 中文调教指南\",\n    \"downloadUrl\": \"https://raw.githubuserc"
  },
  {
    "path": "src/components/common/HoverButton/Button.vue",
    "chars": 365,
    "preview": "<script setup lang='ts'>\ninterface Emit {\n  (e: 'click'): void\n}\n\nconst emit = defineEmits<Emit>()\n\nfunction handleClick"
  },
  {
    "path": "src/components/common/HoverButton/index.vue",
    "chars": 884,
    "preview": "<script setup lang='ts'>\nimport { computed } from 'vue'\nimport type { PopoverPlacement } from 'naive-ui'\nimport { NToolt"
  },
  {
    "path": "src/components/common/NaiveProvider/index.vue",
    "chars": 885,
    "preview": "<script setup lang=\"ts\">\nimport { defineComponent, h } from 'vue'\nimport {\n  NDialogProvider,\n  NLoadingBarProvider,\n  N"
  },
  {
    "path": "src/components/common/PromptStore/index.vue",
    "chars": 14033,
    "preview": "<script setup lang='ts'>\nimport type { DataTableColumns } from 'naive-ui'\nimport { computed, h, ref, watch } from 'vue'\n"
  },
  {
    "path": "src/components/common/Setting/About.vue",
    "chars": 1952,
    "preview": "<script setup lang='ts'>\nimport { computed, onMounted, ref } from 'vue'\nimport { NSpin } from 'naive-ui'\nimport pkg from"
  },
  {
    "path": "src/components/common/Setting/Advanced.vue",
    "chars": 2409,
    "preview": "<script lang=\"ts\" setup>\nimport { ref } from 'vue'\nimport { NButton, NInput, NSlider, useMessage } from 'naive-ui'\nimpor"
  },
  {
    "path": "src/components/common/Setting/General.vue",
    "chars": 7171,
    "preview": "<script lang=\"ts\" setup>\nimport { computed, ref } from 'vue'\nimport { NButton, NInput, NPopconfirm, NSelect, useMessage "
  },
  {
    "path": "src/components/common/Setting/index.vue",
    "chars": 1939,
    "preview": "<script setup lang='ts'>\nimport { computed, ref } from 'vue'\nimport { NModal, NTabPane, NTabs } from 'naive-ui'\nimport G"
  },
  {
    "path": "src/components/common/SvgIcon/index.vue",
    "chars": 425,
    "preview": "<script setup lang='ts'>\nimport { computed, useAttrs } from 'vue'\nimport { Icon } from '@iconify/vue'\n\ninterface Props {"
  },
  {
    "path": "src/components/common/UserAvatar/index.vue",
    "chars": 1246,
    "preview": "<script setup lang='ts'>\nimport { computed } from 'vue'\nimport { NAvatar } from 'naive-ui'\nimport { useUserStore } from "
  },
  {
    "path": "src/components/common/index.ts",
    "chars": 368,
    "preview": "import HoverButton from './HoverButton/index.vue'\nimport NaiveProvider from './NaiveProvider/index.vue'\nimport SvgIcon f"
  },
  {
    "path": "src/components/custom/GithubSite.vue",
    "chars": 207,
    "preview": "<template>\n  <div class=\"text-neutral-400\">\n    <span>Star on</span>\n    <a href=\"https://github.com/Chanzhaoyu/chatgpt-"
  },
  {
    "path": "src/components/custom/index.ts",
    "chars": 65,
    "preview": "import GithubSite from './GithubSite.vue'\n\nexport { GithubSite }\n"
  },
  {
    "path": "src/hooks/useBasicLayout.ts",
    "chars": 231,
    "preview": "import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'\n\nexport function useBasicLayout() {\n  const breakpoin"
  },
  {
    "path": "src/hooks/useIconRender.ts",
    "chars": 650,
    "preview": "import { h } from 'vue'\nimport { SvgIcon } from '@/components/common'\n\nexport const useIconRender = () => {\n  interface "
  },
  {
    "path": "src/hooks/useLanguage.ts",
    "chars": 695,
    "preview": "import { computed } from 'vue'\nimport { enUS, esAR, koKR, ruRU, viVN, zhCN, zhTW } from 'naive-ui'\nimport { useAppStore "
  },
  {
    "path": "src/hooks/useTheme.ts",
    "chars": 942,
    "preview": "import type { GlobalThemeOverrides } from 'naive-ui'\nimport { computed, watch } from 'vue'\nimport { darkTheme, useOsThem"
  },
  {
    "path": "src/icons/403.vue",
    "chars": 19850,
    "preview": "<template>\n  <div class=\"text-[#142D6E] dark:text-[#3a71ff]\">\n    <svg viewBox=\"0 0 400 300\" fill=\"none\" xmlns=\"http://w"
  },
  {
    "path": "src/icons/500.vue",
    "chars": 36264,
    "preview": "<template>\n  <div class=\"text-[currentColor] dark:text-[#3a71ff]\">\n    <svg viewBox=\"0 0 400 300\" fill=\"none\" xmlns=\"htt"
  },
  {
    "path": "src/locales/en-US.ts",
    "chars": 3558,
    "preview": "export default {\n  common: {\n    add: 'Add',\n    addSuccess: 'Add Success',\n    edit: 'Edit',\n    editSuccess: 'Edit Suc"
  },
  {
    "path": "src/locales/es-ES.ts",
    "chars": 3854,
    "preview": "export default {\n  common: {\n    add: 'Agregar',\n    addSuccess: 'Agregado con éxito',\n    edit: 'Editar',\n    editSucce"
  },
  {
    "path": "src/locales/index.ts",
    "chars": 914,
    "preview": "import type { App } from 'vue'\nimport { createI18n } from 'vue-i18n'\nimport enUS from './en-US'\nimport esES from './es-E"
  },
  {
    "path": "src/locales/ko-KR.ts",
    "chars": 2750,
    "preview": "export default {\n  common: {\n    add: '추가',\n    addSuccess: '추가 성공',\n    edit: '편집',\n    editSuccess: '편집 성공',\n    delet"
  },
  {
    "path": "src/locales/ru-RU.ts",
    "chars": 4088,
    "preview": "export default {\n  common: {\n    add: 'Добавить',\n    addSuccess: 'Добавлено успешно',\n    edit: 'Редактировать',\n    ed"
  },
  {
    "path": "src/locales/vi-VN.ts",
    "chars": 3727,
    "preview": "export default {\n  common: {\n    add: 'Thêm',\n    addSuccess: 'Thêm thành công',\n    edit: 'Sửa',\n    editSuccess: 'Sửa "
  },
  {
    "path": "src/locales/zh-CN.ts",
    "chars": 2513,
    "preview": "export default {\n  common: {\n    add: '添加',\n    addSuccess: '添加成功',\n    edit: '编辑',\n    editSuccess: '编辑成功',\n    delete:"
  },
  {
    "path": "src/locales/zh-TW.ts",
    "chars": 2537,
    "preview": "export default {\n  common: {\n    add: '新增',\n    addSuccess: '新增成功',\n    edit: '編輯',\n    editSuccess: '編輯成功',\n    delete:"
  },
  {
    "path": "src/main.ts",
    "chars": 434,
    "preview": "import { createApp } from 'vue'\nimport App from './App.vue'\nimport { setupI18n } from './locales'\nimport { setupAssets, "
  },
  {
    "path": "src/plugins/assets.ts",
    "chars": 447,
    "preview": "import 'katex/dist/katex.min.css'\nimport '@/styles/lib/tailwind.css'\nimport '@/styles/lib/highlight.less'\nimport '@/styl"
  },
  {
    "path": "src/plugins/index.ts",
    "chars": 131,
    "preview": "import setupAssets from './assets'\nimport setupScrollbarStyle from './scrollbarStyle'\n\nexport { setupAssets, setupScroll"
  },
  {
    "path": "src/plugins/scrollbarStyle.ts",
    "chars": 919,
    "preview": "import { darkTheme, lightTheme } from 'naive-ui'\n\nconst setupScrollbarStyle = () => {\n  const style = document.createEle"
  },
  {
    "path": "src/router/index.ts",
    "chars": 1071,
    "preview": "import type { App } from 'vue'\nimport type { RouteRecordRaw } from 'vue-router'\nimport { createRouter, createWebHashHist"
  },
  {
    "path": "src/router/permission.ts",
    "chars": 700,
    "preview": "import type { Router } from 'vue-router'\nimport { useAuthStoreWithout } from '@/store/modules/auth'\n\nexport function set"
  },
  {
    "path": "src/store/helper.ts",
    "chars": 72,
    "preview": "import { createPinia } from 'pinia'\n\nexport const store = createPinia()\n"
  },
  {
    "path": "src/store/index.ts",
    "chars": 150,
    "preview": "import type { App } from 'vue'\nimport { store } from './helper'\n\nexport function setupStore(app: App) {\n  app.use(store)"
  },
  {
    "path": "src/store/modules/app/helper.ts",
    "chars": 1013,
    "preview": "import { ss } from '@/utils/storage'\n\nconst LOCAL_NAME = 'appSetting'\n\nexport type Theme = 'light' | 'dark' | 'auto'\n\nex"
  },
  {
    "path": "src/store/modules/app/index.ts",
    "chars": 801,
    "preview": "import { defineStore } from 'pinia'\nimport type { AppState, Language, Theme } from './helper'\nimport { getLocalSetting, "
  },
  {
    "path": "src/store/modules/auth/helper.ts",
    "chars": 278,
    "preview": "import { ss } from '@/utils/storage'\n\nconst LOCAL_NAME = 'SECRET_TOKEN'\n\nexport function getToken() {\n  return ss.get(LO"
  },
  {
    "path": "src/store/modules/auth/index.ts",
    "chars": 1120,
    "preview": "import { defineStore } from 'pinia'\nimport { getToken, removeToken, setToken } from './helper'\nimport { store } from '@/"
  },
  {
    "path": "src/store/modules/chat/helper.ts",
    "chars": 558,
    "preview": "import { ss } from '@/utils/storage'\nimport { t } from '@/locales'\n\nconst LOCAL_NAME = 'chatStorage'\n\nexport function de"
  },
  {
    "path": "src/store/modules/chat/index.ts",
    "chars": 5569,
    "preview": "import { defineStore } from 'pinia'\nimport { defaultState, getLocalState, setLocalState } from './helper'\nimport { route"
  },
  {
    "path": "src/store/modules/index.ts",
    "chars": 143,
    "preview": "export * from './app'\nexport * from './chat'\nexport * from './user'\nexport * from './prompt'\nexport * from './settings'\n"
  },
  {
    "path": "src/store/modules/prompt/helper.ts",
    "chars": 429,
    "preview": "import { ss } from '@/utils/storage'\n\nconst LOCAL_NAME = 'promptStore'\n\nexport type PromptList = []\n\nexport interface Pr"
  },
  {
    "path": "src/store/modules/prompt/index.ts",
    "chars": 454,
    "preview": "import { defineStore } from 'pinia'\nimport type { PromptStore } from './helper'\nimport { getLocalPromptList, setLocalPro"
  },
  {
    "path": "src/store/modules/settings/helper.ts",
    "chars": 755,
    "preview": "import { ss } from '@/utils/storage'\n\nconst LOCAL_NAME = 'settingsStorage'\n\nexport interface SettingsState {\n  systemMes"
  },
  {
    "path": "src/store/modules/settings/index.ts",
    "chars": 593,
    "preview": "import { defineStore } from 'pinia'\nimport type { SettingsState } from './helper'\nimport { defaultSetting, getLocalState"
  },
  {
    "path": "src/store/modules/user/helper.ts",
    "chars": 809,
    "preview": "import { ss } from '@/utils/storage'\n\nconst LOCAL_NAME = 'userStorage'\n\nexport interface UserInfo {\n  avatar: string\n  n"
  },
  {
    "path": "src/store/modules/user/index.ts",
    "chars": 590,
    "preview": "import { defineStore } from 'pinia'\nimport type { UserInfo, UserState } from './helper'\nimport { defaultSetting, getLoca"
  },
  {
    "path": "src/styles/global.less",
    "chars": 143,
    "preview": "html,\nbody,\n#app {\n\theight: 100%;\n}\n\nbody {\n\tpadding-bottom: constant(safe-area-inset-bottom);\n\tpadding-bottom: env(safe"
  },
  {
    "path": "src/styles/lib/github-markdown.less",
    "chars": 25782,
    "preview": "html.dark {\n  .markdown-body {\n    color-scheme: dark;\n    --color-prettylights-syntax-comment: #8b949e;\n    --color-pre"
  },
  {
    "path": "src/styles/lib/highlight.less",
    "chars": 2603,
    "preview": "html.dark {\n\tpre code.hljs {\n\t\tdisplay: block;\n\t\toverflow-x: auto;\n\t\tpadding: 1em\n\t}\n\n\tcode.hljs {\n\t\tpadding: 3px 5px\n\t}"
  },
  {
    "path": "src/styles/lib/tailwind.css",
    "chars": 59,
    "preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n"
  },
  {
    "path": "src/typings/chat.d.ts",
    "chars": 956,
    "preview": "declare namespace Chat {\n\n\tinterface Chat {\n\t\tdateTime: string\n\t\ttext: string\n\t\tinversion?: boolean\n\t\terror?: boolean\n\t\t"
  },
  {
    "path": "src/typings/env.d.ts",
    "chars": 227,
    "preview": "/// <reference types=\"vite/client\" />\n\ninterface ImportMetaEnv {\n\treadonly VITE_GLOB_API_URL: string;\n\treadonly VITE_APP"
  },
  {
    "path": "src/typings/global.d.ts",
    "chars": 247,
    "preview": "interface Window {\n  $loadingBar?: import('naive-ui').LoadingBarProviderInst;\n  $dialog?: import('naive-ui').DialogProvi"
  },
  {
    "path": "src/utils/copy.ts",
    "chars": 502,
    "preview": "export function copyToClip(text: string) {\n  return new Promise((resolve, reject) => {\n    try {\n      const input: HTML"
  },
  {
    "path": "src/utils/functions/debounce.ts",
    "chars": 421,
    "preview": "type CallbackFunc<T extends unknown[]> = (...args: T) => void\n\nexport function debounce<T extends unknown[]>(\n  func: Ca"
  },
  {
    "path": "src/utils/functions/index.ts",
    "chars": 197,
    "preview": "export function getCurrentDate() {\n  const date = new Date()\n  const day = date.getDate()\n  const month = date.getMonth("
  },
  {
    "path": "src/utils/is/index.ts",
    "chars": 2149,
    "preview": "export function isNumber<T extends number>(value: T | unknown): value is number {\n  return Object.prototype.toString.cal"
  },
  {
    "path": "src/utils/request/axios.ts",
    "chars": 689,
    "preview": "import axios, { type AxiosResponse } from 'axios'\nimport { useAuthStore } from '@/store'\n\nconst service = axios.create({"
  },
  {
    "path": "src/utils/request/index.ts",
    "chars": 2090,
    "preview": "import type { AxiosProgressEvent, AxiosResponse, GenericAbortSignal } from 'axios'\nimport request from './axios'\nimport "
  },
  {
    "path": "src/utils/storage/index.ts",
    "chars": 1275,
    "preview": "interface StorageData<T = any> {\n  data: T\n  expire: number | null\n}\n\nexport function createLocalStorage(options?: { exp"
  },
  {
    "path": "src/views/chat/components/Header/index.vue",
    "chars": 2188,
    "preview": "<script lang=\"ts\" setup>\nimport { computed, nextTick } from 'vue'\nimport { HoverButton, SvgIcon } from '@/components/com"
  },
  {
    "path": "src/views/chat/components/Message/Avatar.vue",
    "chars": 1771,
    "preview": "<script lang=\"ts\" setup>\nimport { computed } from 'vue'\nimport { NAvatar } from 'naive-ui'\nimport { useUserStore } from "
  },
  {
    "path": "src/views/chat/components/Message/Text.vue",
    "chars": 4171,
    "preview": "<script lang=\"ts\" setup>\nimport { computed, onMounted, onUnmounted, onUpdated, ref } from 'vue'\nimport MarkdownIt from '"
  },
  {
    "path": "src/views/chat/components/Message/index.vue",
    "chars": 3706,
    "preview": "<script setup lang='ts'>\nimport { computed, ref } from 'vue'\nimport { NDropdown, useMessage } from 'naive-ui'\nimport Ava"
  },
  {
    "path": "src/views/chat/components/Message/style.less",
    "chars": 2961,
    "preview": ".markdown-body {\n\tbackground-color: transparent;\n\tfont-size: 14px;\n\n\tp {\n\t\twhite-space: pre-wrap;\n\t}\n\n\tol {\n\t\tlist-style"
  },
  {
    "path": "src/views/chat/components/index.ts",
    "chars": 62,
    "preview": "import Message from './Message/index.vue'\n\nexport { Message }\n"
  },
  {
    "path": "src/views/chat/hooks/useChat.ts",
    "chars": 698,
    "preview": "import { useChatStore } from '@/store'\n\nexport function useChat() {\n  const chatStore = useChatStore()\n\n  const getChatB"
  },
  {
    "path": "src/views/chat/hooks/useScroll.ts",
    "chars": 1200,
    "preview": "import type { Ref } from 'vue'\nimport { nextTick, ref } from 'vue'\n\ntype ScrollElement = HTMLDivElement | null\n\ninterfac"
  },
  {
    "path": "src/views/chat/hooks/useUsingContext.ts",
    "chars": 579,
    "preview": "import { computed } from 'vue'\nimport { useMessage } from 'naive-ui'\nimport { t } from '@/locales'\nimport { useChatStore"
  },
  {
    "path": "src/views/chat/index.vue",
    "chars": 15721,
    "preview": "<script setup lang='ts'>\nimport type { Ref } from 'vue'\nimport { computed, onMounted, onUnmounted, ref } from 'vue'\nimpo"
  },
  {
    "path": "src/views/chat/layout/Layout.vue",
    "chars": 1620,
    "preview": "<script setup lang='ts'>\nimport { computed } from 'vue'\nimport { NLayout, NLayoutContent } from 'naive-ui'\nimport { useR"
  },
  {
    "path": "src/views/chat/layout/Permission.vue",
    "chars": 1946,
    "preview": "<script setup lang='ts'>\nimport { computed, ref } from 'vue'\nimport { NButton, NInput, NModal, useMessage } from 'naive-"
  },
  {
    "path": "src/views/chat/layout/index.ts",
    "chars": 61,
    "preview": "import ChatLayout from './Layout.vue'\n\nexport { ChatLayout }\n"
  },
  {
    "path": "src/views/chat/layout/sider/Footer.vue",
    "chars": 740,
    "preview": "<script setup lang='ts'>\nimport { defineAsyncComponent, ref } from 'vue'\nimport { HoverButton, SvgIcon, UserAvatar } fro"
  },
  {
    "path": "src/views/chat/layout/sider/List.vue",
    "chars": 3756,
    "preview": "<script setup lang='ts'>\nimport { computed } from 'vue'\nimport { NInput, NPopconfirm, NScrollbar } from 'naive-ui'\nimpor"
  },
  {
    "path": "src/views/chat/layout/sider/index.vue",
    "chars": 2975,
    "preview": "<script setup lang='ts'>\nimport type { CSSProperties } from 'vue'\nimport { computed, ref, watch } from 'vue'\nimport { NB"
  },
  {
    "path": "src/views/exception/404/index.vue",
    "chars": 888,
    "preview": "<script lang=\"ts\" setup>\nimport { NButton } from 'naive-ui'\nimport { useRouter } from 'vue-router'\n\nconst router = useRo"
  },
  {
    "path": "src/views/exception/500/index.vue",
    "chars": 857,
    "preview": "<script lang=\"ts\" setup>\nimport { NButton } from 'naive-ui'\nimport { useRouter } from 'vue-router'\nimport Icon500 from '"
  },
  {
    "path": "start.cmd",
    "chars": 163,
    "preview": "cd ./service\nstart pnpm start > service.log &\necho \"Start service complete!\"\n\n\ncd ..\necho \"\" > front.log\nstart pnpm dev "
  },
  {
    "path": "start.sh",
    "chars": 182,
    "preview": "\ncd ./service\nnohup pnpm start > service.log &\necho \"Start service complete!\"\n\n\ncd ..\necho \"\" > front.log\nnohup pnpm dev"
  },
  {
    "path": "tailwind.config.js",
    "chars": 470,
    "preview": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  darkMode: 'class',\n  content: [\n    './index.html',\n   "
  },
  {
    "path": "tsconfig.json",
    "chars": 588,
    "preview": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"module\": \"ESNext\",\n    \"target\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\""
  },
  {
    "path": "vite.config.ts",
    "chars": 1316,
    "preview": "import path from 'path'\nimport type { PluginOption } from 'vite'\nimport { defineConfig, loadEnv } from 'vite'\nimport vue"
  }
]

About this extraction

This page contains the full source code of the Chanzhaoyu/chatgpt-web GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 136 files (288.7 KB), approximately 121.0k tokens, and a symbol index with 146 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!