提交 809eba8c 作者: fxjhello

增加前端views

上级 c613c41d
......@@ -85,6 +85,38 @@ def get_vs_path(local_doc_id: str):
def get_file_path(local_doc_id: str, doc_name: str):
return os.path.join(UPLOAD_ROOT_PATH, local_doc_id, doc_name)
async def single_upload_file(
file: UploadFile = File(description="A single binary file"),
knowledge_base_id: str = Form(..., description="Knowledge Base Name", example="kb1"),
):
saved_path = get_folder_path(knowledge_base_id)
if not os.path.exists(saved_path):
os.makedirs(saved_path)
file_content = await file.read() # 读取上传文件的内容
file_path = os.path.join(saved_path, file.filename)
if os.path.exists(file_path) and os.path.getsize(file_path) == len(file_content):
file_status = f"文件 {file.filename} 已存在。"
return BaseResponse(code=200, msg=file_status)
with open(file_path, "wb") as f:
f.write(file_content)
vs_path = get_vs_path(knowledge_base_id)
if os.path.exists(vs_path):
added_files = await local_doc_qa.add_files_to_knowledge_vector_store(vs_path, [file_path])
if len(added_files) > 0:
file_status = f"文件 {file.filename} 已上传并已加载知识库,请开始提问。"
return BaseResponse(code=200, msg=file_status)
else:
vs_path, loaded_files = await local_doc_qa.init_knowledge_vector_store([file_path], vs_path)
if len(loaded_files) > 0:
file_status = f"文件 {file.filename} 已上传至新的知识库,并已加载知识库,请开始提问。"
return BaseResponse(code=200, msg=file_status)
file_status = "文件上传失败,请重新上传"
return BaseResponse(code=500, msg=file_status)
async def upload_file(
files: Annotated[
......@@ -204,6 +236,24 @@ async def chat(
source_documents=source_documents,
)
async def no_knowledge_chat(
question: str = Body(..., description="Question", example="工伤保险是什么?"),
history: List[List[str]] = Body(
[],
description="History of previous questions and answers",
example=[
[
"工伤保险是什么?",
"工伤保险是指用人单位按照国家规定,为本单位的职工和用人单位的其他人员,缴纳工伤保险费,由保险机构按照国家规定的标准,给予工伤保险待遇的社会保险制度。",
]
],
),
):
for resp, history in local_doc_qa._call(
query=question, chat_history=history, streaming=True
):
pass
async def stream_chat(websocket: WebSocket, knowledge_base_id: str):
await websocket.accept()
......@@ -262,7 +312,9 @@ def main():
app = FastAPI()
app.websocket("/chat-docs/stream-chat/{knowledge_base_id}")(stream_chat)
app.post("/chat-docs/chat", response_model=ChatMessage)(chat)
app.post("/chat-docs/chatno", response_model=ChatMessage)(no_knowledge_chat)
app.post("/chat-docs/upload", response_model=BaseResponse)(upload_file)
app.post("/chat-docs/uploadone", response_model=BaseResponse)(single_upload_file)
app.get("/chat-docs/list", response_model=ListDocsResponse)(list_docs)
app.delete("/chat-docs/delete", response_model=BaseResponse)(delete_docs)
app.get("/", response_model=BaseResponse)(document)
......
{
"extends": ["@commitlint/config-conventional"]
}
**/node_modules
*/node_modules
node_modules
Dockerfile
.*
*/.*
!.env
# 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
docker-compose
kubernetes
module.exports = {
root: true,
extends: ['@antfu'],
}
"*.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
# 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
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no -- commitlint --edit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
strict-peer-dependencies=false
{
"recommendations": ["Vue.volar", "dbaeumer.vscode-eslint"]
}
{
"prettier.enable": false,
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"json",
"jsonc",
"json5",
"yaml",
"yml",
"markdown"
],
"cSpell.words": [
"antfu",
"axios",
"bumpp",
"chatgpt",
"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"
}
## 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)
# 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)
\ No newline at end of file
# 贡献指南
感谢你的宝贵时间。你的贡献将使这个项目变得更好!在提交贡献之前,请务必花点时间阅读下面的入门指南。
## 语义化版本
该项目遵循语义化版本。我们对重要的漏洞修复发布修订号,对新特性或不重要的变更发布次版本号,对重大且不兼容的变更发布主版本号。
每个重大更改都将记录在 `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)
# 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"]
server {
listen 80;
server_name localhost;
charset utf-8;
error_page 500 502 503 504 /50x.html;
# 防止爬虫抓取
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; #转发用户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;
}
### docker-compose 部署教程
- 将打包好的前端文件放到 `nginx/html` 目录下
- ```shell
# 启动
docker-compose up -d
```
- ```shell
# 查看运行状态
docker ps
```
- ```shell
# 结束运行
docker-compose down
```
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<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>langchain-ChatGLM</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>
## 增加一个Kubernetes的部署方式
```
kubectl apply -f deploy.yaml
```
### 如果需要Ingress域名接入
```
kubectl apply -f 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
MIT License
Copyright (c) 2023 fxj
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.
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "chatgpt-web",
"version": "2.11.0",
"private": false,
"description": "ChatGPT Web",
"author": "fxj",
"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": {
"@traptitech/markdown-it-katex": "^3.6.0",
"@vueuse/core": "^9.13.0",
"highlight.js": "^11.7.0",
"html2canvas": "^1.4.1",
"katex": "^0.16.4",
"markdown-it": "^13.0.1",
"naive-ui": "^2.34.3",
"pinia": "^2.0.33",
"qs": "^6.11.1",
"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",
"@types/qs": "^6.9.7",
"@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.2.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"
]
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
<svg id="openai-symbol" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M29.71,13.09A8.09,8.09,0,0,0,20.34,2.68a8.08,8.08,0,0,0-13.7,2.9A8.08,8.08,0,0,0,2.3,18.9,8,8,0,0,0,3,25.45a8.08,8.08,0,0,0,8.69,3.87,8,8,0,0,0,6,2.68,8.09,8.09,0,0,0,7.7-5.61,8,8,0,0,0,5.33-3.86A8.09,8.09,0,0,0,29.71,13.09Zm-12,16.82a6,6,0,0,1-3.84-1.39l.19-.11,6.37-3.68a1,1,0,0,0,.53-.91v-9l2.69,1.56a.08.08,0,0,1,.05.07v7.44A6,6,0,0,1,17.68,29.91ZM4.8,24.41a6,6,0,0,1-.71-4l.19.11,6.37,3.68a1,1,0,0,0,1,0l7.79-4.49V22.8a.09.09,0,0,1,0,.08L13,26.6A6,6,0,0,1,4.8,24.41ZM3.12,10.53A6,6,0,0,1,6.28,7.9v7.57a1,1,0,0,0,.51.9l7.75,4.47L11.85,22.4a.14.14,0,0,1-.09,0L5.32,18.68a6,6,0,0,1-2.2-8.18Zm22.13,5.14-7.78-4.52L20.16,9.6a.08.08,0,0,1,.09,0l6.44,3.72a6,6,0,0,1-.9,10.81V16.56A1.06,1.06,0,0,0,25.25,15.67Zm2.68-4-.19-.12-6.36-3.7a1,1,0,0,0-1.05,0l-7.78,4.49V9.2a.09.09,0,0,1,0-.09L19,5.4a6,6,0,0,1,8.91,6.21ZM11.08,17.15,8.38,15.6a.14.14,0,0,1-.05-.08V8.1a6,6,0,0,1,9.84-4.61L18,3.6,11.61,7.28a1,1,0,0,0-.53.91ZM12.54,14,16,12l3.47,2v4L16,20l-3.47-2Z"/></svg>
\ No newline at end of file
<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>
import request from './axios'
export const api = async (data: object): Promise<any> => {
return await request({
...data,
})
}
/* eslint-disable prefer-promise-reject-errors */
import type { AxiosResponse } from 'axios'
import axios from 'axios'
import { useMessage } from 'naive-ui'
const instance = axios.create({
// process.env.NODE_ENV === 'development' 来判断是否开发环境
baseURL: '/api',
/* process.env.USAGE === 'development'
?
window.baseApi ?? '/api'
: 'http://192.168.1.99:51798', */
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
// timeout: 5000,
})
const message = useMessage()
instance.interceptors.response.use(
(response: AxiosResponse) => {
return response
},
async (error) => {
if (error.response) {
switch (error.response.status) {
case 401:
// 返回 401 清除token信息并跳转到登录页面
message.error('401')
sessionStorage.removeItem('xtoken')
break
case 403:
message.error('403')
break
case 404:
message.error('404')
break
case 500:
message.error('500')
}
}
return await Promise.reject()
},
)
export default instance
import qs from 'qs'
import { api } from './api'
export const chat = (params: any) => {
return api({
url: '/chat-docs/chatno',
method: 'post',
data: JSON.stringify(params),
})
}
export const chatfile = (params: any) => {
return api({
url: '/chatfile',
method: 'post',
data: qs.stringify(params),
})
}
export const getfilelist = () => {
return api({
url: '/chat-docs/list',
method: 'get',
params: {
knowledge_base_id: '123',
},
})
}
export const deletefile = (params: any) => {
return api({
url: '/chat-docs/delete',
method: 'post',
data: JSON.stringify(params),
})
}
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 },
})
}
[
{
"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"
}
]
<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>
<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>
<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>
<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>
<script setup lang='ts'>
import { computed, onMounted, ref } from 'vue'
import { NSpin } from 'naive-ui'
import { fetchChatConfig } from '@/api'
import pkg from '@/../package.json'
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>
此项目开源于
<a
class="text-blue-600 dark:text-blue-500"
href="https://github.com/Chanzhaoyu/chatgpt-web"
target="_blank"
>
GitHub
</a>
,免费且基于 MIT 协议,没有任何形式的付费行为!
</p>
<p>
如果你觉得此项目对你有帮助,请在 GitHub 帮我点个 Star 或者给予一点赞助,谢谢!
</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>
<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="1" :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>
<script lang="ts" setup>
import { computed/* , ref */ } from 'vue'
import { NButton, 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: '简体中文', key: 'zh-CN', value: 'zh-CN' },
{ label: '繁體中文', key: 'zh-TW', value: 'zh-TW' },
{ label: 'English', key: 'en-US', value: 'en-US' },
{ label: '한국어', key: 'ko-KR', value: 'ko-KR' },
{ label: 'Русский язык', key: 'ru-RU', value: 'ru-RU' },
]
/* 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>
<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 { 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>
<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>
<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">
<h1 class="overflow-hidden font-bold text-3xl text-ellipsis whitespace-nowrap">
&nbsp;语链
</h1>
<!-- <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>
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 }
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
export function useBasicLayout() {
const breakpoints = useBreakpoints(breakpointsTailwind)
const isMobile = breakpoints.smaller('sm')
return { isMobile }
}
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,
}
}
import { computed } from 'vue'
import { enUS, koKR, zhCN, zhTW } from 'naive-ui'
import { useAppStore } from '@/store'
import { setLocale } from '@/locales'
export function useLanguage() {
const appStore = useAppStore()
const language = computed(() => {
switch (appStore.language) {
case 'en-US':
setLocale('en-US')
return enUS
case 'ru-RU':
setLocale('ru-RU')
return enUS
case 'ko-KR':
setLocale('ko-KR')
return koKR
case 'zh-CN':
setLocale('zh-CN')
return zhCN
case 'zh-TW':
setLocale('zh-TW')
return zhTW
default:
setLocale('zh-CN')
return zhCN
}
})
return { language }
}
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 {
common: {
primaryColor: '#EC5E42',
primaryColorHover: '#CF523A',
primaryColorPressed: '#963C2A',
},
}
})
watch(
() => isDark.value,
(dark) => {
if (dark)
document.documentElement.classList.add('dark')
else
document.documentElement.classList.remove('dark')
},
{ immediate: true },
)
return { theme, themeOverrides }
}
<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>
<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="36" y="32" width="329" height="237"><path d="M364.5 32H36v237h328.5V32Z" fill="#fff" /></mask><g mask="url(#a)"><g opacity=".4"><path fill-rule="evenodd" clip-rule="evenodd" d="M194.196 77.903c2.3.6 4.5 1.2 6.8 1.8 2.3.6 4.6 1.3 6.9 2 4.8 1.4 9.5 3.1 14 5.1 1.2.5 2.4 1.1 3.6 1.8.6.3 1.2.7 1.8 1.1.6.4 1.1.8 1.7 1.3 2 1.7 3.7 3.6 5.1 5.7 2.7 4 4.2 8.1 5.8 12 1.6 3.9 3 7.7 4.8 11.4 1.7 3.6 4 7 6.6 10.2 2.6 3.1 5.8 5.8 9.4 7.9 3.7 2.1 7.6 3.7 11.6 4.4 1.9.3 3.9.4 5.8.2 1.7-.2 3.4-.6 5-1.3 2.8-1.3 4.8-3.5 5.6-6.1.4-1.3.6-2.7.5-4-.1-1.4-.3-2.9-.8-4.2-.1-.4-.3-.7-.4-1.1l-.5-1.3c-.4-.8-.7-1.7-1.1-2.5-.8-1.6-1.7-3.2-2.7-4.8-2-3.2-4.5-6.3-7.1-9.8-2.9-3.7-5.4-7.5-7.6-11.5-2.2-4.2-3.9-8.5-5.1-12.9-.2-.5-.3-1.1-.4-1.6l-.4-1.5c-.2-.8-.4-1.6-.7-2.5-.5-1.7-1.1-3.3-1.8-4.9-2.8-6.4-7.1-12-12.9-16.2-3-2.2-6.2-4-9.8-5.4-1.8-.8-3.7-1.5-5.7-2-2-.6-4.1-1.1-6.1-1.6-2.1-.4-4.3-.8-6.5-1.1-1-.1-2.4-.3-3.6-.4-1.3-.1-2.5-.3-3.7-.5-4.9-.8-9.7-1.8-14.5-3.2-4.7-1.3-9.2-2.8-13.6-4.4-2.2-.8-4.4-1.6-6.6-2.3-2.1-.7-4.3-1.2-6.6-1.4-4.7-.3-9.3.3-13.8 1.5-4.4 1.3-8.4 3.5-11 6.5-.7.7-1.2 1.5-1.7 2.3-.5.8-.8 1.6-1.1 2.5-.6 1.7-.7 3.4-.6 5.1.4 3.4 2.1 6.6 4.4 9.5.1.2.3.4.4.5l.4.4c.3.3.6.5.9.8.6.5 1.3 1 2 1.5 1.5 1 3.2 1.8 4.9 2.5 3.8 1.6 7.8 2.9 11.9 4 8.4 2.3 17.4 4.3 26.5 6.5ZM210.2 95c2.2.4 4.3.9 6.3 1.4 1 .3 1.9.6 2.8.9.4.2.8.3 1.2.5.4.1.8.3 1.2.5 1.6.9 3.1 2 4.4 3.2 2.7 2.7 5 6.1 7 9.7 4.1 7.1 7.5 15 13 22.1 1.4 1.8 2.9 3.5 4.5 5.1 1.7 1.6 3.5 3.2 5.4 4.6 3.9 3 8.4 5.4 13.3 7.1 2.7.9 5.5 1.4 8.3 1.6 3.1.2 6.2-.1 9.1-.9 3.2-.8 6-2.2 8.5-4 2.5-1.9 4.5-4.2 6-6.7s2.4-5.2 2.7-7.9c.3-2.7.2-5.3-.3-7.9-.1-.7-.2-1.3-.4-1.9l-.5-1.7c-.3-1.1-.7-2.2-1.1-3.3-.9-2.2-1.8-4.3-2.9-6.4-2.1-4.1-4.6-7.9-6.8-11.3-2.1-3.2-3.9-6.6-5.5-10-1.5-3.3-2.6-6.7-3.4-10.2l-.6-2.8c-.2-1.2-.5-2.4-.9-3.5-.7-2.3-1.6-4.5-2.6-6.7-2-4.5-4.7-8.6-8.1-12.4-3.4-3.9-7.5-7.2-12.2-10-4.6-2.7-9.7-4.7-15.2-5.9-5.2-1.2-10.6-1.7-16-1.5-2.6.1-5.2.3-7.8.6-1.3.2-2.4.4-3.5.5-1.1.1-2.3.2-3.4.3-4.6.4-9.2.4-13.8 0-2.3-.2-4.6-.5-6.9-.9-2.3-.4-4.7-.8-6.9-1.4-2.2-.6-4.5-1.2-6.8-1.8-1.2-.3-2.3-.6-3.5-.8-1.2-.3-2.4-.4-3.7-.5-4.9-.2-9.8.2-15.2 1.4-5 1-9.6 3-13.5 5.9-1 .8-1.9 1.6-2.7 2.5-.8.9-1.6 1.9-2.2 2.9-1.3 2.2-2.2 4.4-2.6 6.8-.4 2.3-.4 4.7 0 7.1.4 2.3 1 4.6 2 6.8.2.3.3.5.4.8l.5.9c.4.6.7 1.1 1.1 1.7.8 1.1 1.6 2.1 2.6 3.1.9 1 1.9 1.9 3 2.7 1.1.8 2.1 1.6 3.2 2.3 4.3 2.7 8.8 4.9 13.7 6.7 9.4 3.4 18.9 5.5 28.2 7.2 2.4.4 4.7.8 7 1.2l6.9 1.1c2.3.4 4.5.8 6.7 1.2Z" fill="#EBEBEB" opacity=".4" /><path fill-rule="evenodd" clip-rule="evenodd" d="M156.302 100.995c-1.3.7-2.6 1.3-3.9 2l-3.8 2c-5 2.8-9.8 5.6-14.6 8.1-2.4 1.3-4.8 2.5-7.2 3.5-2.3 1-4.7 1.8-7.1 2.5-2.3.5-4.7 1-7.1 1.2-2.5.3-5 .5-7.6.5h-8.1l-8.4-.2c-6-.2-12 0-18 .6-3.3.3-6.5.9-9.7 1.7-3.3.9-6.6 2.1-9.6 3.6-3.1 1.5-5.9 3.5-8.4 5.7-.6.6-1.3 1.2-1.8 1.8-.5.6-1.1 1.3-1.6 2s-1.1 1.5-1.4 2.2c-.3.7-.7 1.5-1 2.3-1 3-1.3 6.1-.7 9.1.5 2.6 1.4 5.2 2.6 7.6 1.2 2.3 2.5 4.5 3.9 6.6 1.3 2.1 2.8 4.2 4.2 6.2 2.9 4.1 5.6 8.1 8 12.3 2.4 4.1 4.2 8.5 5.3 13.1l2.7 14.2.8 3.5c.3 1.2.5 2.3.8 3.5.3 1.2.6 2.3.9 3.5.3 1.2.6 2.3.9 3.5 1.2 4.7 2.8 9.4 4.7 14 1 2.3 2.2 4.7 3.5 6.9 1.4 2.3 3 4.5 4.8 6.6.9 1.1 2 2.1 3 3.1.5.4 1.1.9 1.7 1.4.3.2.6.5.9.7l1 .7c2.7 1.9 5.6 3.4 8.8 4.5.8.3 1.7.6 2.5.8.9.2 1.7.4 2.6.6.4.1.9.2 1.3.3l1.3.2 2.7.3 2.7.1h1.3l3.9-.2c.9-.1 1.7-.2 2.6-.3 1.7-.2 3.3-.5 5-.9l1.2-.3 1.2-.3 2.4-.7c.8-.2 1.5-.5 2.3-.7.8-.3 1.5-.5 2.3-.8 1.6-.6 3.1-1.1 4.6-1.8 3.2-1.3 6.3-2.9 9.1-4.8 3-2 5.6-4.3 7.9-6.8 2.2-2.4 4-4.9 5.6-7.6 1.5-2.5 2.7-5 3.8-7.3 2.2-4.7 4.2-9.2 6.2-13.6 4-8.8 8.7-17.4 13.9-25.7l1-1.6 1-1.6c.7-1 1.4-2.1 2.1-3.1l4.3-6.3c.8-1.1 1.5-2.3 2.2-3.4.7-1.1 1.4-2.3 2.1-3.4.7-1.2 1.4-2.3 2-3.5.6-1.2 1.2-2.3 1.8-3.5 1.2-2.4 2.2-4.8 3.2-7.2l.7-1.8.7-1.8c.5-1.2.8-2.5 1.2-3.7.7-2.5 1.4-5.1 1.8-7.6.5-2.6.8-5.1.9-7.7.4-5.1-.2-10.2-1.7-15.2-.4-1.2-.8-2.4-1.3-3.6l-.8-1.8c-.3-.6-.6-1.1-.9-1.7-1.2-2.3-2.7-4.5-4.5-6.5-1.8-2-3.8-3.8-6-5.5-.6-.4-1.1-.8-1.7-1.2l-1.8-1.2c-1.3-.7-2.7-1.4-4.1-1.9-3-1.1-6.2-1.5-9.5-1.4-3.2.2-6.3.8-9.3 1.8-2.9.9-5.6 2.1-8.3 3.4Zm3.401 4.503c-2.3 1.4-4.5 2.8-6.7 4.4-4.4 3.1-8.6 6.5-13.2 9.8-2.3 1.7-4.7 3.3-7.2 4.9-2.6 1.7-5.4 3.1-8.3 4.4-6.2 2.6-12.6 3.8-18.8 4.5-12.3 1.3-23.7.5-33.9 1.2-5.1.3-9.7 1-13.8 2.5-2.1.7-4 1.7-5.8 2.8-1.6 1-3 2.2-4 3.7-1 1.6-1.6 3.3-1.8 5.1-.2 2.1 0 4.2.5 6.3.5 2.2 1.1 4.4 1.9 6.6.8 2.2 1.7 4.4 2.7 6.6 1.9 4.4 4.1 8.8 6.1 13.2 1.9 4.4 3.5 8.9 4.7 13.6 1 4.601 2.2 9.201 3.8 13.701l.6 1.7.6 1.7c.4 1.1.9 2.2 1.3 3.3.9 2.2 1.9 4.4 2.9 6.5 2.1 4.2 4.6 8.3 7.6 12.1 1.4 1.9 3 3.6 4.7 5.3 1.6 1.6 3.4 3.1 5.3 4.5.9.7 1.9 1.2 2.8 1.8l1.5.8c.2.1.5.3.7.4l.8.3c1.9.9 3.9 1.5 6.1 1.8.5.1 1 .2 1.5.2l1.6.1c.3 0 .5.1.8.1H107.103l1.6-.1c.3 0 .5 0 .8-.1l.8-.1 1.6-.2 1.7-.4c2.3-.5 4.5-1.2 6.7-2 .6-.2 1.2-.4 1.7-.6.6-.2 1.2-.4 1.7-.7 1.1-.5 2.2-.9 3.2-1.4 2-.9 3.8-2 5.5-3.2 1.6-1.1 3-2.4 4.2-3.8 2.5-2.8 4.5-6.7 6.7-11 1.1-2.2 2.1-4.4 3.2-6.7s2.3-4.6 3.4-6.9c4.8-9.3 10.3-18.201 16.6-26.901l1.2-1.6 1.2-1.6c.8-1.1 1.6-2.1 2.4-3.2l4.8-6.1c1.5-2 3-3.9 4.5-5.9l2.1-3c.7-1 1.4-2 2-3 1.3-2 2.6-4.1 3.8-6.2l.9-1.6.8-1.6c.6-1 1.1-2.1 1.6-3.2 2-4.2 3.6-8.6 4.6-13 .5-2.2.8-4.4.9-6.6.1-2.2 0-4.4-.3-6.6-.6-4.3-2.4-8.5-5.2-12.2-1.4-1.8-3-3.5-4.9-5-.5-.4-.9-.8-1.4-1.1l-1.4-1c-1-.6-2-1.1-3.1-1.5-2.2-.8-4.6-1.2-7-1-2.5.2-5 .8-7.3 1.7-2.5 1-4.8 2.1-7 3.5ZM221.302 173l-.7 1.8-1.4 3.4c-.8 1.9-1.7 3.8-2.8 5.7-.2.4-.4.8-.7 1.2-.2.2-.3.4-.4.6l-.4.5c-.2.4-.5.7-.8 1.1l-.5.5-.6.6c-2.1 1.9-4.3 3.9-6.3 6-4.2 4.3-7.8 8.8-10.9 13.7-1.5 2.5-2.7 5.1-3.4 7.8-.6 2.7-.6 5.5.2 8.1.8 2.6 2.1 5.1 3.7 7.3s3.6 4.3 5.7 6.3c2.2 2 4.6 3.9 7.1 5.6 2.6 1.7 5.3 3.2 8.2 4.6.7.4 1.5.7 2.2 1 .7.3 1.4.6 2.2.9.8.3 1.5.6 2.3.8.7.2 1.5.5 2.3.7 3.2.9 6.5 1.5 9.8 1.8 3.4.3 6.8.3 10.2 0 3.5-.3 7-1 10.3-2.1.8-.2 1.7-.6 2.5-.9l1.2-.5.6-.3.6-.3 1.2-.6 1.2-.6c.8-.5 1.6-.9 2.3-1.5.7-.6 1.5-1.1 2.1-1.7.6-.6 1.3-1.2 1.9-1.8 1.1-1.2 2.2-2.5 3.1-3.9l.7-1c.1-.4.3-.7.5-1.1 1.1-2.2 1.7-4.5 1.9-6.9.1-2.9-.3-5.8-1.2-8.6-1.7-5.3-4.2-10-5.9-14.8-.5-1.2-.9-2.4-1.2-3.6-.1-.6-.3-1.2-.4-1.8l-.3-1.8c-.1-.6-.2-1.1-.2-1.7V195l.1-.8.2-1.8c.7-5.1 1.4-10.4 1.4-15.8-.1-1.3-.1-2.7-.2-4.1 0-.7-.1-1.4-.2-2.1-.2-.8-.3-1.5-.4-2.2-.3-1.5-.7-3-1.2-4.5-.6-1.7-1.4-3.4-2.3-5.3l-.5-.7c-.2-.2-.4-.5-.6-.7l-.6-.7c-.2-.2-.4-.5-.7-.7-.3-.3-.5-.4-.8-.7-.3-.3-.6-.5-.9-.7l-.5-.3c-.1-.1-.3-.2-.5-.3-.4-.2-.7-.4-1.1-.6-.8-.4-1.6-.7-2.4-1-.4-.1-.9-.2-1.3-.3l-.7-.1c-.2 0-.4-.1-.6-.1-.9-.1-1.7-.2-2.6-.2h-2c-1 0-2.1.1-3.1.3l-1.6.3-1.6.4c-1.1.3-2.1.6-3.2 1-.5.2-1.1.5-1.6.7l-.8.3-.8.4c-2 1-3.9 2.2-5.5 3.7-1.4 1.4-2.8 2.8-3.9 4.4-1 1.4-1.9 2.8-2.7 4.3-.7 1.4-1.4 2.7-1.9 4l-.8 1.9ZM114.904 93.995c.3-.2.6-.5.9-.7l.8-.8c1-1.1 1.7-2.4 1.9-3.8.1-1 .1-2-.1-3-.2-.8-.5-1.7-.8-2.5-1.3-3.1-3-5.5-4.5-8s-2.8-5.2-4.3-7.9-3-5.4-5.2-7.7c-2.1-2.3-4.8-4.2-7.8-5.6-3.2-1.4-6.7-2.2-10.3-2.4-3.8-.1-7.6.1-11.3.7-1.8.3-3.6.7-5.3 1.3-.8.3-1.6.7-2.4 1.1-.7.5-1.4 1-2 1.5-1.2 1.1-2.3 2.2-3.3 3.3-1.1 1.3-2 2.7-2.7 4.2-.8 1.7-1.3 3.4-1.4 5.2-.1 1.8 0 3.7.4 5.5.7 3.4 2.4 6.6 4.9 9.3 2.3 2.4 5 4.6 8 6.4 2.9 1.8 6 3.2 9.3 4.3 3.3 1.1 6.7 2 10.1 2.6 3.4.7 7 1.2 10.8 1.1 2.1 0 4-.2 6-.6.5-.1 1-.2 1.6-.4l.8-.2.8-.4c.5-.2 1.2-.4 1.7-.6.6-.2 1.1-.5 1.7-.8.6-.4 1.1-.7 1.7-1.1Z" fill="#EBEBEB" opacity=".4" /></g><path fill-rule="evenodd" clip-rule="evenodd" d="M348.6 255.2c0-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.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 fill-rule="evenodd" clip-rule="evenodd" d="m138.297 163.797 12.5-4.2c.5-.2.8-.8.6-1.3-.2-.5-.8-.8-1.3-.6l-12.5 4.2c-.5.2-.8.8-.6 1.3.1.4.5.7.9.7.1 0 .3-.1.4-.1ZM155.402 189.799c1.9-.7 3.5-2.1 4.4-3.9 1.8-3.8.3-8.4-3.5-10.3-1.9-.9-4-1.1-5.9-.4-1.9.7-3.5 2.1-4.4 3.9-1.8 3.8-.3 8.4 3.5 10.3 1.1.5 2.2.8 3.4.8.8 0 1.6-.1 2.5-.4Zm-4.299-12.599c-1.4.5-2.6 1.5-3.3 2.9-1.4 2.8-.2 6.2 2.6 7.6 1.4.7 2.9.8 4.3.3s2.6-1.5 3.3-2.9c1.4-2.8.2-6.2-2.6-7.6-.8-.4-1.7-.6-2.5-.6-.6 0-1.2.1-1.8.3ZM169.004 201.404l7.4-34.3c.1-.6-.3-1.1-.8-1.2-.6-.1-1.1.3-1.2.8l-7.4 34.3c-.1.6.3 1.1.8 1.2h.2c.4 0 .9-.3 1-.8Z" fill="#FFC412" /><path fill-rule="evenodd" clip-rule="evenodd" d="m52.497 200.297 13.3-4.8c.5-.2.8-.8.6-1.3-.2-.5-.8-.8-1.3-.6l-13.3 4.8c-.5.2-.8.8-.6 1.3.1.4.5.7.9.7.1 0 .2 0 .4-.1ZM143.801 197.6l-24.5-4c-.4 0-.8.2-1 .5-.3.3-.2.8 0 1.1l20.4 28.5c.2.2.5.4.8.4.1 0 .2 0 .3-.1.4-.1.7-.4.8-.8l4-24.5c.1-.5-.3-1-.8-1.1Zm-4.901 23 3.5-21.1-21.1-3.5 17.6 24.6Z" fill="#03D5B7" /><path fill-rule="evenodd" clip-rule="evenodd" d="M345.199 118.8c4.6 0 8.4-3.8 8.4-8.4-.1-4.6-3.9-8.4-8.1-8.4h-1.5V40c0-.3-.2-.5-.2-.8 0-.3-.1-.6-.2-.9 0-.1-.2-.4-.2-.6l-.3-.9c-.2-.2-.3-.4-.3-.6-.2-.3-.4-.6-.6-.8-.2-.1-.3-.4-.5-.6l-.6-.6c0-.1-.3-.3-.5-.3 0-.1-.1-.2-.2-.2-.3-.2-.5-.3-.8-.5-.2-.1-.5-.3-.8-.6-.1 0-.4-.2-.6-.2-.3-.1-.7-.1-.9-.3-.3 0-.6-.1-.8-.2h-2.6c-.2.2-.4.2-.6.2-.4.1-.7.1-.9.3-.3 0-.4.2-.6.5-.3 0-.6.1-.9.3-.1.2-.4.3-.6.5-.2.3-.5.4-.8.5-.2.2-.3.4-.5.6-.1.1-.1.3-.3.3l-56.5 70.2-.3.3c-2.7 3.6-2 8.7 1.6 11.4 1.4 1.2 3.3 1.9 5.2 1.8h48.1v11.6c.2 4.5 3.8 8.1 8.2 8.2 4.6.1 8.5-3.6 8.6-8.2v-11.6h1.6Zm-48.8-16.8h30.4V64.4h-.1l-30.3 37.6Z" fill="#FFC412" /><path fill-rule="evenodd" clip-rule="evenodd" d="M131.299 73.299c.4-2.5-1.2-4.9-3.7-5.3-2.5-.4-4.9 1.2-5.3 3.7l-1.6 8.7c-.4 2.5 1.2 4.9 3.7 5.3 2.4.5 4.8-1.2 5.3-3.7l1.6-8.7ZM233.795 75.195c-.4-2.5-2.7-4.2-5.2-3.9-2.5.4-4.2 2.7-3.9 5.2l1.3 8.7c.4 2.5 2.7 4.2 5.2 3.9 2.5-.2 4.2-2.6 3.9-5.1l-1.3-8.8ZM148.195 46.795c1.8-1.7 1.9-4.6.2-6.5-1.7-1.8-4.6-2-6.5-.2l-3.5 3.2c-1.8 1.7-1.9 4.6-.2 6.5 1.8 1.9 4.7 2 6.5.2l3.5-3.2ZM146.395 118.8c-1.7-1.9-4.6-2-6.5-.2-1.8 1.7-1.9 4.6-.2 6.5l3.2 3.5c1.7 1.8 4.6 1.9 6.5.2 1.8-1.8 1.9-4.7.2-6.6l-3.2-3.4ZM220.002 49.602l-5.1 3.5c-.7.5-.9 1.4-.4 2.1l6.3 9c.5.7 1.4.9 2.1.4l5.1-3.5c.7-.5.9-1.4.4-2.1l-6.3-9c-.5-.6-1.4-.8-2.1-.4ZM200.501 136.801c2.4-.9 3.6-3.5 2.7-5.9-.9-2.4-3.5-3.6-5.9-2.7l-4.5 1.6c-2.4.9-3.6 3.5-2.7 5.9.9 2.3 3.5 3.6 5.9 2.7l4.5-1.6Z" fill="#FFC727" /><path fill-rule="evenodd" clip-rule="evenodd" d="M169.054 32.975c-28.729 4.998-47.967 32.34-42.969 61.069 4.998 28.729 32.34 47.967 61.069 42.969 28.728-4.998 47.966-32.34 42.968-61.069-4.998-28.728-32.34-47.967-61.068-42.969Z" fill="#FFC412" /><path opacity=".2" fill-rule="evenodd" clip-rule="evenodd" d="M169.7 60.9c-.8.7-1.7 1.3-2.7 1.8-2.1.9-4.5 1.4-6.8 1.3 2.4 1 5.1 1.2 7.5.4 2.6-.9 4.7-2.6 6-5 1.3-2.2 1.9-4.8 1.8-7.4-.1-2.5-.9-5-2.5-7 .7 2.2 1 4.6.8 6.9-.1 1.1-.4 2.2-.7 3.3-.2.6-.4 1.1-.6 1.6-.3.5-.5 1-.8 1.5-.6 1-1.2 1.8-2 2.6ZM160.495 49.397c.6-1.2 1.7-2.2 2.9-2.9 1.4-.7 2.9-1.1 4.5-1.2-1.6-.8-3.5-1-5.2-.5-1.8.6-3.4 1.8-4.3 3.5-.9 1.6-1.3 3.4-1.2 5.2.1 1.8.8 3.5 2 4.8-.7-3-.3-6.1 1.3-8.9ZM177.7 119.8c-.4 1-1 1.8-1.7 2.6s-1.5 1.3-2.5 1.5c-1.1.2-2.2.3-3.3.1 1 .8 2.2 1.2 3.5 1.2 1.4 0 2.7-.6 3.6-1.6.9-.9 1.5-2.1 1.7-3.4.2-1.2 0-2.5-.7-3.6 0 1.2-.2 2.3-.6 3.2ZM172.895 118.5c.4-.5.9-.8 1.5-1 .7-.2 1.4-.3 2.2-.3-.6-.7-1.5-1.1-2.4-1.1-1 0-1.9.4-2.6 1.1-.7.7-1.1 1.6-1.2 2.5-.1.9.1 1.8.7 2.5.1-1.4.7-2.7 1.8-3.7ZM147.6 105.6c.3.8.4 1.7.2 2.6-.1.8-.6 1.6-1.2 2.2-.8.8-1.7 1.5-2.6 2.1 1.2.2 2.5 0 3.6-.6 1.3-.7 2.2-1.9 2.4-3.3.2-1.4-.1-2.8-.9-3.9-.8-1-1.9-1.7-3.2-1.9.6.9 1.2 1.8 1.7 2.8ZM142.498 108.695c-.1-.5-.1-1.1 0-1.6 0-.5.3-1 .6-1.4.5-.5 1-1.1 1.6-1.6-.8-.3-1.8-.2-2.6.2-.9.4-1.6 1.3-1.8 2.4-.2 1.1.1 2.1.7 2.9.6.8 1.5 1.2 2.4 1.2-.4-.7-.6-1.3-.9-2.1ZM204.9 52.8c-.3.8-.9 1.5-1.6 2.1-.7.6-1.5.9-2.4.9-1.1 0-2.2 0-3.3-.2.8 1 1.9 1.7 3.1 2 1.4.3 2.9 0 4-.9 1.1-.8 1.8-2.1 1.9-3.5.1-1.3-.3-2.6-1.1-3.6-.1 1.1-.3 2.2-.6 3.2ZM199.204 51.795c.2-.39.6-.89 1-1.19.4-.31.9-.5 1.4-.61.8-.09 1.5-.2 2.3-.2-.5-.8-1.2-1.4-2.1-1.6-1.1-.3-2.2-.09-3 .6-.8.61-1.3 1.61-1.4 2.61-.1 1 .3 1.9 1 2.5.1-.7.4-1.4.8-2.11Z" fill="#000" /><path fill-rule="evenodd" clip-rule="evenodd" d="M201.795 81.395c-1.2-6.3-7.3-10.5-13.6-9.3-6.3 1.2-10.5 7.3-9.3 13.6 1.2 6.3 7.3 10.5 13.6 9.3 6.3-1.2 10.5-7.3 9.3-13.6Z" fill="#FFC727" /><path opacity=".1" fill-rule="evenodd" clip-rule="evenodd" d="M201.795 81.395c-1.2-6.3-7.3-10.5-13.6-9.3-6.3 1.2-10.5 7.3-9.3 13.6 1.2 6.3 7.3 10.5 13.6 9.3 6.3-1.2 10.5-7.3 9.3-13.6Z" fill="#000" /><path opacity=".2" fill-rule="evenodd" clip-rule="evenodd" d="M200.903 78.502c-2.8-5.8-9.7-8.3-15.5-5.6-5.8 2.8-8.3 9.7-5.6 15.5 1.5 3.1 4.3 5.5 7.6 6.3-4.7-3.5-5.7-10.2-2.2-14.9 1.6-2 3.9-3.5 6.5-4 3.3-.6 6.7.4 9.2 2.7ZM203.4 92.5c-.7 1.2-1.5 2.3-2.5 3.2-.5.5-1 .9-1.6 1.3-.6.4-1.2.7-1.8 1-2.7 1.1-5.6 1.6-8.5 1.4 2.9 1.1 6.1 1.2 9.1.2 3.1-1.1 5.6-3.2 7.2-6.1 1.6-2.8 2.4-5.9 2.3-9.1-.1-3.1-1.1-6.1-2.9-8.6.9 2.8 1.3 5.7 1.1 8.7-.1 1.4-.4 2.8-.8 4.1-.2.7-.4 1.4-.7 2-.2.7-.6 1.3-.9 1.9Z" fill="#000" /><path fill-rule="evenodd" clip-rule="evenodd" d="M220.198 66.498c-.8-2.3-3.4-3.3-5.7-2.5-2.3.8-3.3 3.4-2.5 5.7 1 2.3 3.5 3.4 5.7 2.5 2.2-.9 3.3-3.4 2.5-5.7Z" fill="#FFC727" /><path opacity=".1" fill-rule="evenodd" clip-rule="evenodd" d="M220.198 66.498c-.8-2.3-3.4-3.3-5.7-2.5-2.3.8-3.3 3.4-2.5 5.7 1 2.3 3.5 3.4 5.7 2.5 2.2-.9 3.3-3.4 2.5-5.7Z" fill="#000" /><path opacity=".2" fill-rule="evenodd" clip-rule="evenodd" d="M219.704 65.505c-1.4-1.9-4.2-2.3-6.1-.9-1.9 1.4-2.3 4.2-.9 6.1.8 1 1.9 1.7 3.2 1.8-2-.9-2.8-3.3-1.9-5.3.4-.9 1.1-1.6 2.1-2 1.2-.5 2.5-.4 3.6.3ZM221.3 70.4c-.3 1-.9 1.8-1.7 2.4-.8.6-1.8 1-2.9 1.2 1.1.2 2.3.1 3.4-.5s1.9-1.6 2.3-2.8c.4-1.1.5-2.3.2-3.5-.3-1.1-.9-2.1-1.8-2.9 1 1.9 1.2 4.1.5 6.1Z" fill="#000" /><path fill-rule="evenodd" clip-rule="evenodd" d="M211.302 104.002c-1.8-2.9-5.6-3.8-8.5-2-2.9 1.8-3.8 5.6-2 8.5 1.8 2.9 5.6 3.8 8.5 2 2.9-1.8 3.8-5.6 2-8.5Z" fill="#FFC727" /><path opacity=".1" fill-rule="evenodd" clip-rule="evenodd" d="M211.302 104.002c-1.8-2.9-5.6-3.8-8.5-2-2.9 1.8-3.8 5.6-2 8.5 1.8 2.9 5.6 3.8 8.5 2 2.9-1.8 3.8-5.6 2-8.5Z" fill="#000" /><path opacity=".2" fill-rule="evenodd" clip-rule="evenodd" d="M210.401 102.801c-2.5-2.4-6.4-2.3-8.7.2-2.4 2.5-2.3 6.4.2 8.7 1.3 1.3 3.1 1.9 4.9 1.7-3-.8-4.7-4-3.9-7 .3-1.3 1.2-2.4 2.4-3.2 1.5-.9 3.4-1.1 5.1-.4ZM213.9 104.8c.4 1.4.5 2.9.3 4.4-.2 1.5-.9 2.9-1.9 3.9-1.1 1.1-2.4 1.9-3.9 2.3 1.6 0 3.2-.6 4.5-1.6 1.3-1.1 2.2-2.7 2.4-4.4.2-1.7 0-3.3-.6-4.9-.6-1.5-1.6-2.8-3-3.7 1 1.1 1.8 2.5 2.2 4Z" fill="#000" /><path fill-rule="evenodd" clip-rule="evenodd" d="M141.898 71.098c-5.4.1-9.6 4.2-9.5 8.9.1 4.7 4.6 8.5 10 8.3 5.4-.1 9.6-4.1 9.5-8.9-.2-4.8-4.6-8.4-10-8.3Z" fill="#FFC727" /><path opacity=".1" fill-rule="evenodd" clip-rule="evenodd" d="M141.898 71.098c-5.4.1-9.6 4.2-9.5 8.9.1 4.7 4.6 8.5 10 8.3 5.4-.1 9.6-4.1 9.5-8.9-.2-4.8-4.6-8.4-10-8.3Z" fill="#000" /><path opacity=".2" fill-rule="evenodd" clip-rule="evenodd" d="M144.598 73.805c2.2-.1 4.4.6 6.1 1.9-1.9-3.1-5.3-4.9-8.9-4.7-5.4.1-9.6 4.1-9.5 8.9.1 4.1 3.4 7.4 7.6 8.2-2.2-1.3-3.7-3.6-3.8-6.2-.1-4.4 3.8-8 8.5-8.1Z" fill="#000" /><path opacity=".2" fill-rule="evenodd" clip-rule="evenodd" d="M154.3 77.7c.3 1 .3 2.1.2 3.1-.1 1-.4 2.1-.8 3-.9 1.8-2.4 3.4-4.2 4.5-2 1.2-4 2.2-6.2 2.9 2.4.1 4.8-.4 7-1.4 1.1-.6 2.2-1.3 3.1-2.1 1-.9 1.7-1.9 2.3-3.1.5-1.1.7-2.4.8-3.7 0-1.3-.2-2.6-.7-3.8-1-2.2-2.8-4.1-5.1-5 1.7 1.5 2.9 3.5 3.6 5.6Z" fill="#000" /><path fill-rule="evenodd" clip-rule="evenodd" d="M107.399 32.6c-.1 0-.4-.2-.6-.2-.3-.1-.7-.1-.9-.3-.3 0-.6-.1-.8-.2h-2.6c-.2.2-.4.2-.6.2-.4.1-.7.1-.9.3-.3 0-.4.2-.6.5-.3 0-.6.1-.9.3-.1.2-.4.3-.6.5-.2.3-.5.4-.8.5-.2.2-.3.4-.5.6-.1.1-.1.3-.3.3l-56.4 70.2-.3.3c-2.7 3.6-2 8.7 1.6 11.4 1.4 1.2 3.3 1.9 5.2 1.8h48.2v11.6c.2 4.5 3.8 8.1 8.2 8.2 4.6.1 8.5-3.6 8.6-8.2v-11.6h1.6c4.6 0 8.4-3.8 8.4-8.4-.1-4.6-3.9-8.4-8.3-8.4h-1.5V40c0-.3-.2-.5-.2-.8 0-.3-.1-.6-.2-.9 0-.1-.2-.4-.2-.6l-.3-.9c-.2-.2-.3-.4-.3-.6-.2-.3-.4-.6-.6-.8-.2-.1-.3-.4-.5-.6l-.6-.6c0-.1-.3-.3-.5-.3 0-.1-.1-.2-.2-.2-.3-.2-.5-.3-.8-.5-.2-.1-.5-.3-.8-.6Zm-42.2 69.4h30.4l-.1-37.6L65.2 102Z" fill="#FFC412" /><path fill-rule="evenodd" clip-rule="evenodd" d="M339.003 241.599c.6-.1.9-.6.8-1.1-11.4-61.2-31-97.9-31.2-98.3-.3-.5-.8-.7-1.3-.4-.5.3-.6.8-.4 1.3.2.4 19.6 36.9 31 97.7.1.5.5.8.9.8h.2Z" fill="#142D6E" /><path fill-rule="evenodd" clip-rule="evenodd" d="M364.5 240.7c0-.5-.4-.9-.9-.9H106.1c-.5 0-.9.4-.9.9s.4.9.9.9h257.5c.5 0 .9-.4.9-.9ZM100.9 240.7c0-.5-.4-.9-.9-.9H47.5c-.5 0-.9.4-.9.9s.4.9.9.9H100c.5 0 .9-.4.9-.9ZM259.3 120.303l-4.7 8.5c-7.5 16.4-24.5 51.2-27.4 53.9-.7.7-4.2 3.4-8.6 6.8-26.3 20.5-42.7 33.9-42.8 37.5 0 .5.2.9.5 1.2.1.1.3.2.4.2 1.6.4 37.8 10.1 47.6 10.1h1.6c7.5-1.3 31.1-33.4 36.1-44.5.2-.5 0-1.1-.5-1.3-.5-.2-1.1 0-1.3.5-5.2 11.5-28.4 42.3-34.7 43.4-5.2 1-33-5.9-47.6-9.8 2-4.6 30.9-27.2 42-35.8 4.8-3.7 8-6.2 8.7-6.9 3.3-3.2 21-39.5 27.8-54.5l4.6-8.4c.2-.5.1-1-.4-1.3-.5-.2-1-.1-1.3.4Z" fill="#142D6E" /><path fill-rule="evenodd" clip-rule="evenodd" d="m195.898 177.404 9.1 39.2c.6 2.4 3 4 5.5 3.4l7.1-1.7c2.4-.6 4-3 3.4-5.5l-9.1-39.2c-.6-2.4-3-4-5.5-3.4l-7.1 1.7c-2.5.6-4 3.1-3.4 5.5Z" fill="#fff" /><path fill-rule="evenodd" clip-rule="evenodd" d="M199.104 171.198c-2.9.7-4.8 3.6-4.1 6.6l9.1 39.2c.4 1.4 1.2 2.6 2.5 3.4.9.5 1.9.8 2.9.8.4 0 .8-.1 1.3-.1l7.1-1.7c2.9-.7 4.8-3.6 4.1-6.6l-9.1-39.2c-.4-1.4-1.2-2.6-2.5-3.4-1.3-.8-2.8-1-4.2-.7l-7.1 1.7Zm8.392-.098c-.3 0-.5 0-.8.1l-7.1 1.7c-2 .5-3.2 2.4-2.7 4.4l9.1 39.2c.5 2 2.4 3.2 4.4 2.7l7.1-1.7c2-.5 3.2-2.4 2.7-4.4l-9.1-39.2c-.5-1.6-2-2.8-3.6-2.8Z" fill="#142D6E" /><path fill-rule="evenodd" clip-rule="evenodd" d="M198.001 180.101c0 .3.2.5.4.6.2.1.3.1.5.1h.2l10.6-2.5c.3 0 .5-.2.6-.4.1-.2.2-.5.1-.7l-7.6-32.9c-.1-.5-.6-.8-1.1-.7l-10.6 2.5c-.5.1-.8.6-.7 1.1l7.6 32.9Zm1.599-1.501 8.8-2-7.2-31-8.8 2 7.2 31Z" fill="#142D6E" /><path fill-rule="evenodd" clip-rule="evenodd" d="M134.599 106.895c6.7 28.7 35.3 46.5 64 39.8 28.7-6.7 46.5-35.3 39.8-63.9-6.7-28.7-35.3-46.5-64-39.9-28.7 6.7-46.5 35.3-39.8 64Zm92.906-21.5c-5.2-22.7-27.801-36.7-50.501-31.5-22.7 5.2-36.799 27.9-31.5 50.501 5.2 22.7 27.9 36.8 50.5 31.5 22.6-5.3 36.7-27.9 31.501-50.5Z" fill="#03D5B7" /><g opacity=".3"><path fill-rule="evenodd" clip-rule="evenodd" d="M176.904 53.801c-22.7 5.3-36.8 27.9-31.5 50.5 5.2 22.7 27.9 36.8 50.5 31.5 22.7-5.2 36.8-27.9 31.5-50.5-5.2-22.6-27.8-36.7-50.5-31.5Z" fill="#fff" opacity=".3" /><path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 136.9c23.1 1.1 42.7-16.6 44-39.7l-44 39.7ZM188.9 52.8 144.4 93c-.2 3.8.1 7.7 1 11.4.8 3.3 1.9 6.4 3.4 9.4l60.3-54.4c-6.1-3.9-13-6.1-20.2-6.6ZM222.899 73.7l-61.1 55.2c2.2 1.7 4.6 3 7.1 4.2l57.4-51.8c-.9-2.6-2-5.1-3.4-7.6ZM212.499 61.8l-61.6 55.6c.2.5.5.9.8 1.3l62-55.9c-.3-.3-.7-.6-1.2-1ZM171.399 55.6l-22.4 20.2c-.6 1.1-1.1 2.2-1.6 3.4l27.5-24.8c-1.3.3-2.4.7-3.5 1.2Z" fill="#EBEBEB" opacity=".3" /></g><path fill-rule="evenodd" clip-rule="evenodd" d="M156.404 100.204c0 .53.05 1.06.13 1.51-.01-.07.01-.04.07.19v.2c.4 1.7-.6 3.4-2.4 3.7-1.6.5-3.4-.4-3.9-2.1-.2-1.4-.4-2.8-.5-4.2-.4-6.4.6-12.8 2.9-18.8 3.5-8.9 10.3-15.9 18.7-20.4 1.5-.9 3.5-.5 4.4 1 .9 1.5.5 3.5-1 4.4-1.3.7-2.6 1.5-3.8 2.3-.6.4-1.1.8-1.7 1.2l-.8.7c0 .04-.09.11-.16.17.22-.14-.14.13-.14.13-1.8 1.6-3.6 3.4-5.1 5.3l-.463.648c.018-.025-.004.02-.137.252-.3.4-.5.8-.8 1.2-.5.8-1.1 1.8-1.5 2.6-.4.8-.8 1.7-1.2 2.5l-.3.7c-.07.22-.07.26-.05.23-.11.28-.27.62-.35.97-.5 1.6-1 3.2-1.3 4.9-.2.8-.3 1.5-.4 2.3l-.1.6c-.009.008-.01.024-.01.035v.005c-.02.28-.09.6-.09.86-.2 2.3-.2 4.6 0 6.9Zm2.25-16.57c.02-.05.03-.09.05-.13.13-.34-.01.05-.05.13Zm9.64-13.523.012-.009-.002.002-.01.007Zm-5.761 6.212c.03-.048.089-.144.108-.17l-.037.051-.088.146.017-.027ZM155.502 109.102c-1.7.5-2.6 2.3-2.1 3.9s2.3 2.6 3.9 2.1 2.6-2.3 2.1-3.9c-.5-1.7-2.3-2.6-3.9-2.1Z" fill="#03D5B7" /><path fill-rule="evenodd" clip-rule="evenodd" d="M295.497 129.998c-4.5-16.2-16.4-23.7-19.3 1.2-2.1 17.9-5 48.4-6.4 47.9-.7-.2-24.5-4.7-32.3-8-6.4-3.3-25.4-12.9-31.6-13.4-6.4-.4-19.1 1.1-21.7 5.3-2.3 3.8.7 7.2.7 7.2-2 1-3.5 2.6-4.4 4.7-1.4 3.6 3.7 7.4 3.7 7.4-1.8 1.5-3.2 3.4-3.9 5.7-1 3.6 3.6 8.2 3.6 8.2-.9 2.3-1 4.8-.3 7.1 2.3 6.1 12.1 8.2 18.3 9.5 4.8 1 17.3.1 24.3-.5 21.3 4.3 53.1 10.9 62.8 6.1 4.2-2.1 10.8-8.4 13.6-17.6 4.4-14.4.7-42.7-7.1-70.8Z" fill="#fff" /><path fill-rule="evenodd" clip-rule="evenodd" d="m225.099 213.602 1.2-.1 1.6.3c16.2 3.2 36.8 7.4 50.4 7.4 4.8 0 8.7-.5 11.1-1.7 4.9-2.4 11.4-9.1 14.1-18.2 4.1-13.8 1.4-40.5-7.2-71.5-2.6-9.6-7.7-16-12.4-15.6-3.1.3-7.2 3.4-8.7 17-.4 3.9-.9 8.4-1.4 13-1.4 13.1-3.3 30.7-4.4 34-.4-.1-.9-.2-1.8-.4-6.4-1.3-23.4-4.7-29.7-7.4-7.6-3.9-25.7-13-31.9-13.4-6.6-.4-19.7 1.1-22.6 5.8-1.8 2.8-.9 5.7.1 7.4-1.7 1.1-3.1 2.7-3.9 4.6-1.3 3.3 1.6 6.4 3.1 7.8-1.5 1.5-2.7 3.3-3.3 5.4-1 3.4 2.2 7.3 3.4 8.6-.8 2.3-.9 4.8-.1 7.2 2.5 6.5 12.2 8.6 18.7 10l.4.1c4.7 1.1 15.9.2 23.3-.3Zm42.103-34.103c-6.4-1.3-23.5-4.8-30-7.5 0 0-.1 0-.1-.1-5.8-3-25.1-12.8-31.2-13.3-6.9-.5-18.7 1.4-20.9 4.9-1.9 3.1.6 6.1.6 6.1.2.2.3.5.2.8-.1.3-.2.5-.5.7-1.8.9-3.2 2.4-4 4.2-1.1 2.8 3.3 6.2 3.4 6.3.2.2.4.5.4.7 0 .3-.1.6-.4.8-1.7 1.3-2.9 3.1-3.6 5.2-.7 2.5 2.2 6 3.4 7.2.3.3.4.7.2 1-.9 2.1-1 4.3-.3 6.5 2.1 5.5 11.2 7.5 17.3 8.8l.4.1c4.4 1 15.9.1 22.8-.4H226.502l1.8.4c20.4 4.1 51.3 10.4 60.4 5.8 3.3-1.6 10.2-7.2 13.1-17.1 3.9-13.2 1.1-40.2-7.2-70.4-2.3-8.4-6.8-14.5-10.5-14.2-3.4.3-5.9 5.8-7 15.3-.4 3.9-.9 8.4-1.4 13-3.1 28.5-4 34.8-5.3 35.6-.3.2-.6.2-.9.1-.1 0-1.1-.2-2.3-.5Z" fill="#142D6E" /><path fill-rule="evenodd" clip-rule="evenodd" d="M273.699 137.5c10 .8 13.4-9.9 13.4-9.9l-8.7-32.6s10.9.3 10.5-7.9c0 0 .1-3.5-2.3-5.2 0 0 5.3-28.5-16.4-34.2-21.7-5.7-32 3.8-35.1 15.7-3.1 12-7.7 33.2 3.5 42.1 7.5 5.9 14 3.9 18 3.5l4.3 13.3s3.4 13.8 12.8 15.2Z" fill="#fff" /><path fill-rule="evenodd" clip-rule="evenodd" d="M259.999 122.598c.2.6 3.7 14.4 13.7 15.9h1.1c9.8 0 13.2-10.5 13.2-10.6v-.5l-8.4-31.4c2-.1 5.8-.7 8.2-3.1 1.5-1.5 2.1-3.5 2-5.7 0-.2.1-3.6-2.2-5.6.6-4.2 3.4-29.3-17.2-34.8-10.5-2.8-19.1-2.3-25.6 1.5-5.2 3.1-8.9 8.2-10.6 14.9-3.9 15.5-7.1 34.4 3.9 43.1 6.8 5.4 12.9 4.5 16.9 3.9.4 0 .7-.1 1-.1l4 12.5Zm-14.201-72.502c-4.8 2.8-8.1 7.6-9.7 13.7-2.2 8.8-8.2 32.1 3.2 41.2 6.1 4.9 11.5 4.1 15.4 3.5.7-.2 1.3-.3 1.9-.3.4 0 .8.3.9.7l4.3 13.3c.1.2 3.4 13.2 12.1 14.5 8.3.6 11.7-7.5 12.2-9l-8.6-32.3c-.1-.3 0-.6.2-.8.2-.2.5-.4.8-.4.1 0 5.4.1 8-2.6 1.1-1.1 1.6-2.5 1.5-4.3 0-.1 0-3.1-1.9-4.4-.3-.2-.5-.6-.4-.9.2-1.1 4.9-27.7-15.7-33.2-10-2.7-18.1-2.2-24.2 1.3Z" fill="#142D6E" /><path fill-rule="evenodd" clip-rule="evenodd" d="M215.8 91.5s10 10.3 19.8 11.3c-.22-.13-6.77-12.65-3.1-29.2l-16.7 17.9ZM272.7 79.695s-4.1 8.1-.7 10.1c3.2 1.9 6.6.5 10.8-8.6 0 0 1.9.4 3 1.4 2.9 2.8 6.7 8.9-6.7 12.5l2.9 14s21.1 6.4 33.1-3.4c0 0 2.2-74.4-45.6-68.8 0 0-27.7-10.6-39.2 21.7 0 0 16.6 10.1 30.1-2.7 0 0-3.2 22.3 12.3 23.8Z" fill="#142D6E" /><path fill-rule="evenodd" clip-rule="evenodd" d="M285.9 89.5c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5Z" fill="#FFC412" /><path fill-rule="evenodd" clip-rule="evenodd" d="M236.1 65.4c-3.92 0-7.1 3.18-7.1 7.1 0 3.92 3.18 7.1 7.1 7.1 3.92 0 7.1-3.18 7.1-7.1 0-3.92-3.18-7.1-7.1-7.1Z" fill="#fff" /><path fill-rule="evenodd" clip-rule="evenodd" d="M244.1 72.6c0-4.4-3.6-8-8-8s-8 3.6-8 8 3.6 8 8 8 8-3.6 8-8Zm-14.1-.1c0 3.3 2.8 6.1 6.1 6.1 3.4 0 6.1-2.8 6.1-6.1s-2.7-6.1-6.1-6.1c-3.4 0-6.1 2.8-6.1 6.1Z" fill="#142D6E" /><path fill-rule="evenodd" clip-rule="evenodd" d="M234.427 70.331a1.805 1.805 0 0 0-1.378 2.14 1.805 1.805 0 0 0 2.14 1.379 1.805 1.805 0 0 0 1.378-2.14 1.805 1.805 0 0 0-2.14-1.379ZM230.396 68.896c.3-.4.3-1-.1-1.3l-2.9-2.5c-.4-.3-1-.3-1.3.1-.3.4-.3 1 .1 1.3l2.9 2.5c.2.1.4.2.6.2.2 0 .5-.1.7-.3ZM232.603 67.403c.5-.2.7-.7.5-1.2l-1.9-4.7c-.2-.5-.7-.7-1.2-.5-.5.2-.7.7-.5 1.2l1.9 4.7c.2.4.5.6.9.6.2 0 .3 0 .3-.1Z" fill="#142D6E" /><path fill-rule="evenodd" clip-rule="evenodd" d="M241.998 77.7s-3.5.8-3.4 2.6c.2 1.8 1.7 2.6 4.2 2.3l1.3-11.5-2.1 6.6Z" fill="#fff" /><path fill-rule="evenodd" clip-rule="evenodd" d="m242.799 77.997 2.1-6.6c.2-.5-.1-1-.6-1.2-.5-.2-1 .1-1.2.6l-1.9 6.1c-1.1.4-3.7 1.4-3.5 3.5.1 1 .6 1.8 1.3 2.4.7.5 1.6.8 2.8.8h1.1c.5-.1.9-.6.8-1.1-.1-.5-.6-.9-1.1-.8-1.2.1-2.1 0-2.6-.4-.3-.3-.5-.6-.5-1.1-.1-.6 1.5-1.3 2.6-1.6.3 0 .6-.3.7-.6ZM259.896 66.403c.3-.4.1-1-.3-1.3-4.8-3-8.9-.7-9.1-.6-.4.3-.6.8-.3 1.3.3.4.8.6 1.3.3 0 0 3.3-1.8 7.1.6.1.2.3.2.5.2.3 0 .6-.1.8-.5ZM243.296 63.203c.3-.4.1-1-.3-1.3-4.8-3-8.9-.7-9.1-.6-.4.3-.6.8-.3 1.3s.8.7 1.3.4c.1 0 3.4-1.8 7.1.6.1.2.3.2.5.2.3 0 .6-.2.8-.6ZM263.096 75.901c.3-.4.3-1-.1-1.3-6-5.1-14.5-.3-14.9-.1-.4.3-.6.8-.3 1.3.3.4.8.6 1.3.3 0-.1 7.7-4.4 12.7-.1.2.1.4.2.6.2.2 0 .5-.1.7-.3Z" fill="#142D6E" /><path fill-rule="evenodd" clip-rule="evenodd" d="M259.6 81.004c.5-.1.8-.6.7-1.1-1.4-5.6-11.1-5.2-11.5-5.2-.5 0-.9.5-.9 1s.5.9 1 .9c.1 0 8.6-.3 9.6 3.8.1.4.5.7.9.7.1 0 .2-.1.2-.1ZM243.195 88.898c1.2.4 2.1 1.7 2.7 3.8.1.4.5.7.9.7 0 0 .1 0 .3.1.5-.2.8-.7.7-1.2-.8-2.7-2.1-4.5-3.9-5.1-2.7-1-5.4.8-5.5.9-.4.3-.5.9-.2 1.3s.9.5 1.3.2c0 0 2-1.3 3.7-.7Z" fill="#142D6E" /><path fill-rule="evenodd" clip-rule="evenodd" d="m273.2 38 .8-2-4.1-1.1-2.2 1.5 5.5 1.6Z" fill="#E2A40A" /><path fill-rule="evenodd" clip-rule="evenodd" d="M283.6 80.901s14.6-13.5 2.2-44.2c.1 0-5.6-7.8-15.9-1.8 0 0 24 17.8 13.7 46Z" fill="#FFC412" /><path fill-rule="evenodd" clip-rule="evenodd" d="m268.198 167.105 42.9 11.3s2.1-24.4-5-41.5c-4.8-11.5-12.2-20.6-20.3-22.9-4.3-1.2-9 .8-10.9 4.9-7 14.7-6.7 48.2-6.7 48.2Z" fill="#fff" /><path fill-rule="evenodd" clip-rule="evenodd" d="M311.899 178.502c.1-1 2.1-24.8-5-41.9-5.1-12.4-13-21.2-20.9-23.5-4.8-1.4-9.8.9-12 5.4-7 14.7-6.8 47.2-6.8 48.6 0 .4.3.8.7.9l43 11.3h.2c.2 0 .4 0 .4-.1.2-.2.4-.4.4-.7Zm-1.7-1.402c.3-5 1.1-25.3-5-39.9-5-11.9-12.3-20.2-19.7-22.4-3.9-1.1-8 .7-9.8 4.4-6.2 13.2-6.6 42.2-6.6 47.1l41.1 10.8ZM214 175.299c.4-.3.4-.9 0-1.3-6.3-6.6-28.2-4.6-29.1-4.5-.6 0-.9.5-.9 1 0 .6.5 1 1 .9.2 0 21.9-2 27.6 3.9.2.2.5.3.7.3.2 0 .4-.1.7-.3ZM213.604 187.2c.3-.4.1-1-.3-1.3-9.6-6.2-28.4-4-29.2-3.9-.5.1-.9.6-.8 1.1.1.5.6.9 1.1.8.2 0 18.9-2.3 27.9 3.6.1.2.3.2.5.2.3 0 .6-.2.8-.5ZM218.999 200.703c.3-.4.2-1-.2-1.3-8.6-6-33.9-4.4-35-4.3-.5 0-.9.5-.9 1s.5.9 1 .9c.2 0 25.7-1.7 33.8 4 .1.1.3.2.5.2.3 0 .6-.2.8-.5Z" fill="#142D6E" /><path fill-rule="evenodd" clip-rule="evenodd" d="M234.095 223.8c-6.5-2.3-13.7 7.7-20.1 5-3.7-2.8-7.4-5.5-10.3-5.9-2.7-.5-5.4.8-6.6 3.2 0 0-2.4-5.4-7.4-5.4-2.5 0-4.7 4.1-4.7 4.1s-2.2-4.3-6.5-4c-3.5.2-5.2 4.3-5.2 4.3s-4-3.1-7.5-1.7c-7.1 2.8-8.2 32.9 2.6 37.9 5.1 2.4 5-5 5-5s2.7 10.5 8.9 10.9c6.2.4 5.5-9.1 5.5-9.1s2.7 10.2 9.2 10c6.7-.2 4.4-9.7 4.4-9.7s4.3 6.9 9.2 5.6c8.1-2.2-.6-18.4 1.2-25h10.2c12.7-1.8 20.5-12.2 12.1-15.2Z" fill="#fff" /><path fill-rule="evenodd" clip-rule="evenodd" d="M165.602 222.6c-4.1 1.6-5.8 9.8-6 16.4-.3 8.7 1.8 20 8.6 23.2 1.5.7 2.9.7 4 0 .8-.5 1.3-1.3 1.6-2.2 1.4 3.4 4.1 7.9 8.5 8.2 1.6.1 3-.4 4.1-1.5 1.1-1.1 1.7-2.8 2-4.3 1.5 3 4.2 6.6 8.3 6.6.1 0 .2 0 .3.2 1.9-.1 3.3-.8 4.3-2.1 1.2-1.5 1.4-3.5 1.4-5.3 1.9 2 4.8 4.2 8.1 3.3 5.9-1.6 4.3-9.1 2.9-15.7-.7-3.4-1.4-6.9-1.1-9.2h9.4c.1 0 .2-.1.3-.1 9.2-1.2 15.8-6.9 16.3-11.4.3-2.5-1.2-4.5-4.1-5.5-3.6-1.3-7.3.7-10.8 2.6-3.2 1.8-6.3 3.5-9.1 2.4-3.9-3-7.5-5.5-10.5-6-2.5-.5-5.1.4-6.7 2.3-1.1-1.8-3.6-4.6-7.4-4.6-2.1 0-3.8 1.9-4.8 3.3-1.1-1.4-3.3-3.4-6.6-3.2-2.9.1-4.7 2.4-5.5 3.8-1.5-.9-4.6-2.4-7.5-1.2Zm8.802 33.6c0 .1 2.6 9.9 8 10.2 1.1.1 2-.2 2.7-.9 1.8-1.8 1.9-5.8 1.8-7.2 0-.5.3-.9.8-1 0-.2 0-.2.1-.2.4 0 .8.3.9.7 0 .1 2.6 9.5 8.2 9.3 1.3 0 2.2-.5 2.9-1.3 1.8-2.4.6-7.1.6-7.2-.1-.5.1-.9.5-1.1.4-.2.9 0 1.2.4 0 .1 4 6.3 8.1 5.1 3.9-1 3.1-6.2 1.5-13.5-.9-4.2-1.7-8.1-1-10.8.1-.4.5-.7.9-.7h10.1c8.3-1.1 14.4-6 14.8-9.8.2-1.6-.8-2.8-2.9-3.5-2.8-1-5.9.7-9.2 2.5-3.6 2-7.3 4-10.9 2.5-.1 0-.1-.1-.2-.1-3.7-2.8-7.2-5.3-9.8-5.7-2.3-.5-4.6.6-5.6 2.7-.2.3-.5.5-.9.5s-.7-.2-.8-.6c0 0-2.2-4.8-6.5-4.8-1.6 0-3.4 2.7-3.9 3.6-.2.3-.5.5-.8.5-.4 0-.7-.2-.8-.5-.1-.1-1.9-3.7-5.6-3.5-2.9.2-4.4 3.7-4.4 3.7-.1.3-.3.5-.6.6-.3.1-.6 0-.8-.2 0 0-3.6-2.8-6.5-1.6-2.6 1-4.6 7.2-4.8 14.7-.3 8.8 2 18.9 7.5 21.5.9.4 1.7.5 2.2.2 1.2-.8 1.4-3.4 1.4-4.3 0-.5.3-.9.8-.9.5-.1.9.2 1 .7Z" fill="#142D6E" /><path fill-rule="evenodd" clip-rule="evenodd" d="M174.501 256.9c-.9-8.7-.4-31.8-.4-32 0-.6-.4-1-.9-1-.6 0-1 .4-1 .9 0 .9-.5 23.4.4 32.2 0 .5.4.9.9.9.6-.1 1-.6 1-1ZM189.004 259.7l-3.6-35.4c0-.5-.5-.8-1-.8s-.9.5-.8 1l3.6 35.4c0 .4.4.8.9.8.6 0 1-.5.9-1ZM201.7 259.4c.5-.3.7-.8.5-1.3-2.6-5.5-4.2-31.7-4.2-32 0-.5-.5-.9-1-.9s-.9.5-.9 1c0 1 1.6 26.8 4.3 32.6.2.3.5.5.9.5.2 0 .3 0 .4.1ZM266.401 182.202c0-.5-.3-.9-.8-1-10.2-1.6-22.7-8.4-22.8-8.5-.4-.2-1 0-1.2.4-.2.4 0 1 .4 1.2.5.3 12.9 7 23.4 8.7h.1c.4 0 .8-.3.9-.8Z" fill="#142D6E" /><path fill-rule="evenodd" clip-rule="evenodd" d="m266.4 189.3.9-13.7c0-.5-.4-1-.9-1s-1 .4-1 .9l-.9 13.7c0 .5.3 1 .9 1 .5 0 .9-.3 1-.9Z" fill="#142D6E" /><path fill-rule="evenodd" clip-rule="evenodd" d="m335.303 169.403 23.7-10.7c.5-.2.7-.8.5-1.3-.2-.5-.8-.7-1.3-.5l-23.7 10.7c-.5.2-.7.8-.5 1.3.2.4.5.6.9.6.1 0 .2 0 .4-.1Z" fill="#FFC412" /></g></svg>
<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>
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.',
},
chat: {
newChatButton: 'New Chat',
placeholder: 'Ask me anything...(Shift + Enter = line break, "/" to trigger prompts)',
placeholderMobile: 'Ask me anything...',
copy: 'Copy',
copied: 'Copied',
copyCode: 'Copy Code',
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',
},
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',
},
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',
},
}
import type { App } from 'vue'
import { createI18n } from 'vue-i18n'
import enUS from './en-US'
import koKR from './ko-KR'
import zhCN from './zh-CN'
import zhTW from './zh-TW'
import ruRU from './ru-RU'
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,
'ko-KR': koKR,
'zh-CN': zhCN,
'zh-TW': zhTW,
'ru-RU': ruRU,
},
})
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
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: '인증되지 않았습니다. 먼저 확인하십시오.',
},
chat: {
newChatButton: '새로운 채팅',
placeholder: '무엇이든 물어보세요...(Shift + Enter = 줄바꿈, "/"를 눌러서 힌트를 보세요)',
placeholderMobile: '무엇이든 물어보세요...',
copy: '복사',
copied: '복사됨',
copyCode: '코드 복사',
clearChat: '채팅 비우기',
clearChatConfirm: '이 채팅을 비우시겠습니까?',
exportImage: '이미지 내보내기',
exportImageConfirm: '이 채팅을 png로 내보내시겠습니까?',
exportSuccess: '내보내기 성공',
exportFailed: '내보내기 실패',
usingContext: '컨텍스트 모드',
turnOnContext: '현재 모드에서는 이전 대화 기록을 포함하여 메시지를 보낼 수 있습니다.',
turnOffContext: '현재 모드에서는 이전 대화 기록을 포함하지 않고 메시지를 보낼 수 있습니다.',
deleteMessage: '메시지 삭제',
deleteMessageConfirm: '이 메시지를 삭제하시겠습니까?',
deleteHistoryConfirm: '이 기록을 삭제하시겠습니까?',
clearHistoryConfirm: '채팅 기록을 삭제하시겠습니까?',
preview: '미리보기',
showRawText: '원본 텍스트로 보기',
},
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: '월 사용량',
},
store: {
siderButton: '프롬프트 저장소',
local: '로컬',
online: '온라인',
title: '제목',
description: '설명',
clearStoreConfirm: '데이터를 삭제하시겠습니까?',
importPlaceholder: '여기에 JSON 데이터를 붙여넣으십시오',
addRepeatTitleTips: '제목 중복됨, 다시 입력하십시오',
addRepeatContentTips: '내용 중복됨: {msg}, 다시 입력하십시오',
editRepeatTitleTips: '제목 충돌, 수정하십시오',
editRepeatContentTips: '내용 충돌 {msg} , 수정하십시오',
importError: '키 값 불일치',
importRepeatTitle: '제목이 반복되어 건너뜀: {msg}',
importRepeatContent: '내용이 반복되어 건너뜀: {msg}',
onlineImportWarning: '참고: JSON 파일 소스를 확인하십시오!',
},
}
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: 'Не авторизован, сначала подтвердите свою личность.',
},
chat: {
newChatButton: 'Новый чат',
placeholder: 'Спросите меня о чем-нибудь ... (Shift + Enter = перенос строки, "/" для вызова подсказок)',
placeholderMobile: 'Спросите меня о чем-нибудь ...',
copy: 'Копировать',
copied: 'Скопировано',
copyCode: 'Копировать код',
clearChat: 'Очистить чат',
clearChatConfirm: 'Вы уверены, что хотите очистить этот чат?',
exportImage: 'Экспорт в изображение',
exportImageConfirm: 'Вы уверены, что хотите экспортировать этот чат в формате PNG?',
exportSuccess: 'Экспортировано успешно',
exportFailed: 'Не удалось выполнить экспорт',
usingContext: 'Режим контекста',
turnOnContext: 'В текущем режиме отправка сообщений будет включать предыдущие записи чата.',
turnOffContext: 'В текущем режиме отправка сообщений не будет включать предыдущие записи чата.',
deleteMessage: 'Удалить сообщение',
deleteMessageConfirm: 'Вы уверены, что хотите удалить это сообщение?',
deleteHistoryConfirm: 'Вы уверены, что хотите очистить эту историю?',
clearHistoryConfirm: 'Вы уверены, что хотите очистить историю чата?',
preview: 'Предварительный просмотр',
showRawText: 'Показать как обычный текст',
},
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: 'Ежемесячное использование',
},
store: {
siderButton: 'Хранилище подсказок',
local: 'Локальное',
online: 'Онлайн',
title: 'Название',
description: 'Описание',
clearStoreConfirm: 'Вы действительно хотите очистить данные?',
importPlaceholder: 'Пожалуйста, вставьте здесь JSON-данные',
addRepeatTitleTips: 'Дубликат названия, пожалуйста, введите другое название',
addRepeatContentTips: 'Дубликат содержимого: {msg}, пожалуйста, введите другой текст',
editRepeatTitleTips: 'Конфликт названий, пожалуйста, измените название',
editRepeatContentTips: 'Конфликт содержимого {msg}, пожалуйста, измените текст',
importError: 'Не совпадает ключ-значение',
importRepeatTitle: 'Название повторяющееся, пропускается: {msg}',
importRepeatContent: 'Содержание повторяющееся, пропускается: {msg}',
onlineImportWarning: 'Внимание! Проверьте источник JSON-файла!',
downloadError: 'Проверьте состояние сети и правильность JSON-файла',
},
}
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: '未经授权,请先进行验证。',
},
chat: {
newChatButton: '新建聊天',
placeholder: '来说点什么吧...(Shift + Enter = 换行,"/" 触发提示词)',
placeholderMobile: '来说点什么...',
copy: '复制',
copied: '复制成功',
copyCode: '复制代码',
clearChat: '清空会话',
clearChatConfirm: '是否清空会话?',
exportImage: '保存会话到图片',
exportImageConfirm: '是否将会话保存为图片?',
exportSuccess: '保存成功',
exportFailed: '保存失败',
usingContext: '上下文模式',
turnOnContext: '当前模式下, 发送消息会携带之前的聊天记录',
turnOffContext: '当前模式下, 发送消息不会携带之前的聊天记录',
deleteMessage: '删除消息',
deleteMessageConfirm: '是否删除此消息?',
deleteHistoryConfirm: '确定删除此记录?',
clearHistoryConfirm: '确定清空聊天记录?',
preview: '预览',
showRawText: '显示原文',
},
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: '本月使用量',
},
store: {
siderButton: '提示词商店',
local: '本地',
online: '在线',
title: '标题',
description: '描述',
clearStoreConfirm: '是否清空数据?',
importPlaceholder: '请粘贴 JSON 数据到此处',
addRepeatTitleTips: '标题重复,请重新输入',
addRepeatContentTips: '内容重复:{msg},请重新输入',
editRepeatTitleTips: '标题冲突,请重新修改',
editRepeatContentTips: '内容冲突{msg} ,请重新修改',
importError: '键值不匹配',
importRepeatTitle: '标题重复跳过:{msg}',
importRepeatContent: '内容重复跳过:{msg}',
onlineImportWarning: '注意:请检查 JSON 文件来源!',
downloadError: '请检查网络状态与 JSON 文件有效性',
},
}
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: '未經授權,請先進行驗證。',
},
chat: {
newChatButton: '新增對話',
placeholder: '來說點什麼...(Shift + Enter = 換行,"/" 觸發提示詞)',
placeholderMobile: '來說點什麼...',
copy: '複製',
copied: '複製成功',
copyCode: '複製代碼',
clearChat: '清除對話',
clearChatConfirm: '是否清空對話?',
exportImage: '儲存對話為圖片',
exportImageConfirm: '是否將對話儲存為圖片?',
exportSuccess: '儲存成功',
exportFailed: '儲存失敗',
usingContext: '上下文模式',
turnOnContext: '啟用上下文模式,在此模式下,發送訊息會包含之前的聊天記錄。',
turnOffContext: '關閉上下文模式,在此模式下,發送訊息不會包含之前的聊天記錄。',
deleteMessage: '刪除訊息',
deleteMessageConfirm: '是否刪除此訊息?',
deleteHistoryConfirm: '確定刪除此紀錄?',
clearHistoryConfirm: '確定清除紀錄?',
preview: '預覽',
showRawText: '顯示原文',
},
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 Credit 餘額',
monthlyUsage: '本月使用量',
},
store: {
siderButton: '提示詞商店',
local: '本機',
online: '線上',
title: '標題',
description: '描述',
clearStoreConfirm: '是否清除資料?',
importPlaceholder: '請將 JSON 資料貼在此處',
addRepeatTitleTips: '標題重複,請重新輸入',
addRepeatContentTips: '內容重複:{msg},請重新輸入',
editRepeatTitleTips: '標題衝突,請重新修改',
editRepeatContentTips: '內容衝突{msg} ,請重新修改',
importError: '鍵值不符合',
importRepeatTitle: '因標題重複跳過:{msg}',
importRepeatContent: '因內容重複跳過:{msg}',
onlineImportWarning: '注意:請檢查 JSON 檔案來源!',
downloadError: '請檢查網路狀態與 JSON 檔案有效性',
},
}
import { createApp } from 'vue'
import App from './App.vue'
import { setupI18n } from './locales'
import { setupAssets, setupScrollbarStyle } from './plugins'
import { setupStore } from './store'
import { setupRouter } from './router'
async function bootstrap() {
const app = createApp(App)
setupAssets()
setupScrollbarStyle()
setupStore(app)
setupI18n(app)
await setupRouter(app)
app.mount('#app')
}
bootstrap()
import 'katex/dist/katex.min.css'
import '@/styles/lib/tailwind.css'
import '@/styles/lib/highlight.less'
import '@/styles/lib/github-markdown.less'
import '@/styles/global.less'
/** Tailwind's Preflight Style Override */
function naiveStyleOverride() {
const meta = document.createElement('meta')
meta.name = 'naive-ui-style'
document.head.appendChild(meta)
}
function setupAssets() {
naiveStyleOverride()
}
export default setupAssets
import setupAssets from './assets'
import setupScrollbarStyle from './scrollbarStyle'
export { setupAssets, setupScrollbarStyle }
import { darkTheme, lightTheme } from 'naive-ui'
const setupScrollbarStyle = () => {
const style = document.createElement('style')
const styleContent = `
::-webkit-scrollbar {
background-color: transparent;
width: ${lightTheme.Scrollbar.common?.scrollbarWidth};
}
::-webkit-scrollbar-thumb {
background-color: ${lightTheme.Scrollbar.common?.scrollbarColor};
border-radius: ${lightTheme.Scrollbar.common?.scrollbarBorderRadius};
}
html.dark ::-webkit-scrollbar {
background-color: transparent;
width: ${darkTheme.Scrollbar.common?.scrollbarWidth};
}
html.dark ::-webkit-scrollbar-thumb {
background-color: ${darkTheme.Scrollbar.common?.scrollbarColor};
border-radius: ${darkTheme.Scrollbar.common?.scrollbarBorderRadius};
}
`
style.innerHTML = styleContent
document.head.appendChild(style)
}
export default setupScrollbarStyle
import type { App } from 'vue'
import type { RouteRecordRaw } from 'vue-router'
import { createRouter, createWebHashHistory } from 'vue-router'
import { ChatLayout } from '@/views/chat/layout'
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'Root',
component: ChatLayout,
redirect: '/chat',
children: [
{
path: '/chat/:uuid?',
name: 'Chat',
component: () => import('@/views/chat/index.vue'),
},
],
},
{
path: '/404',
name: '404',
component: () => import('@/views/exception/404/index.vue'),
},
{
path: '/500',
name: '500',
component: () => import('@/views/exception/500/index.vue'),
},
{
path: '/:pathMatch(.*)*',
name: 'notFound',
redirect: '/404',
},
]
export const router = createRouter({
history: createWebHashHistory(),
routes,
scrollBehavior: () => ({ left: 0, top: 0 }),
})
// setupPageGuard(router)
export async function setupRouter(app: App) {
app.use(router)
await router.isReady()
}
import type { Router } from 'vue-router'
import { useAuthStoreWithout } from '@/store/modules/auth'
export function setupPageGuard(router: Router) {
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStoreWithout()
if (!authStore.session) {
try {
const data = await authStore.getSession()
if (String(data.auth) === 'false' && authStore.token)
authStore.removeToken()
if (to.path === '/500')
next({ name: 'Root' })
else
next()
}
catch (error) {
if (to.path !== '/500')
next({ name: '500' })
else
next()
}
}
else {
next()
}
})
}
import type { App } from 'vue'
import { createPinia } from 'pinia'
export const store = createPinia()
export function setupStore(app: App) {
app.use(store)
}
export * from './modules'
import { ss } from '@/utils/storage'
const LOCAL_NAME = 'appSetting'
export type Theme = 'light' | 'dark' | 'auto'
export type Language = 'zh-CN' | 'zh-TW' | 'en-US' | 'ko-KR' | 'ru-RU'
export interface AppState {
siderCollapsed: boolean
theme: Theme
language: Language
}
export function defaultSetting(): AppState {
return { siderCollapsed: false, theme: 'light', language: 'zh-CN' }
}
export function getLocalSetting(): AppState {
const localSetting: AppState | undefined = ss.get(LOCAL_NAME)
return { ...defaultSetting(), ...localSetting }
}
export function setLocalSetting(setting: AppState): void {
ss.set(LOCAL_NAME, setting)
}
import { defineStore } from 'pinia'
import type { AppState, Language, Theme } from './helper'
import { getLocalSetting, setLocalSetting } from './helper'
import { store } from '@/store'
export const useAppStore = defineStore('app-store', {
state: (): AppState => getLocalSetting(),
actions: {
setSiderCollapsed(collapsed: boolean) {
this.siderCollapsed = collapsed
this.recordState()
},
setTheme(theme: Theme) {
this.theme = theme
this.recordState()
},
setLanguage(language: Language) {
if (this.language !== language) {
this.language = language
this.recordState()
}
},
recordState() {
setLocalSetting(this.$state)
},
},
})
export function useAppStoreWithOut() {
return useAppStore(store)
}
import { ss } from '@/utils/storage'
const LOCAL_NAME = 'SECRET_TOKEN'
export function getToken() {
return ss.get(LOCAL_NAME)
}
export function setToken(token: string) {
return ss.set(LOCAL_NAME, token)
}
export function removeToken() {
return ss.remove(LOCAL_NAME)
}
import { defineStore } from 'pinia'
import { getToken, removeToken, setToken } from './helper'
import { store } from '@/store'
import { fetchSession } from '@/api'
interface SessionResponse {
auth: boolean
model: 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI'
}
export interface AuthState {
token: string | undefined
session: SessionResponse | null
}
export const useAuthStore = defineStore('auth-store', {
state: (): AuthState => ({
token: getToken(),
session: null,
}),
getters: {
isChatGPTAPI(state): boolean {
return state.session?.model === 'ChatGPTAPI'
},
},
actions: {
async getSession() {
try {
const { data } = await fetchSession<SessionResponse>()
this.session = { ...data }
return Promise.resolve(data)
}
catch (error) {
return Promise.reject(error)
}
},
setToken(token: string) {
this.token = token
setToken(token)
},
removeToken() {
this.token = undefined
removeToken()
},
},
})
export function useAuthStoreWithout() {
return useAuthStore(store)
}
import { ss } from '@/utils/storage'
const LOCAL_NAME = 'chatStorage'
export function defaultState(): Chat.ChatState {
const uuid = 1002
return {
active: uuid,
usingContext: true,
history: [{ uuid, title: 'New Chat', isEdit: false }],
chat: [{ uuid, data: [] }],
}
}
export function getLocalState(): Chat.ChatState {
const localState = ss.get(LOCAL_NAME)
return { ...defaultState(), ...localState }
}
export function setLocalState(state: Chat.ChatState) {
ss.set(LOCAL_NAME, state)
}
import { defineStore } from 'pinia'
import { getLocalState, setLocalState } from './helper'
import { router } from '@/router'
export const useChatStore = defineStore('chat-store', {
state: (): Chat.ChatState => getLocalState(),
getters: {
getChatHistoryByCurrentActive(state: Chat.ChatState) {
const index = state.history.findIndex(item => item.uuid === state.active)
if (index !== -1)
return state.history[index]
return null
},
getChatByUuid(state: Chat.ChatState) {
return (uuid?: number) => {
if (uuid)
return state.chat.find(item => item.uuid === uuid)?.data ?? []
return state.chat.find(item => item.uuid === state.active)?.data ?? []
}
},
},
actions: {
setUsingContext(context: boolean) {
this.usingContext = context
this.recordState()
},
addHistory(history: Chat.History, chatData: Chat.Chat[] = []) {
this.history.unshift(history)
this.chat.unshift({ uuid: history.uuid, data: chatData })
this.active = history.uuid
this.reloadRoute(history.uuid)
},
updateHistory(uuid: number, edit: Partial<Chat.History>) {
const index = this.history.findIndex(item => item.uuid === uuid)
if (index !== -1) {
this.history[index] = { ...this.history[index], ...edit }
this.recordState()
}
},
async deleteHistory(index: number) {
this.history.splice(index, 1)
this.chat.splice(index, 1)
if (this.history.length === 0) {
this.active = null
this.reloadRoute()
return
}
if (index > 0 && index <= this.history.length) {
const uuid = this.history[index - 1].uuid
this.active = uuid
this.reloadRoute(uuid)
return
}
if (index === 0) {
if (this.history.length > 0) {
const uuid = this.history[0].uuid
this.active = uuid
this.reloadRoute(uuid)
}
}
if (index > this.history.length) {
const uuid = this.history[this.history.length - 1].uuid
this.active = uuid
this.reloadRoute(uuid)
}
},
async setActive(uuid: number) {
this.active = uuid
return await this.reloadRoute(uuid)
},
getChatByUuidAndIndex(uuid: number, index: number) {
if (!uuid || uuid === 0) {
if (this.chat.length)
return this.chat[0].data[index]
return null
}
const chatIndex = this.chat.findIndex(item => item.uuid === uuid)
if (chatIndex !== -1)
return this.chat[chatIndex].data[index]
return null
},
addChatByUuid(uuid: number, chat: Chat.Chat) {
if (!uuid || uuid === 0) {
if (this.history.length === 0) {
const uuid = Date.now()
this.history.push({ uuid, title: chat.text, isEdit: false })
this.chat.push({ uuid, data: [chat] })
this.active = uuid
this.recordState()
}
else {
this.chat[0].data.push(chat)
if (this.history[0].title === 'New Chat')
this.history[0].title = chat.text
this.recordState()
}
}
const index = this.chat.findIndex(item => item.uuid === uuid)
if (index !== -1) {
this.chat[index].data.push(chat)
if (this.history[index].title === 'New Chat')
this.history[index].title = chat.text
this.recordState()
}
},
updateChatByUuid(uuid: number, index: number, chat: Chat.Chat) {
if (!uuid || uuid === 0) {
if (this.chat.length) {
this.chat[0].data[index] = chat
this.recordState()
}
return
}
const chatIndex = this.chat.findIndex(item => item.uuid === uuid)
if (chatIndex !== -1) {
this.chat[chatIndex].data[index] = chat
this.recordState()
}
},
updateChatSomeByUuid(uuid: number, index: number, chat: Partial<Chat.Chat>) {
if (!uuid || uuid === 0) {
if (this.chat.length) {
this.chat[0].data[index] = { ...this.chat[0].data[index], ...chat }
this.recordState()
}
return
}
const chatIndex = this.chat.findIndex(item => item.uuid === uuid)
if (chatIndex !== -1) {
this.chat[chatIndex].data[index] = { ...this.chat[chatIndex].data[index], ...chat }
this.recordState()
}
},
deleteChatByUuid(uuid: number, index: number) {
if (!uuid || uuid === 0) {
if (this.chat.length) {
this.chat[0].data.splice(index, 1)
this.recordState()
}
return
}
const chatIndex = this.chat.findIndex(item => item.uuid === uuid)
if (chatIndex !== -1) {
this.chat[chatIndex].data.splice(index, 1)
this.recordState()
}
},
clearChatByUuid(uuid: number) {
if (!uuid || uuid === 0) {
if (this.chat.length) {
this.chat[0].data = []
this.recordState()
}
return
}
const index = this.chat.findIndex(item => item.uuid === uuid)
if (index !== -1) {
this.chat[index].data = []
this.recordState()
}
},
async reloadRoute(uuid?: number) {
this.recordState()
await router.push({ name: 'Chat', params: { uuid } })
},
recordState() {
setLocalState(this.$state)
},
},
})
export * from './app'
export * from './chat'
export * from './user'
export * from './prompt'
export * from './settings'
export * from './auth'
import { ss } from '@/utils/storage'
const LOCAL_NAME = 'promptStore'
export type PromptList = []
export interface PromptStore {
promptList: PromptList
}
export function getLocalPromptList(): PromptStore {
const promptStore: PromptStore | undefined = ss.get(LOCAL_NAME)
return promptStore ?? { promptList: [] }
}
export function setLocalPromptList(promptStore: PromptStore): void {
ss.set(LOCAL_NAME, promptStore)
}
import { defineStore } from 'pinia'
import type { PromptStore } from './helper'
import { getLocalPromptList, setLocalPromptList } from './helper'
export const usePromptStore = defineStore('prompt-store', {
state: (): PromptStore => getLocalPromptList(),
actions: {
updatePromptList(promptList: []) {
this.$patch({ promptList })
setLocalPromptList({ promptList })
},
getPromptList() {
return this.$state
},
},
})
import { ss } from '@/utils/storage'
const LOCAL_NAME = 'settingsStorage'
export interface SettingsState {
systemMessage: string
temperature: number
top_p: number
}
export function defaultSetting(): SettingsState {
return {
systemMessage: 'You are ChatGPT, a large language model trained by OpenAI. Follow the user\'s instructions carefully. Respond using markdown.',
temperature: 0.8,
top_p: 1,
}
}
export function getLocalState(): SettingsState {
const localSetting: SettingsState | undefined = ss.get(LOCAL_NAME)
return { ...defaultSetting(), ...localSetting }
}
export function setLocalState(setting: SettingsState): void {
ss.set(LOCAL_NAME, setting)
}
export function removeLocalState() {
ss.remove(LOCAL_NAME)
}
import { defineStore } from 'pinia'
import type { SettingsState } from './helper'
import { defaultSetting, getLocalState, removeLocalState, setLocalState } from './helper'
export const useSettingStore = defineStore('setting-store', {
state: (): SettingsState => getLocalState(),
actions: {
updateSetting(settings: Partial<SettingsState>) {
this.$state = { ...this.$state, ...settings }
this.recordState()
},
resetSetting() {
this.$state = defaultSetting()
removeLocalState()
},
recordState() {
setLocalState(this.$state)
},
},
})
import { ss } from '@/utils/storage'
const LOCAL_NAME = 'userStorage'
export interface UserInfo {
avatar: string
name: string
description: string
}
export interface UserState {
userInfo: UserInfo
}
export function defaultSetting(): UserState {
return {
userInfo: {
avatar: 'https://raw.githubusercontent.com/Chanzhaoyu/chatgpt-web/main/src/assets/avatar.jpg',
name: '语链',
description: 'Star on <a href="https://github.com/Chanzhaoyu/chatgpt-bot" class="text-blue-500" target="_blank" >GitHub</a>',
},
}
}
export function getLocalState(): UserState {
const localSetting: UserState | undefined = ss.get(LOCAL_NAME)
return { ...defaultSetting(), ...localSetting }
}
export function setLocalState(setting: UserState): void {
ss.set(LOCAL_NAME, setting)
}
import { defineStore } from 'pinia'
import type { UserInfo, UserState } from './helper'
import { defaultSetting, getLocalState, setLocalState } from './helper'
export const useUserStore = defineStore('user-store', {
state: (): UserState => getLocalState(),
actions: {
updateUserInfo(userInfo: Partial<UserInfo>) {
this.userInfo = { ...this.userInfo, ...userInfo }
this.recordState()
},
resetUserInfo() {
this.userInfo = { ...defaultSetting().userInfo }
this.recordState()
},
recordState() {
setLocalState(this.$state)
},
},
})
html,
body,
#app {
height: 100%;
}
body {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
declare namespace Chat {
interface Chat {
dateTime: string
text: string
inversion?: boolean
error?: boolean
loading?: boolean
conversationOptions?: ConversationRequest | null
requestOptions: { prompt: string; options?: ConversationRequest | null }
}
interface History {
title: string
isEdit: boolean
uuid: number
}
interface ChatState {
active: number | null
usingContext: boolean;
history: History[]
chat: { uuid: number; data: Chat[] }[]
}
interface ConversationRequest {
conversationId?: string
parentMessageId?: string
}
interface ConversationResponse {
conversationId: string
detail: {
choices: { finish_reason: string; index: number; logprobs: any; text: string }[]
created: number
id: string
model: string
object: string
usage: { completion_tokens: number; prompt_tokens: number; total_tokens: number }
}
id: string
parentMessageId: string
role: string
text: string
}
}
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_GLOB_API_URL: string;
readonly VITE_APP_API_BASE_URL: string;
readonly VITE_GLOB_OPEN_LONG_REPLY: string;
readonly VITE_GLOB_APP_PWA: string;
}
interface Window {
$loadingBar?: import('naive-ui').LoadingBarProviderInst;
$dialog?: import('naive-ui').DialogProviderInst;
$message?: import('naive-ui').MessageProviderInst;
$notification?: import('naive-ui').NotificationProviderInst;
}
export function copyToClip(text: string) {
return new Promise((resolve, reject) => {
try {
const input: HTMLTextAreaElement = document.createElement('textarea')
input.setAttribute('readonly', 'readonly')
input.value = text
document.body.appendChild(input)
input.select()
if (document.execCommand('copy'))
document.execCommand('copy')
document.body.removeChild(input)
resolve(text)
}
catch (error) {
reject(error)
}
})
}
type CallbackFunc<T extends unknown[]> = (...args: T) => void
export function debounce<T extends unknown[]>(
func: CallbackFunc<T>,
wait: number,
): (...args: T) => void {
let timeoutId: ReturnType<typeof setTimeout> | undefined
return (...args: T) => {
const later = () => {
clearTimeout(timeoutId)
func(...args)
}
clearTimeout(timeoutId)
timeoutId = setTimeout(later, wait)
}
}
export function getCurrentDate() {
const date = new Date()
const day = date.getDate()
const month = date.getMonth() + 1
const year = date.getFullYear()
return `${year}-${month}-${day}`
}
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 isBoolean<T extends boolean>(value: T | unknown): value is boolean {
return Object.prototype.toString.call(value) === '[object Boolean]'
}
export function isNull<T extends null>(value: T | unknown): value is null {
return Object.prototype.toString.call(value) === '[object Null]'
}
export function isUndefined<T extends undefined>(value: T | unknown): value is undefined {
return Object.prototype.toString.call(value) === '[object Undefined]'
}
export function isObject<T extends object>(value: T | unknown): value is object {
return Object.prototype.toString.call(value) === '[object Object]'
}
export function isArray<T extends any[]>(value: T | unknown): value is T {
return Object.prototype.toString.call(value) === '[object Array]'
}
export function isFunction<T extends (...args: any[]) => any | void | never>(value: T | unknown): value is T {
return Object.prototype.toString.call(value) === '[object Function]'
}
export function isDate<T extends Date>(value: T | unknown): value is T {
return Object.prototype.toString.call(value) === '[object Date]'
}
export function isRegExp<T extends RegExp>(value: T | unknown): value is T {
return Object.prototype.toString.call(value) === '[object RegExp]'
}
export function isPromise<T extends Promise<any>>(value: T | unknown): value is T {
return Object.prototype.toString.call(value) === '[object Promise]'
}
export function isSet<T extends Set<any>>(value: T | unknown): value is T {
return Object.prototype.toString.call(value) === '[object Set]'
}
export function isMap<T extends Map<any, any>>(value: T | unknown): value is T {
return Object.prototype.toString.call(value) === '[object Map]'
}
export function isFile<T extends File>(value: T | unknown): value is T {
return Object.prototype.toString.call(value) === '[object File]'
}
import axios, { type AxiosResponse } from 'axios'
import { useAuthStore } from '@/store'
const service = axios.create({
baseURL: import.meta.env.VITE_GLOB_API_URL,
})
service.interceptors.request.use(
(config) => {
const token = useAuthStore().token
if (token)
config.headers.Authorization = `Bearer ${token}`
return config
},
(error) => {
return Promise.reject(error.response)
},
)
service.interceptors.response.use(
(response: AxiosResponse): AxiosResponse => {
if (response.status === 200)
return response
throw new Error(response.status.toString())
},
(error) => {
return Promise.reject(error)
},
)
export default service
import type { AxiosProgressEvent, AxiosResponse, GenericAbortSignal } from 'axios'
import request from './axios'
import { useAuthStore } from '@/store'
export interface HttpOption {
url: string
data?: any
method?: string
headers?: any
onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void
signal?: GenericAbortSignal
beforeRequest?: () => void
afterRequest?: () => void
}
export interface Response<T = any> {
data: T
message: string | null
status: string
}
function http<T = any>(
{ url, data, method, headers, onDownloadProgress, signal, beforeRequest, afterRequest }: HttpOption,
) {
const successHandler = (res: AxiosResponse<Response<T>>) => {
const authStore = useAuthStore()
if (res.data.status === 'Success' || typeof res.data === 'string')
return res.data
if (res.data.status === 'Unauthorized') {
authStore.removeToken()
window.location.reload()
}
return Promise.reject(res.data)
}
const failHandler = (error: Response<Error>) => {
afterRequest?.()
throw new Error(error?.message || 'Error')
}
beforeRequest?.()
method = method || 'GET'
const params = Object.assign(typeof data === 'function' ? data() : data ?? {}, {})
return method === 'GET'
? request.get(url, { params, signal, onDownloadProgress }).then(successHandler, failHandler)
: request.post(url, params, { headers, signal, onDownloadProgress }).then(successHandler, failHandler)
}
export function get<T = any>(
{ url, data, method = 'GET', onDownloadProgress, signal, beforeRequest, afterRequest }: HttpOption,
): Promise<Response<T>> {
return http<T>({
url,
method,
data,
onDownloadProgress,
signal,
beforeRequest,
afterRequest,
})
}
export function post<T = any>(
{ url, data, method = 'POST', headers, onDownloadProgress, signal, beforeRequest, afterRequest }: HttpOption,
): Promise<Response<T>> {
return http<T>({
url,
method,
data,
headers,
onDownloadProgress,
signal,
beforeRequest,
afterRequest,
})
}
export default post
interface StorageData<T = any> {
data: T
expire: number | null
}
export function createLocalStorage(options?: { expire?: number | null }) {
const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7
const { expire } = Object.assign({ expire: DEFAULT_CACHE_TIME }, options)
function set<T = any>(key: string, data: T) {
const storageData: StorageData<T> = {
data,
expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
}
const json = JSON.stringify(storageData)
window.localStorage.setItem(key, json)
}
function get(key: string) {
const json = window.localStorage.getItem(key)
if (json) {
let storageData: StorageData | null = null
try {
storageData = JSON.parse(json)
}
catch {
// Prevent failure
}
if (storageData) {
const { data, expire } = storageData
if (expire === null || expire >= Date.now())
return data
}
remove(key)
return null
}
}
function remove(key: string) {
window.localStorage.removeItem(key)
}
function clear() {
window.localStorage.clear()
}
return { set, get, remove, clear }
}
export const ls = createLocalStorage()
export const ss = createLocalStorage({ expire: null })
<script lang="ts" setup>
import { computed, nextTick } from 'vue'
import { HoverButton, SvgIcon } from '@/components/common'
import { useAppStore, useChatStore } from '@/store'
interface Props {
usingContext: boolean
}
interface Emit {
(ev: 'export'): void
(ev: 'toggleUsingContext'): void
}
defineProps<Props>()
const emit = defineEmits<Emit>()
const appStore = useAppStore()
const chatStore = useChatStore()
const collapsed = computed(() => appStore.siderCollapsed)
const currentChatHistory = computed(() => chatStore.getChatHistoryByCurrentActive)
function handleUpdateCollapsed() {
appStore.setSiderCollapsed(!collapsed.value)
}
function onScrollToTop() {
const scrollRef = document.querySelector('#scrollRef')
if (scrollRef)
nextTick(() => scrollRef.scrollTop = 0)
}
function handleExport() {
emit('export')
}
function toggleUsingContext() {
emit('toggleUsingContext')
}
</script>
<template>
<header
class="sticky top-0 left-0 right-0 z-30 border-b dark:border-neutral-800 bg-white/80 dark:bg-black/20 backdrop-blur"
>
<div class="relative flex items-center justify-between min-w-0 overflow-hidden h-14">
<div class="flex items-center">
<button
class="flex items-center justify-center w-11 h-11"
@click="handleUpdateCollapsed"
>
<SvgIcon v-if="collapsed" class="text-2xl" icon="ri:align-justify" />
<SvgIcon v-else class="text-2xl" icon="ri:align-right" />
</button>
</div>
<h1
class="flex-1 px-4 pr-6 overflow-hidden cursor-pointer select-none text-ellipsis whitespace-nowrap"
@dblclick="onScrollToTop"
>
{{ currentChatHistory?.title ?? '' }}
</h1>
<div class="flex items-center space-x-2">
<HoverButton @click="toggleUsingContext">
<span class="text-xl" :class="{ 'text-[#4b9e5f]': usingContext, 'text-[#a8071a]': !usingContext }">
<SvgIcon icon="ri:chat-history-line" />
</span>
</HoverButton>
<HoverButton @click="handleExport">
<span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="ri:download-2-line" />
</span>
</HoverButton>
</div>
</div>
</header>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { NAvatar } from 'naive-ui'
import { useUserStore } from '@/store'
import { isString } from '@/utils/is'
import defaultAvatar from '@/assets/avatar.jpg'
interface Props {
image?: boolean
}
defineProps<Props>()
const userStore = useUserStore()
const avatar = computed(() => userStore.userInfo.avatar)
</script>
<template>
<template v-if="image">
<NAvatar v-if="isString(avatar) && avatar.length > 0" :src="avatar" :fallback-src="defaultAvatar" />
<NAvatar v-else round :src="defaultAvatar" />
</template>
<span v-else class="text-[28px] dark:text-white">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-hidden="true" width="1em" height="1em">
<path d="M29.71,13.09A8.09,8.09,0,0,0,20.34,2.68a8.08,8.08,0,0,0-13.7,2.9A8.08,8.08,0,0,0,2.3,18.9,8,8,0,0,0,3,25.45a8.08,8.08,0,0,0,8.69,3.87,8,8,0,0,0,6,2.68,8.09,8.09,0,0,0,7.7-5.61,8,8,0,0,0,5.33-3.86A8.09,8.09,0,0,0,29.71,13.09Zm-12,16.82a6,6,0,0,1-3.84-1.39l.19-.11,6.37-3.68a1,1,0,0,0,.53-.91v-9l2.69,1.56a.08.08,0,0,1,.05.07v7.44A6,6,0,0,1,17.68,29.91ZM4.8,24.41a6,6,0,0,1-.71-4l.19.11,6.37,3.68a1,1,0,0,0,1,0l7.79-4.49V22.8a.09.09,0,0,1,0,.08L13,26.6A6,6,0,0,1,4.8,24.41ZM3.12,10.53A6,6,0,0,1,6.28,7.9v7.57a1,1,0,0,0,.51.9l7.75,4.47L11.85,22.4a.14.14,0,0,1-.09,0L5.32,18.68a6,6,0,0,1-2.2-8.18Zm22.13,5.14-7.78-4.52L20.16,9.6a.08.08,0,0,1,.09,0l6.44,3.72a6,6,0,0,1-.9,10.81V16.56A1.06,1.06,0,0,0,25.25,15.67Zm2.68-4-.19-.12-6.36-3.7a1,1,0,0,0-1.05,0l-7.78,4.49V9.2a.09.09,0,0,1,0-.09L19,5.4a6,6,0,0,1,8.91,6.21ZM11.08,17.15,8.38,15.6a.14.14,0,0,1-.05-.08V8.1a6,6,0,0,1,9.84-4.61L18,3.6,11.61,7.28a1,1,0,0,0-.53.91ZM12.54,14,16,12l3.47,2v4L16,20l-3.47-2Z" fill="currentColor" />
</svg>
</span>
</template>
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, onUpdated, ref } from 'vue'
import MarkdownIt from 'markdown-it'
import mdKatex from '@traptitech/markdown-it-katex'
import mila from 'markdown-it-link-attributes'
import hljs from 'highlight.js'
import { useBasicLayout } from '@/hooks/useBasicLayout'
import { t } from '@/locales'
import { copyToClip } from '@/utils/copy'
interface Props {
inversion?: boolean
error?: boolean
text?: string
loading?: boolean
asRawText?: boolean
}
const props = defineProps<Props>()
const { isMobile } = useBasicLayout()
const textRef = ref<HTMLElement>()
const mdi = new MarkdownIt({
html: false,
linkify: true,
highlight(code, language) {
const validLang = !!(language && hljs.getLanguage(language))
if (validLang) {
const lang = language ?? ''
return highlightBlock(hljs.highlight(code, { language: lang }).value, lang)
}
return highlightBlock(hljs.highlightAuto(code).value, '')
},
})
mdi.use(mila, { attrs: { target: '_blank', rel: 'noopener' } })
mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' })
const wrapClass = computed(() => {
return [
'text-wrap',
'min-w-[20px]',
'rounded-md',
isMobile.value ? 'p-2' : 'px-3 py-2',
props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
props.inversion ? 'dark:bg-[#a1dc95]' : 'dark:bg-[#1e1e20]',
props.inversion ? 'message-request' : 'message-reply',
{ 'text-red-500': props.error },
]
})
const text = computed(() => {
const value = props.text ?? ''
if (!props.asRawText)
return mdi.render(value)
return value
})
function highlightBlock(str: string, lang?: string) {
return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">${t('chat.copyCode')}</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`
}
function addCopyEvents() {
if (textRef.value) {
const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy')
copyBtn.forEach((btn) => {
btn.addEventListener('click', () => {
const code = btn.parentElement?.nextElementSibling?.textContent
if (code) {
copyToClip(code).then(() => {
btn.textContent = '复制成功'
setTimeout(() => {
btn.textContent = '复制代码'
}, 1000)
})
}
})
})
}
}
function removeCopyEvents() {
if (textRef.value) {
const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy')
copyBtn.forEach((btn) => {
btn.removeEventListener('click', () => {})
})
}
}
onMounted(() => {
addCopyEvents()
})
onUpdated(() => {
addCopyEvents()
})
onUnmounted(() => {
removeCopyEvents()
})
</script>
<template>
<div class="text-black" :class="wrapClass">
<div ref="textRef" class="leading-relaxed break-words">
<div v-if="!inversion">
<div v-if="!asRawText" class="markdown-body" v-html="text" />
<div v-else class="whitespace-pre-wrap" v-text="text" />
</div>
<div v-else class="whitespace-pre-wrap" v-text="text" />
<template v-if="loading">
<span class="dark:text-white w-[4px] h-[20px] block animate-blink" />
</template>
</div>
</div>
</template>
<style lang="less">
@import url(./style.less);
</style>
<script setup lang='ts'>
import { computed, ref } from 'vue'
import { NDropdown, useMessage } from 'naive-ui'
import AvatarComponent from './Avatar.vue'
import TextComponent from './Text.vue'
import { SvgIcon } from '@/components/common'
import { useIconRender } from '@/hooks/useIconRender'
import { t } from '@/locales'
import { useBasicLayout } from '@/hooks/useBasicLayout'
import { copyToClip } from '@/utils/copy'
interface Props {
dateTime?: string
text?: string
inversion?: boolean
error?: boolean
loading?: boolean
}
interface Emit {
(ev: 'regenerate'): void
(ev: 'delete'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const { isMobile } = useBasicLayout()
const { iconRender } = useIconRender()
const message = useMessage()
const textRef = ref<HTMLElement>()
const asRawText = ref(props.inversion)
const messageRef = ref<HTMLElement>()
const options = computed(() => {
const common = [
{
label: t('chat.copy'),
key: 'copyText',
icon: iconRender({ icon: 'ri:file-copy-2-line' }),
},
{
label: t('common.delete'),
key: 'delete',
icon: iconRender({ icon: 'ri:delete-bin-line' }),
},
]
if (!props.inversion) {
common.unshift({
label: asRawText.value ? t('chat.preview') : t('chat.showRawText'),
key: 'toggleRenderType',
icon: iconRender({ icon: asRawText.value ? 'ic:outline-code-off' : 'ic:outline-code' }),
})
}
return common
})
function handleSelect(key: 'copyText' | 'delete' | 'toggleRenderType') {
switch (key) {
case 'copyText':
handleCopy()
return
case 'toggleRenderType':
asRawText.value = !asRawText.value
return
case 'delete':
emit('delete')
}
}
function handleRegenerate() {
messageRef.value?.scrollIntoView()
emit('regenerate')
}
async function handleCopy() {
try {
await copyToClip(props.text || '')
message.success('复制成功')
}
catch {
message.error('复制失败')
}
}
</script>
<template>
<div
ref="messageRef"
class="flex w-full mb-6 overflow-hidden"
:class="[{ 'flex-row-reverse': inversion }]"
>
<div
class="flex items-center justify-center flex-shrink-0 h-8 overflow-hidden rounded-full basis-8"
:class="[inversion ? 'ml-2' : 'mr-2']"
>
<AvatarComponent :image="inversion" />
</div>
<div class="overflow-hidden text-sm " :class="[inversion ? 'items-end' : 'items-start']">
<p class="text-xs text-[#b4bbc4]" :class="[inversion ? 'text-right' : 'text-left']">
{{ dateTime }}
</p>
<div
class="flex items-end gap-1 mt-2"
:class="[inversion ? 'flex-row-reverse' : 'flex-row']"
>
<TextComponent
ref="textRef"
:inversion="inversion"
:error="error"
:text="text"
:loading="loading"
:as-raw-text="asRawText"
/>
<div class="flex flex-col">
<button
v-if="!inversion"
class="mb-2 transition text-neutral-300 hover:text-neutral-800 dark:hover:text-neutral-300"
@click="handleRegenerate"
>
<SvgIcon icon="ri:restart-line" />
</button>
<NDropdown
:trigger="isMobile ? 'click' : 'hover'"
:placement="!inversion ? 'right' : 'left'"
:options="options"
@select="handleSelect"
>
<button class="transition text-neutral-300 hover:text-neutral-800 dark:hover:text-neutral-200">
<SvgIcon icon="ri:more-2-fill" />
</button>
</NDropdown>
</div>
</div>
</div>
</div>
</template>
.markdown-body {
background-color: transparent;
font-size: 14px;
p {
white-space: pre-wrap;
}
ol {
list-style-type: decimal;
}
ul {
list-style-type: disc;
}
pre code,
pre tt {
line-height: 1.65;
}
.highlight pre,
pre {
background-color: #fff;
}
code.hljs {
padding: 0;
}
.code-block {
&-wrapper {
position: relative;
padding-top: 24px;
}
&-header {
position: absolute;
top: 5px;
right: 0;
width: 100%;
padding: 0 1rem;
display: flex;
justify-content: flex-end;
align-items: center;
color: #b3b3b3;
&__copy {
cursor: pointer;
margin-left: 0.5rem;
user-select: none;
&:hover {
color: #65a665;
}
}
}
}
}
html.dark {
.message-reply {
.whitespace-pre-wrap {
white-space: pre-wrap;
color: var(--n-text-color);
}
}
.highlight pre,
pre {
background-color: #282c34;
}
}
import Message from './Message/index.vue'
export { Message }
import { useChatStore } from '@/store'
export function useChat() {
const chatStore = useChatStore()
const getChatByUuidAndIndex = (uuid: number, index: number) => {
return chatStore.getChatByUuidAndIndex(uuid, index)
}
const addChat = (uuid: number, chat: Chat.Chat) => {
chatStore.addChatByUuid(uuid, chat)
}
const updateChat = (uuid: number, index: number, chat: Chat.Chat) => {
chatStore.updateChatByUuid(uuid, index, chat)
}
const updateChatSome = (uuid: number, index: number, chat: Partial<Chat.Chat>) => {
chatStore.updateChatSomeByUuid(uuid, index, chat)
}
return {
addChat,
updateChat,
updateChatSome,
getChatByUuidAndIndex,
}
}
import type { Ref } from 'vue'
import { nextTick, ref } from 'vue'
type ScrollElement = HTMLDivElement | null
interface ScrollReturn {
scrollRef: Ref<ScrollElement>
scrollToBottom: () => Promise<void>
scrollToTop: () => Promise<void>
scrollToBottomIfAtBottom: () => Promise<void>
}
export function useScroll(): ScrollReturn {
const scrollRef = ref<ScrollElement>(null)
const scrollToBottom = async () => {
await nextTick()
if (scrollRef.value)
scrollRef.value.scrollTop = scrollRef.value.scrollHeight
}
const scrollToTop = async () => {
await nextTick()
if (scrollRef.value)
scrollRef.value.scrollTop = 0
}
const scrollToBottomIfAtBottom = async () => {
await nextTick()
if (scrollRef.value) {
const threshold = 100 // 阈值,表示滚动条到底部的距离阈值
const distanceToBottom = scrollRef.value.scrollHeight - scrollRef.value.scrollTop - scrollRef.value.clientHeight
if (distanceToBottom <= threshold)
scrollRef.value.scrollTop = scrollRef.value.scrollHeight
}
}
return {
scrollRef,
scrollToBottom,
scrollToTop,
scrollToBottomIfAtBottom,
}
}
import { computed } from 'vue'
import { useMessage } from 'naive-ui'
import { t } from '@/locales'
import { useChatStore } from '@/store'
export function useUsingContext() {
const ms = useMessage()
const chatStore = useChatStore()
const usingContext = computed<boolean>(() => chatStore.usingContext)
function toggleUsingContext() {
chatStore.setUsingContext(!usingContext.value)
if (usingContext.value)
ms.success(t('chat.turnOnContext'))
else
ms.warning(t('chat.turnOffContext'))
}
return {
usingContext,
toggleUsingContext,
}
}
<script setup lang='ts'>
import type { Ref } from 'vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { storeToRefs } from 'pinia'
import { NAutoComplete, NButton, NInput, NSwitch, useDialog, useMessage } from 'naive-ui'
import html2canvas from 'html2canvas'
import { Message } from './components'
import { useScroll } from './hooks/useScroll'
import { useChat } from './hooks/useChat'
import { useUsingContext } from './hooks/useUsingContext'
import HeaderComponent from './components/Header/index.vue'
import { HoverButton, SvgIcon } from '@/components/common'
import { useBasicLayout } from '@/hooks/useBasicLayout'
import { useChatStore, usePromptStore } from '@/store'
import { fetchChatAPIProcess } from '@/api'
import { t } from '@/locales'
import { chat, chatfile } from '@/api/chat'
let controller = new AbortController()
const openLongReply = import.meta.env.VITE_GLOB_OPEN_LONG_REPLY === 'true'
const route = useRoute()
const dialog = useDialog()
const ms = useMessage()
const chatStore = useChatStore()
const { isMobile } = useBasicLayout()
const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex } = useChat()
const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll()
const { usingContext, toggleUsingContext } = useUsingContext()
const { uuid } = route.params as { uuid: string }
const dataSources = computed(() => chatStore.getChatByUuid(+uuid))
const conversationList = computed(() => dataSources.value.filter(item => (!item.inversion && !!item.conversationOptions)))
const prompt = ref<string>('')
const loading = ref<boolean>(false)
const inputRef = ref<Ref | null>(null)
// 添加PromptStore
const promptStore = usePromptStore()
// 使用storeToRefs,保证store修改后,联想部分能够重新渲染
const { promptList: promptTemplate } = storeToRefs<any>(promptStore)
// 是否开启知识库问答
const active = ref<boolean>(false)
// 未知原因刷新页面,loading 状态不会重置,手动重置
dataSources.value.forEach((item, index) => {
if (item.loading)
updateChatSome(+uuid, index, { loading: false })
})
function handleSubmit() {
onConversation()
}
async function onConversation() {
const message = prompt.value
if (loading.value)
return
if (!message || message.trim() === '')
return
controller = new AbortController()
addChat(
+uuid,
{
dateTime: new Date().toLocaleString(),
text: message,
inversion: true,
error: false,
conversationOptions: null,
requestOptions: { prompt: message, options: null },
},
)
scrollToBottom()
loading.value = true
prompt.value = ''
let options: Chat.ConversationRequest = {}
const lastContext = conversationList.value[conversationList.value.length - 1]?.conversationOptions
if (lastContext && usingContext.value)
options = { ...lastContext }
addChat(
+uuid,
{
dateTime: new Date().toLocaleString(),
text: '',
loading: true,
inversion: false,
error: false,
conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } },
},
)
scrollToBottom()
try {
const lastText = ''
const fetchChatAPIOnce = async () => {
const res = active.value
? await chatfile({ message })
: await chat({
question: message,
history: [[
'工伤保险是什么?',
'工伤保险是指用人单位按照国家规定,为本单位的职工和用人单位的其他人员,缴纳工伤保险费,由保险机构按照国家规定的标准,给予工伤保险待遇的社会保险制度。',
]],
})
const result = active.value ? res.data.response.text : res.data.response
updateChat(
+uuid,
dataSources.value.length - 1,
{
dateTime: new Date().toLocaleString(),
text: lastText + (result ?? ''),
inversion: false,
error: false,
loading: false,
conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } },
},
)
scrollToBottomIfAtBottom()
loading.value = false
/* await fetchChatAPIProcess<Chat.ConversationResponse>({
prompt: message,
options,
signal: controller.signal,
onDownloadProgress: ({ event }) => {
const xhr = event.target
const { responseText } = xhr
// Always process the final line
const lastIndex = responseText.lastIndexOf('\n', responseText.length - 2)
let chunk = responseText
if (lastIndex !== -1)
chunk = responseText.substring(lastIndex)
try {
const data = JSON.parse(chunk)
updateChat(
+uuid,
dataSources.value.length - 1,
{
dateTime: new Date().toLocaleString(),
text: lastText + (data.text ?? ''),
inversion: false,
error: false,
loading: true,
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
requestOptions: { prompt: message, options: { ...options } },
},
)
if (openLongReply && data.detail.choices[0].finish_reason === 'length') {
options.parentMessageId = data.id
lastText = data.text
message = ''
return fetchChatAPIOnce()
}
scrollToBottomIfAtBottom()
}
catch (error) {
//
}
},
}) */
updateChatSome(+uuid, dataSources.value.length - 1, { loading: false })
}
await fetchChatAPIOnce()
}
catch (error: any) {
const errorMessage = error?.message ?? t('common.wrong')
if (error.message === 'canceled') {
updateChatSome(
+uuid,
dataSources.value.length - 1,
{
loading: false,
},
)
scrollToBottomIfAtBottom()
return
}
const currentChat = getChatByUuidAndIndex(+uuid, dataSources.value.length - 1)
if (currentChat?.text && currentChat.text !== '') {
updateChatSome(
+uuid,
dataSources.value.length - 1,
{
text: `${currentChat.text}\n[${errorMessage}]`,
error: false,
loading: false,
},
)
return
}
updateChat(
+uuid,
dataSources.value.length - 1,
{
dateTime: new Date().toLocaleString(),
text: errorMessage,
inversion: false,
error: true,
loading: false,
conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } },
},
)
scrollToBottomIfAtBottom()
}
finally {
loading.value = false
}
}
async function onRegenerate(index: number) {
if (loading.value)
return
controller = new AbortController()
const { requestOptions } = dataSources.value[index]
let message = requestOptions?.prompt ?? ''
let options: Chat.ConversationRequest = {}
if (requestOptions.options)
options = { ...requestOptions.options }
loading.value = true
updateChat(
+uuid,
index,
{
dateTime: new Date().toLocaleString(),
text: '',
inversion: false,
error: false,
loading: true,
conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } },
},
)
try {
let lastText = ''
const fetchChatAPIOnce = async () => {
await fetchChatAPIProcess<Chat.ConversationResponse>({
prompt: message,
options,
signal: controller.signal,
onDownloadProgress: ({ event }) => {
const xhr = event.target
const { responseText } = xhr
// Always process the final line
const lastIndex = responseText.lastIndexOf('\n', responseText.length - 2)
let chunk = responseText
if (lastIndex !== -1)
chunk = responseText.substring(lastIndex)
try {
const data = JSON.parse(chunk)
updateChat(
+uuid,
index,
{
dateTime: new Date().toLocaleString(),
text: lastText + (data.text ?? ''),
inversion: false,
error: false,
loading: true,
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
requestOptions: { prompt: message, options: { ...options } },
},
)
if (openLongReply && data.detail.choices[0].finish_reason === 'length') {
options.parentMessageId = data.id
lastText = data.text
message = ''
return fetchChatAPIOnce()
}
}
catch (error) {
//
}
},
})
updateChatSome(+uuid, index, { loading: false })
}
await fetchChatAPIOnce()
}
catch (error: any) {
if (error.message === 'canceled') {
updateChatSome(
+uuid,
index,
{
loading: false,
},
)
return
}
const errorMessage = error?.message ?? t('common.wrong')
updateChat(
+uuid,
index,
{
dateTime: new Date().toLocaleString(),
text: errorMessage,
inversion: false,
error: true,
loading: false,
conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } },
},
)
}
finally {
loading.value = false
}
}
function handleExport() {
if (loading.value)
return
const d = dialog.warning({
title: t('chat.exportImage'),
content: t('chat.exportImageConfirm'),
positiveText: t('common.yes'),
negativeText: t('common.no'),
onPositiveClick: async () => {
try {
d.loading = true
const ele = document.getElementById('image-wrapper')
const canvas = await html2canvas(ele as HTMLDivElement, {
useCORS: true,
})
const imgUrl = canvas.toDataURL('image/png')
const tempLink = document.createElement('a')
tempLink.style.display = 'none'
tempLink.href = imgUrl
tempLink.setAttribute('download', 'chat-shot.png')
if (typeof tempLink.download === 'undefined')
tempLink.setAttribute('target', '_blank')
document.body.appendChild(tempLink)
tempLink.click()
document.body.removeChild(tempLink)
window.URL.revokeObjectURL(imgUrl)
d.loading = false
ms.success(t('chat.exportSuccess'))
Promise.resolve()
}
catch (error: any) {
ms.error(t('chat.exportFailed'))
}
finally {
d.loading = false
}
},
})
}
function handleDelete(index: number) {
if (loading.value)
return
dialog.warning({
title: t('chat.deleteMessage'),
content: t('chat.deleteMessageConfirm'),
positiveText: t('common.yes'),
negativeText: t('common.no'),
onPositiveClick: () => {
chatStore.deleteChatByUuid(+uuid, index)
},
})
}
function handleClear() {
if (loading.value)
return
dialog.warning({
title: t('chat.clearChat'),
content: t('chat.clearChatConfirm'),
positiveText: t('common.yes'),
negativeText: t('common.no'),
onPositiveClick: () => {
chatStore.clearChatByUuid(+uuid)
},
})
}
function handleEnter(event: KeyboardEvent) {
if (!isMobile.value) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault()
handleSubmit()
}
}
else {
if (event.key === 'Enter' && event.ctrlKey) {
event.preventDefault()
handleSubmit()
}
}
}
function handleStop() {
if (loading.value) {
controller.abort()
loading.value = false
}
}
// 可优化部分
// 搜索选项计算,这里使用value作为索引项,所以当出现重复value时渲染异常(多项同时出现选中效果)
// 理想状态下其实应该是key作为索引项,但官方的renderOption会出现问题,所以就需要value反renderLabel实现
const searchOptions = computed(() => {
if (prompt.value.startsWith('/')) {
return promptTemplate.value.filter((item: { key: string }) => item.key.toLowerCase().includes(prompt.value.substring(1).toLowerCase())).map((obj: { value: any }) => {
return {
label: obj.value,
value: obj.value,
}
})
}
else {
return []
}
})
// value反渲染key
const renderOption = (option: { label: string }) => {
for (const i of promptTemplate.value) {
if (i.value === option.label)
return [i.key]
}
return []
}
const placeholder = computed(() => {
if (isMobile.value)
return t('chat.placeholderMobile')
return t('chat.placeholder')
})
const buttonDisabled = computed(() => {
return loading.value || !prompt.value || prompt.value.trim() === ''
})
const footerClass = computed(() => {
let classes = ['p-4']
if (isMobile.value)
classes = ['sticky', 'left-0', 'bottom-0', 'right-0', 'p-2', 'pr-3', 'overflow-hidden']
return classes
})
onMounted(() => {
scrollToBottom()
if (inputRef.value && !isMobile.value)
inputRef.value?.focus()
})
onUnmounted(() => {
if (loading.value)
controller.abort()
})
</script>
<template>
<div class="flex flex-col w-full h-full bg-green-50">
<HeaderComponent
v-if="isMobile"
:using-context="usingContext"
@export="handleExport"
@toggle-using-context="toggleUsingContext"
/>
<main class="flex-1 overflow-hidden">
<div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
<div
id="image-wrapper"
class="w-full max-w-screen-xl m-auto dark:bg-[#101014]"
:class="[isMobile ? 'p-2' : 'p-4']"
>
<template v-if="!dataSources.length">
<div class="flex items-center justify-center mt-4 text-center text-neutral-300">
<SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
<span>Aha~</span>
</div>
</template>
<template v-else>
<div>
<Message
v-for="(item, index) of dataSources"
:key="index"
:date-time="item.dateTime"
:text="item.text"
:inversion="item.inversion"
:error="item.error"
:loading="item.loading"
@regenerate="onRegenerate(index)"
@delete="handleDelete(index)"
/>
<div class="sticky bottom-0 left-0 flex justify-center">
<NButton v-if="loading" type="warning" @click="handleStop">
<template #icon>
<SvgIcon icon="ri:stop-circle-line" />
</template>
Stop Responding
</NButton>
</div>
</div>
</template>
</div>
</div>
</main>
<footer :class="footerClass">
<div class="w-full max-w-screen-xl m-auto">
<div class="flex items-center justify-between space-x-2">
<NSwitch v-model:value="active">
<template #checked>
知识库
</template>
<template #unchecked>
知识库&nbsp;&nbsp;
</template>
</NSwitch>
<HoverButton @click="handleClear">
<span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="ri:delete-bin-line" />
</span>
</HoverButton>
<HoverButton v-if="!isMobile" @click="handleExport">
<span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="ri:download-2-line" />
</span>
</HoverButton>
<HoverButton v-if="!isMobile" @click="toggleUsingContext">
<span class="text-xl" :class="{ 'text-[#4b9e5f]': usingContext, 'text-[#a8071a]': !usingContext }">
<SvgIcon icon="ri:chat-history-line" />
</span>
</HoverButton>
<NAutoComplete v-model:value="prompt" :options="searchOptions" :render-label="renderOption">
<template #default="{ handleInput, handleBlur, handleFocus }">
<NInput
ref="inputRef"
v-model:value="prompt"
type="textarea"
:placeholder="placeholder"
:autosize="{ minRows: 1, maxRows: isMobile ? 4 : 8 }"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
@keypress="handleEnter"
/>
</template>
</NAutoComplete>
<NButton type="primary" :disabled="buttonDisabled" @click="handleSubmit">
<template #icon>
<span class="dark:text-black">
<SvgIcon icon="ri:send-plane-fill" />
</span>
</template>
</NButton>
</div>
</div>
</footer>
</div>
</template>
<script setup lang='ts'>
import { computed } from 'vue'
import { NLayout, NLayoutContent } from 'naive-ui'
import { useRouter } from 'vue-router'
import Sider from './sider/index.vue'
import Permission from './Permission.vue'
import { useBasicLayout } from '@/hooks/useBasicLayout'
import { useAppStore, useAuthStore, useChatStore } from '@/store'
const router = useRouter()
const appStore = useAppStore()
const chatStore = useChatStore()
const authStore = useAuthStore()
router.replace({ name: 'Chat', params: { uuid: chatStore.active } })
const { isMobile } = useBasicLayout()
const collapsed = computed(() => appStore.siderCollapsed)
const needPermission = computed(() => !!authStore.session?.auth && !authStore.token)
const getMobileClass = computed(() => {
if (isMobile.value)
return ['rounded-none', 'shadow-none']
return ['border', 'rounded-md', 'shadow-md', 'dark:border-neutral-800']
})
const getContainerClass = computed(() => {
return [
'h-full',
{ 'pl-[260px]': !isMobile.value && !collapsed.value },
]
})
</script>
<template>
<div class="h-full dark:bg-[#24272e] transition-all" :class="[isMobile ? 'p-0' : 'p-4']">
<div class="h-full overflow-hidden" :class="getMobileClass">
<NLayout class="z-40 transition" :class="getContainerClass" has-sider>
<Sider />
<NLayoutContent class="h-full">
<RouterView v-slot="{ Component, route }">
<component :is="Component" :key="route.fullPath" />
</RouterView>
</NLayoutContent>
</NLayout>
</div>
<Permission :visible="needPermission" />
</div>
</template>
<script setup lang='ts'>
import { computed, ref } from 'vue'
import { NButton, NInput, NModal, useMessage } from 'naive-ui'
import { fetchVerify } from '@/api'
import { useAuthStore } from '@/store'
import Icon403 from '@/icons/403.vue'
interface Props {
visible: boolean
}
defineProps<Props>()
const authStore = useAuthStore()
const ms = useMessage()
const loading = ref(false)
const token = ref('')
const disabled = computed(() => !token.value.trim() || loading.value)
async function handleVerify() {
const secretKey = token.value.trim()
if (!secretKey)
return
try {
loading.value = true
await fetchVerify(secretKey)
authStore.setToken(secretKey)
ms.success('success')
window.location.reload()
}
catch (error: any) {
ms.error(error.message ?? 'error')
authStore.removeToken()
token.value = ''
}
finally {
loading.value = false
}
}
function handlePress(event: KeyboardEvent) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault()
handleVerify()
}
}
</script>
<template>
<NModal :show="visible" style="width: 90%; max-width: 640px">
<div class="p-10 bg-white rounded dark:bg-slate-800">
<div class="space-y-4">
<header class="space-y-2">
<h2 class="text-2xl font-bold text-center text-slate-800 dark:text-neutral-200">
403
</h2>
<p class="text-base text-center text-slate-500 dark:text-slate-500">
{{ $t('common.unauthorizedTips') }}
</p>
<Icon403 class="w-[200px] m-auto" />
</header>
<NInput v-model:value="token" type="password" placeholder="" @keypress="handlePress" />
<NButton
block
type="primary"
:disabled="disabled"
:loading="loading"
@click="handleVerify"
>
{{ $t('common.verify') }}
</NButton>
</div>
</div>
</NModal>
</template>
import ChatLayout from './Layout.vue'
export { ChatLayout }
<script setup lang='ts'>
import { defineAsyncComponent, ref } from 'vue'
import { HoverButton, SvgIcon, UserAvatar } from '@/components/common'
const Setting = defineAsyncComponent(() => import('@/components/common/Setting/index.vue'))
const show = ref(false)
</script>
<template>
<footer class="flex items-center justify-between min-w-0 p-4 overflow-hidden border-t dark:border-neutral-800">
<div class="flex-1 flex-shrink-0 overflow-hidden">
<UserAvatar />
</div>
<HoverButton @click="show = true">
<span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="ri:settings-4-line" />
</span>
</HoverButton>
<Setting v-if="show" v-model:visible="show" />
</footer>
</template>
<script setup lang='ts'>
import { computed } from 'vue'
import { NInput, NPopconfirm, NScrollbar } from 'naive-ui'
import { SvgIcon } from '@/components/common'
import { useAppStore, useChatStore } from '@/store'
import { useBasicLayout } from '@/hooks/useBasicLayout'
import { debounce } from '@/utils/functions/debounce'
const { isMobile } = useBasicLayout()
const appStore = useAppStore()
const chatStore = useChatStore()
const dataSources = computed(() => chatStore.history)
async function handleSelect({ uuid }: Chat.History) {
if (isActive(uuid))
return
if (chatStore.active)
chatStore.updateHistory(chatStore.active, { isEdit: false })
await chatStore.setActive(uuid)
if (isMobile.value)
appStore.setSiderCollapsed(true)
}
function handleEdit({ uuid }: Chat.History, isEdit: boolean, event?: MouseEvent) {
event?.stopPropagation()
chatStore.updateHistory(uuid, { isEdit })
}
function handleDelete(index: number, event?: MouseEvent | TouchEvent) {
event?.stopPropagation()
chatStore.deleteHistory(index)
if (isMobile.value)
appStore.setSiderCollapsed(true)
}
const handleDeleteDebounce = debounce(handleDelete, 600)
function handleEnter({ uuid }: Chat.History, isEdit: boolean, event: KeyboardEvent) {
event?.stopPropagation()
if (event.key === 'Enter')
chatStore.updateHistory(uuid, { isEdit })
}
function isActive(uuid: number) {
return chatStore.active === uuid
}
</script>
<template>
<NScrollbar class="px-4">
<div class="flex flex-col gap-2 text-sm">
<template v-if="!dataSources.length">
<div class="flex flex-col items-center mt-4 text-center text-neutral-300">
<SvgIcon icon="ri:inbox-line" class="mb-2 text-3xl" />
<span>{{ $t('common.noData') }}</span>
</div>
</template>
<template v-else>
<div v-for="(item, index) of dataSources" :key="index">
<a
class="relative flex items-center gap-3 px-3 py-3 break-all border rounded-md cursor-pointer hover:bg-neutral-100 group dark:border-neutral-800 dark:hover:bg-[#24272e]"
:class="isActive(item.uuid) && ['border-[#4b9e5f]', 'bg-neutral-100', 'text-[#4b9e5f]', 'dark:bg-[#24272e]', 'dark:border-[#4b9e5f]', 'pr-14']"
@click="handleSelect(item)"
>
<span>
<SvgIcon icon="ri:message-3-line" />
</span>
<div class="relative flex-1 overflow-hidden break-all text-ellipsis whitespace-nowrap">
<NInput
v-if="item.isEdit"
v-model:value="item.title" size="tiny"
@keypress="handleEnter(item, false, $event)"
/>
<span v-else>{{ item.title }}</span>
</div>
<div v-if="isActive(item.uuid)" class="absolute z-10 flex visible right-1">
<template v-if="item.isEdit">
<button class="p-1" @click="handleEdit(item, false, $event)">
<SvgIcon icon="ri:save-line" />
</button>
</template>
<template v-else>
<button class="p-1">
<SvgIcon icon="ri:edit-line" @click="handleEdit(item, true, $event)" />
</button>
<NPopconfirm placement="bottom" @positive-click="handleDeleteDebounce(index, $event)">
<template #trigger>
<button class="p-1">
<SvgIcon icon="ri:delete-bin-line" />
</button>
</template>
{{ $t('chat.deleteHistoryConfirm') }}
</NPopconfirm>
</template>
</div>
</a>
</div>
</template>
</div>
</NScrollbar>
</template>
<script setup lang='ts'>
import { onMounted, ref } from 'vue'
import { NInput, NPopconfirm, NScrollbar } from 'naive-ui'
import { SvgIcon } from '@/components/common'
import { useAppStore, useChatStore } from '@/store'
import { useBasicLayout } from '@/hooks/useBasicLayout'
import { deletefile, getfilelist } from '@/api/chat'
const { isMobile } = useBasicLayout()
const appStore = useAppStore()
const chatStore = useChatStore()
const dataSources = ref<any[]>([])
onMounted(async () => {
const res = await getfilelist()
dataSources.value = res.data.data
})
async function handleSelect({ uuid }: Chat.History) {
if (isActive(uuid))
return
if (chatStore.active)
chatStore.updateHistory(chatStore.active, { isEdit: false })
await chatStore.setActive(uuid)
if (isMobile.value)
appStore.setSiderCollapsed(true)
}
/* function handleEdit({ uuid }: Chat.History, isEdit: boolean, event?: MouseEvent) {
event?.stopPropagation()
chatStore.updateHistory(uuid, { isEdit })
} */
async function handleDelete(item: any) {
/* const mid = */await deletefile({ knowledge_base_id: '123', doc_name: item })
const res = await getfilelist()
dataSources.value = res.data.data
}
function handleEnter({ uuid }: Chat.History, isEdit: boolean, event: KeyboardEvent) {
event?.stopPropagation()
if (event.key === 'Enter')
chatStore.updateHistory(uuid, { isEdit })
}
function isActive(uuid: number) {
return chatStore.active === uuid
}
</script>
<template>
<NScrollbar class="px-4">
<div class="flex flex-col gap-2 text-sm">
<template v-if="!dataSources.length">
<div class="flex flex-col items-center mt-4 text-center text-neutral-300">
<SvgIcon icon="ri:inbox-line" class="mb-2 text-3xl" />
<span>{{ $t('common.noData') }}</span>
</div>
</template>
<template v-else>
<div v-for="(item, index) of dataSources" :key="index">
<a
class="relative flex items-center gap-3 px-3 py-3 break-all border rounded-md cursor-pointer hover:bg-neutral-100 group dark:border-neutral-800 dark:hover:bg-[#24272e]"
:class="isActive(item.uuid) && ['border-[#4b9e5f]', 'bg-neutral-100', 'text-[#4b9e5f]', 'dark:bg-[#24272e]', 'dark:border-[#4b9e5f]', 'pr-14']"
@click="handleSelect(item)"
>
<span>
<SvgIcon icon="ri:message-3-line" />
</span>
<div class="relative flex-1 overflow-hidden break-all text-ellipsis whitespace-nowrap">
<NInput
v-if="item.isEdit"
v-model:value="item.title" size="tiny"
@keypress="handleEnter(item, false, $event)"
/>
<span v-else>{{ item }}</span>
</div>
<div class="absolute z-10 flex visible right-1">
<template v-if="item.isEdit">
<!-- <button class="p-1" @click="handleEdit(item, false, $event)">
<SvgIcon icon="ri:save-line" />
</button> -->
</template>
<template v-else>
<!-- <button class="p-1">
<SvgIcon icon="ri:edit-line" @click="handleEdit(item, true, $event)" />
</button> -->
<NPopconfirm placement="bottom" @positive-click="handleDelete(item)">
<template #trigger>
<button class="p-1">
<SvgIcon icon="ri:delete-bin-line" />
</button>
</template>
{{ $t('chat.deleteHistoryConfirm') }}
</NPopconfirm>
</template>
</div>
</a>
</div>
</template>
</div>
</NScrollbar>
</template>
<script setup lang='ts'>
import type { CSSProperties } from 'vue'
import { computed, ref, watch } from 'vue'
import { NButton, NLayoutSider, NUpload } from 'naive-ui'
import List from './List.vue'
import filelist from './filelist.vue'
import Footer from './Footer.vue'
import { useAppStore, useChatStore } from '@/store'
import { useBasicLayout } from '@/hooks/useBasicLayout'
const appStore = useAppStore()
const chatStore = useChatStore()
const { isMobile } = useBasicLayout()
const menu = ref(1)
const collapsed = computed(() => appStore.siderCollapsed)
function handleAdd() {
menu.value = 1
chatStore.addHistory({ title: 'New Chat', uuid: Date.now(), isEdit: false })
if (isMobile.value)
appStore.setSiderCollapsed(true)
}
function handleUpdateCollapsed() {
appStore.setSiderCollapsed(!collapsed.value)
}
const getMobileClass = computed<CSSProperties>(() => {
if (isMobile.value) {
return {
position: 'fixed',
zIndex: 50,
}
}
return {}
})
const mobileSafeArea = computed(() => {
if (isMobile.value) {
return {
paddingBottom: 'env(safe-area-inset-bottom)',
}
}
return {}
})
//
watch(
isMobile,
(val) => {
appStore.setSiderCollapsed(val)
},
{
immediate: true,
flush: 'post',
},
)
</script>
<template>
<NLayoutSider
:collapsed="collapsed"
:collapsed-width="0"
:width="260"
:show-trigger="isMobile ? false : 'arrow-circle'"
collapse-mode="transform"
position="absolute"
bordered
:style="getMobileClass"
@update-collapsed="handleUpdateCollapsed"
>
<Footer />
<div class="flex flex-col h-full " :style="mobileSafeArea">
<main class="flex flex-col flex-1 min-h-0">
<div class=" flex justify-between">
<NButton dashed @click="menu = 1">
会话
</NButton>
<NButton dashed @click="menu = 2">
模型
</NButton>
<NButton dashed @click="menu = 3">
知识库
</NButton>
<NButton dashed @click="menu = 4">
提示词
</NButton>
</div>
<!-- 知识库界面 -->
<div v-if="menu === 3">
<div class="p-4">
<NUpload
action="http://127.0.0.1:1002/api/chat-docs/uploadone"
:headers="{
'naive-info': 'hello!',
}"
:data="{
knowledge_base_id: '123',
}"
>
<NButton block>
文件上传
</NButton>
</NUpload>
</div>
<div class="p-2 flex-1 min-h-0 pb-4 overflow-hidden">
<filelist />
</div>
</div>
<!-- 会话界面 -->
<div v-if="menu === 1">
<div class="p-4">
<NButton block @click="handleAdd">
新建聊天
</NButton>
</div>
<div class="p-2 flex-1 min-h-0 pb-4 overflow-hidden">
<List />
</div>
</div>
<!-- <div class="p-4">
<NButton block @click="show = true">
{{ $t('store.siderButton') }}
</NButton>
</div> -->
</main>
</div>
</NLayoutSider>
<template v-if="isMobile">
<div v-show="!collapsed" class="fixed inset-0 z-40 w-full h-full bg-black/40" @click="handleUpdateCollapsed" />
</template>
<!-- <PromptStore v-model:visible="show" /> -->
</template>
<script lang="ts" setup>
import { NButton } from 'naive-ui'
import { useRouter } from 'vue-router'
const router = useRouter()
function goHome() {
router.push('/')
}
</script>
<template>
<div class="flex h-full">
<div class="px-4 m-auto space-y-4 text-center max-[400px]">
<h1 class="text-4xl text-slate-800 dark:text-neutral-200">
Sorry, page not found!
</h1>
<p class="text-base text-slate-500 dark:text-neutral-400">
Sorry, we couldn’t find the page you’re looking for. Perhaps you’ve mistyped the URL? Be sure to check your spelling.
</p>
<div class="flex items-center justify-center text-center">
<div class="w-[300px]">
<img src="../../../icons/404.svg" alt="404">
</div>
</div>
<NButton type="primary" @click="goHome">
Go to Home
</NButton>
</div>
</div>
</template>
<script lang="ts" setup>
import { NButton } from 'naive-ui'
import { useRouter } from 'vue-router'
import Icon500 from '@/icons/500.vue'
const router = useRouter()
function goHome() {
router.push('/')
}
</script>
<template>
<div class="flex h-full dark:bg-neutral-800">
<div class="px-4 m-auto space-y-4 text-center max-[400px]">
<header class="space-y-2">
<h2 class="text-2xl font-bold text-center text-slate-800 dark:text-neutral-200">
500
</h2>
<p class="text-base text-center text-slate-500 dark:text-slate-500">
Server error
</p>
<div class="flex items-center justify-center text-center">
<Icon500 class="w-[300px]" />
</div>
</header>
<NButton type="primary" @click="goHome">
Go to Home
</NButton>
</div>
</div>
</template>
cd ./service
start pnpm start > service.log &
echo "Start service complete!"
cd ..
echo "" > front.log
start pnpm dev > front.log &
echo "Start front complete!"
cd ./service
nohup pnpm start > service.log &
echo "Start service complete!"
cd ..
echo "" > front.log
nohup pnpm dev > front.log &
echo "Start front complete!"
tail -f front.log
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: 'class',
content: [
'./index.html',
'./src/**/*.{vue,js,ts,jsx,tsx}',
],
theme: {
extend: {
animation: {
blink: 'blink 1.2s infinite steps(1, start)',
},
keyframes: {
blink: {
'0%, 100%': { 'background-color': 'currentColor' },
'50%': { 'background-color': 'transparent' },
},
},
},
},
plugins: [],
}
{
"compilerOptions": {
"baseUrl": ".",
"module": "ESNext",
"target": "ESNext",
"lib": ["DOM", "ESNext"],
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"jsx": "preserve",
"moduleResolution": "node",
"resolveJsonModule": true,
"noUnusedLocals": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"paths": {
"@/*": ["./src/*"]
},
"types": ["vite/client", "node", "naive-ui/volar"]
},
"exclude": ["node_modules", "dist", "service"]
}
import path from 'path'
import type { PluginOption } from 'vite'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { VitePWA } from 'vite-plugin-pwa'
function setupPlugins(env: ImportMetaEnv): PluginOption[] {
return [
vue(),
env.VITE_GLOB_APP_PWA === 'true' && VitePWA({
injectRegister: 'auto',
manifest: {
name: 'chatGPT',
short_name: 'chatGPT',
icons: [
{ src: 'pwa-192x192.png', sizes: '192x192', type: 'image/png' },
{ src: 'pwa-512x512.png', sizes: '512x512', type: 'image/png' },
],
},
}),
]
}
export default defineConfig((env) => {
const viteEnv = loadEnv(env.mode, process.cwd()) as unknown as ImportMetaEnv
return {
resolve: {
alias: {
'@': path.resolve(process.cwd(), 'src'),
},
},
plugins: setupPlugins(viteEnv),
server: {
host: '0.0.0.0',
port: 1002,
open: false,
proxy: {
'/api': {
target: 'http://127.0.0.1:7861',
changeOrigin: true, // 允许跨域
rewrite: path => path.replace('/api/', ''),
},
},
},
build: {
reportCompressedSize: false,
sourcemap: false,
commonjsOptions: {
ignoreTryCatch: false,
},
},
}
})
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论