Compare commits

14 Commits

Author SHA1 Message Date
Redon
32ebbec8ad feat: 增加带格式的复制 (#182)
* feat: 增加带格式的复制

* feat: 移除前端超时设定

* chore: update deps

* feat: 添加权限页面

* feat: 设定页面优化

* feat: 更新 chatgpt 以支持 `gpt-3.5-turbo-0301`

* chore: version 2.9.0
2023-03-02 12:59:20 +08:00
Redon
42e320fe35 chore: version 2.8.3 (#175)
* feat: 保留已存在的内容直到手动操作

* feat: 支持复制文本

* chore: version 2.8.3
2023-03-01 13:20:31 +08:00
Redon
94e23bb916 chore: version 2.8.2 (#159)
* fix: 修复普通文本代码渲染和深色模式下的问题[#139][#154]

* chore: version 2.8.2
2023-02-28 10:00:53 +08:00
Redon
21fb4f817c fix: HTML 渲染异常 (#152)
* fix: 修复 `API` 版本 HTML 会被渲染的问题[#146]

* chore: version 2.8.1
2023-02-27 19:25:15 +08:00
二丫讲梵
2c509c329f feat: 调整镜像构建的tag,让最新代码指向latest,让每次版本指向日期 (#144)
* feat: 调整镜像构建的tag,让最新代码指向latest,让每次版本指向日期

* feat: 触发时机为main分支push或者relases的create,镜像的tag为分支名或tag名

* fix: 修复变量引用方式

* fix: 修复变量引用方式

* fix: 使用github.ref_name 取触发action的来源
2023-02-27 19:24:46 +08:00
ChenZhaoYu
4769c31d09 fix: white space 2023-02-27 13:01:42 +08:00
Redon
89712aada7 chore: version 2.8.0 (#140)
* fix: 用户输入换行符号被忽略的问题[#137]

* feat: 输入框添加换行提示,移动端`Enter` 判定换行而不是提交[#135]

* feat: 调整标签

* chore: version 2.8.0
2023-02-27 11:14:32 +08:00
ChenZhaoYu
3e9db49aad fix: code white-space 2023-02-26 20:24:49 +08:00
ChenZhaoYu
2dcd292dd4 chore: 移除关于不可靠的结论 2023-02-26 11:35:21 +08:00
Redon
90f6989445 feat: 移动端功能优化 (#132)
* feat: 移动端双击返回顶部

* feat: 深色模式下的底色

* feat: 移动端标题返回顶部和返回底部
2023-02-26 10:07:02 +08:00
Redon
1406292405 fix: 修复部份 bug (#131)
* fix: 主题模式图标不一致的问题[#125]

* fix: 修复样式问题[#123][#126]

* perf: 优化代码和添加类型
2023-02-25 22:44:25 +08:00
puppywang
628187f5c3 feat: add proxy support and fix streaming mode (#122) 2023-02-25 17:13:19 +08:00
Redon
cc91e95eed chore: version 2.7.3 (#120)
* fix: 用户输入也被渲染的问题 (#117)

* fix: 用户输入不转换

* feat: 基础深色模式适配

* feat: 主题模式跟随系统

* feat: 深色适配补漏

* chore: version 2.7.3
2023-02-25 00:19:13 +08:00
ChenZhaoYu
abbdcf9c51 chore: 更新封面 2023-02-24 16:45:25 +08:00
67 changed files with 2836 additions and 469 deletions

3
.env
View File

@@ -2,6 +2,3 @@
VITE_GLOB_API_URL=/api VITE_GLOB_API_URL=/api
VITE_APP_API_BASE_URL=http://localhost:3002/ VITE_APP_API_BASE_URL=http://localhost:3002/
# Glob API Timeout (ms)
VITE_GLOB_API_TIMEOUT=100000

View File

@@ -3,6 +3,8 @@ name: build_docker
on: on:
push: push:
branches: [main] branches: [main]
release:
types: [created] # 表示在创建新的 Release 时触发
jobs: jobs:
build_docker: build_docker:
@@ -11,11 +13,11 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Docker meta
id: meta - run: |
uses: docker/metadata-action@v4 echo "本次构建的版本为:${GITHUB_REF_NAME} (但是这个变量目前上下文中无法获取到)"
with: echo 本次构建的版本为:${{ github.ref_name }}
images: chenzhaoyu94/chatgpt-web env
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
@@ -32,6 +34,8 @@ jobs:
with: with:
context: . context: .
push: true push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/chatgpt-web:${{ github.ref_name }}
${{ secrets.DOCKERHUB_USERNAME }}/chatgpt-web:latest

View File

@@ -29,6 +29,7 @@
"dockerhub", "dockerhub",
"esno", "esno",
"GPTAPI", "GPTAPI",
"hljs",
"iconify", "iconify",
"logprobs", "logprobs",
"nodata", "nodata",

View File

@@ -1,3 +1,73 @@
## 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 ## v2.7.2
`2023-02-24` `2023-02-24`

View File

@@ -1,9 +1,9 @@
# ChatGPT Web # ChatGPT Web
> 使用 `express` 和 `vue3` 搭建的支持 `ChatGPT` 双模型演示网页 > 声明:此项目只发布于 Github基于 MIT 协议,免费且作为开源学习使用。并且不会有任何形式的卖号、付费服务、讨论群、讨论组等行为。谨防受骗。
![cover](./docs/cover.png) ![cover](./docs/c1-2.8.0.png)
![cover2](./docs/cover2.png) ![cover2](./docs/c2-2.8.0.png)
- [ChatGPT Web](#chatgpt-web) - [ChatGPT Web](#chatgpt-web)
- [介绍](#介绍) - [介绍](#介绍)
@@ -30,6 +30,7 @@
- [前端网页](#前端网页-1) - [前端网页](#前端网页-1)
- [常见问题](#常见问题) - [常见问题](#常见问题)
- [参与贡献](#参与贡献) - [参与贡献](#参与贡献)
- [赞助](#赞助)
- [License](#license) - [License](#license)
## 介绍 ## 介绍
@@ -38,14 +39,14 @@
| 方式 | 免费? | 可靠性 | 质量 | | 方式 | 免费? | 可靠性 | 质量 |
| ---- | ---- | ---- | ---- | | ---- | ---- | ---- | ---- |
| `ChatGPTAPI(GPT-3)` | 否 | 可靠 | 较笨 | | `ChatGPTAPI(GPT-3)` | 否 | 可靠 | 较笨 |
| `ChatGPTUnofficialProxyAPI(网页 accessToken)` | 是 | 不可靠 | 聪明 | | `ChatGPTUnofficialProxyAPI(网页 accessToken)` | 是 | 相对不可靠 | 聪明 |
***Note:*** 网页 `accessToken` 存在大约 8 小时,而且国内地区网络问题更推荐使用 `GPT-3` 的方式
对比: 对比:
1. `ChatGPTAPI` 使用 `text-davinci-003` 通过官方`OpenAI`补全`API`模拟`ChatGPT`(最稳健的方法,但它不是免费的,并且没有使用针对聊天进行微调的模型) 1. `ChatGPTAPI` 使用 `text-davinci-003` 通过官方`OpenAI`补全`API`模拟`ChatGPT`(最稳健的方法,但它不是免费的,并且没有使用针对聊天进行微调的模型)
2. `ChatGPTUnofficialProxyAPI` 使用非官方代理服务器访问 `ChatGPT` 的后端`API`,绕过`Cloudflare`(使用真实的的`ChatGPT`,非常轻量级,但依赖于第三方服务器,并且有速率限制) 2. `ChatGPTUnofficialProxyAPI` 使用非官方代理服务器访问 `ChatGPT` 的后端`API`,绕过`Cloudflare`(使用真实的的`ChatGPT`,非常轻量级,但依赖于第三方服务器,并且有速率限制)
[查看详情](https://github.com/Chanzhaoyu/chatgpt-web/issues/138)
切换方式: 切换方式:
1. 进入 `service/.env` 文件 1. 进入 `service/.env` 文件
2. 使用 `OpenAI API Key` 请填写 `OPENAI_API_KEY` 字段 [(获取 apiKey)](https://platform.openai.com/overview) 2. 使用 `OpenAI API Key` 请填写 `OPENAI_API_KEY` 字段 [(获取 apiKey)](https://platform.openai.com/overview)
@@ -173,7 +174,7 @@ version: '3'
services: services:
app: app:
image: chenzhaoyu94/chatgpt-web:main image: chenzhaoyu94/chatgpt-web # 总是使用latest,更新时重新pull该tag镜像即可
ports: ports:
- 3002:3002 - 3002:3002
environment: environment:
@@ -257,5 +258,20 @@ A: `vscode` 请安装项目推荐插件,或手动安装 `Eslint` 插件。
<img src="https://contrib.rocks/image?repo=Chanzhaoyu/chatgpt-web" /> <img src="https://contrib.rocks/image?repo=Chanzhaoyu/chatgpt-web" />
</a> </a>
## 赞助
如果你觉得这个项目对你有帮助,并且情况允许的话,可以给我一点点支持,总之非常感谢支持~
<div style="display: flex; gap: 20px;">
<div style="text-align: center">
<img style="max-width: 100%" src="./docs/wechat.png" alt="微信" />
<p>WeChat Pay</p>
</div>
<div style="text-align: center">
<img style="max-width: 100%" src="./docs/alipay.png" alt="支付宝" />
<p>Alipay</p>
</div>
</div>
## License ## License
MIT © [ChenZhaoYu](./license) MIT © [ChenZhaoYu](./license)

BIN
docs/alipay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
docs/c1-2.8.0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

BIN
docs/c1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

BIN
docs/c2-2.8.0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

BIN
docs/c2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
docs/wechat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -1,6 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-cmn-Hans"> <html lang="zh-cmn-Hans">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" type="image/svg+xml" href="/favicon.svg"> <link rel="icon" type="image/svg+xml" href="/favicon.svg">
@@ -9,7 +8,7 @@
<title>ChatGPT Web</title> <title>ChatGPT Web</title>
</head> </head>
<body> <body class="dark:bg-black">
<div id="app"> <div id="app">
<style> <style>
.loading-wrap { .loading-wrap {
@@ -61,6 +60,12 @@
transform: translateX(100%); transform: translateX(100%);
} }
} }
@media (prefers-color-scheme: dark) {
body {
background: #121212;
}
}
</style> </style>
<div class="loading-wrap"> <div class="loading-wrap">
<div class="balls"> <div class="balls">

View File

@@ -1,6 +1,6 @@
{ {
"name": "chatgpt-web", "name": "chatgpt-web",
"version": "2.7.2", "version": "2.9.0",
"private": false, "private": false,
"description": "ChatGPT Web", "description": "ChatGPT Web",
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>", "author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
@@ -24,7 +24,6 @@
}, },
"dependencies": { "dependencies": {
"@vueuse/core": "^9.13.0", "@vueuse/core": "^9.13.0",
"github-markdown-css": "^5.2.0",
"highlight.js": "^11.7.0", "highlight.js": "^11.7.0",
"marked": "^4.2.12", "marked": "^4.2.12",
"naive-ui": "^2.34.3", "naive-ui": "^2.34.3",

6
pnpm-lock.yaml generated
View File

@@ -15,7 +15,6 @@ specifiers:
axios: ^1.3.3 axios: ^1.3.3
crypto-js: ^4.1.1 crypto-js: ^4.1.1
eslint: ^8.34.0 eslint: ^8.34.0
github-markdown-css: ^5.2.0
highlight.js: ^11.7.0 highlight.js: ^11.7.0
husky: ^8.0.3 husky: ^8.0.3
less: ^4.1.3 less: ^4.1.3
@@ -35,7 +34,6 @@ specifiers:
dependencies: dependencies:
'@vueuse/core': 9.13.0_vue@3.2.47 '@vueuse/core': 9.13.0_vue@3.2.47
github-markdown-css: 5.2.0
highlight.js: 11.7.0 highlight.js: 11.7.0
marked: 4.2.12 marked: 4.2.12
naive-ui: 2.34.3_vue@3.2.47 naive-ui: 2.34.3_vue@3.2.47
@@ -2526,10 +2524,6 @@ packages:
through2: 4.0.2 through2: 4.0.2
dev: true dev: true
/github-markdown-css/5.2.0:
resolution: {integrity: sha512-hq5RaCInSUZ48bImOZpkppW2/MT44StRgsbsZ8YA4vJFwLKB/Vo3k7R2t+pUGqO+ThG0QDMi96TewV/B3vyItg==}
dev: false
/glob-parent/5.1.2: /glob-parent/5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'} engines: {node: '>= 6'}

View File

@@ -8,4 +8,4 @@ OPENAI_ACCESS_TOKEN=
API_REVERSE_PROXY= API_REVERSE_PROXY=
# timeout # timeout
TIMEOUT_MS=60000 TIMEOUT_MS=100000

View File

@@ -24,18 +24,20 @@
"common:cleanup": "rimraf node_modules && rimraf pnpm-lock.yaml" "common:cleanup": "rimraf node_modules && rimraf pnpm-lock.yaml"
}, },
"dependencies": { "dependencies": {
"chatgpt": "^4.7.2", "chatgpt": "^5.0.1",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"esno": "^0.16.3", "esno": "^0.16.3",
"express": "^4.18.2", "express": "^4.18.2",
"isomorphic-fetch": "^3.0.0" "isomorphic-fetch": "^3.0.0",
"node-fetch": "^3.3.0",
"socks-proxy-agent": "^7.0.0"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^0.35.2", "@antfu/eslint-config": "^0.35.3",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/node": "^18.14.0", "@types/node": "^18.14.3",
"eslint": "^8.34.0", "eslint": "^8.35.0",
"rimraf": "^4.1.2", "rimraf": "^4.1.3",
"tsup": "^6.6.3", "tsup": "^6.6.3",
"typescript": "^4.9.5" "typescript": "^4.9.5"
} }

417
service/pnpm-lock.yaml generated
View File

@@ -1,55 +1,59 @@
lockfileVersion: 5.4 lockfileVersion: 5.4
specifiers: specifiers:
'@antfu/eslint-config': ^0.35.2 '@antfu/eslint-config': ^0.35.3
'@types/express': ^4.17.17 '@types/express': ^4.17.17
'@types/node': ^18.14.0 '@types/node': ^18.14.3
chatgpt: ^4.7.2 chatgpt: ^5.0.1
dotenv: ^16.0.3 dotenv: ^16.0.3
eslint: ^8.34.0 eslint: ^8.35.0
esno: ^0.16.3 esno: ^0.16.3
express: ^4.18.2 express: ^4.18.2
isomorphic-fetch: ^3.0.0 isomorphic-fetch: ^3.0.0
rimraf: ^4.1.2 node-fetch: ^3.3.0
rimraf: ^4.1.3
socks-proxy-agent: ^7.0.0
tsup: ^6.6.3 tsup: ^6.6.3
typescript: ^4.9.5 typescript: ^4.9.5
dependencies: dependencies:
chatgpt: 4.7.2 chatgpt: 5.0.1
dotenv: 16.0.3 dotenv: 16.0.3
esno: 0.16.3 esno: 0.16.3
express: 4.18.2 express: 4.18.2
isomorphic-fetch: 3.0.0 isomorphic-fetch: 3.0.0
node-fetch: 3.3.0
socks-proxy-agent: 7.0.0
devDependencies: devDependencies:
'@antfu/eslint-config': 0.35.2_7kw3g6rralp5ps6mg3uyzz6azm '@antfu/eslint-config': 0.35.3_ycpbpc6yetojsgtrx3mwntkhsu
'@types/express': 4.17.17 '@types/express': 4.17.17
'@types/node': 18.14.0 '@types/node': 18.14.3
eslint: 8.34.0 eslint: 8.35.0
rimraf: 4.1.2 rimraf: 4.1.3
tsup: 6.6.3_typescript@4.9.5 tsup: 6.6.3_typescript@4.9.5
typescript: 4.9.5 typescript: 4.9.5
packages: packages:
/@antfu/eslint-config-basic/0.35.2_khyrr7my4aaa3ssgqabkfgcc24: /@antfu/eslint-config-basic/0.35.3_y46c5jzhegju5ppnhbs6d4llxi:
resolution: {integrity: sha512-2k7Ovkd8U/q0sgfjuT9KzAUV0q187yGxP7JzD2D4YifuJwL5Bh3JC49KpCv9PXMTeRUMJX2y/kOtGYPetocOug==} resolution: {integrity: sha512-NbWJKNgd3Ky3/ok2Z88cXNme/6I9otkiaB+FYLFgQE81sfMAhKpLKXtTSwzdcKMzhKDqUchAijt0BxjE/mcTJg==}
peerDependencies: peerDependencies:
eslint: '>=7.4.0' eslint: '>=7.4.0'
dependencies: dependencies:
eslint: 8.34.0 eslint: 8.35.0
eslint-plugin-antfu: 0.35.2_7kw3g6rralp5ps6mg3uyzz6azm eslint-plugin-antfu: 0.35.3_ycpbpc6yetojsgtrx3mwntkhsu
eslint-plugin-eslint-comments: 3.2.0_eslint@8.34.0 eslint-plugin-eslint-comments: 3.2.0_eslint@8.35.0
eslint-plugin-html: 7.1.0 eslint-plugin-html: 7.1.0
eslint-plugin-import: 2.27.5_mcvs2y73sfmcxqzpjj5lr7a2m4 eslint-plugin-import: 2.27.5_ajyizmi44oc3hrc35l6ndh7p4e
eslint-plugin-jsonc: 2.6.0_eslint@8.34.0 eslint-plugin-jsonc: 2.6.0_eslint@8.35.0
eslint-plugin-markdown: 3.0.0_eslint@8.34.0 eslint-plugin-markdown: 3.0.0_eslint@8.35.0
eslint-plugin-n: 15.6.1_eslint@8.34.0 eslint-plugin-n: 15.6.1_eslint@8.35.0
eslint-plugin-no-only-tests: 3.1.0 eslint-plugin-no-only-tests: 3.1.0
eslint-plugin-promise: 6.1.1_eslint@8.34.0 eslint-plugin-promise: 6.1.1_eslint@8.35.0
eslint-plugin-unicorn: 45.0.2_eslint@8.34.0 eslint-plugin-unicorn: 45.0.2_eslint@8.35.0
eslint-plugin-unused-imports: 2.0.0_vqeavzxdzk2atb75l6fx3anzpi eslint-plugin-unused-imports: 2.0.0_hlu2tevvfejtijvruutuci6rky
eslint-plugin-yml: 1.5.0_eslint@8.34.0 eslint-plugin-yml: 1.5.0_eslint@8.35.0
jsonc-eslint-parser: 2.1.0 jsonc-eslint-parser: 2.1.0
yaml-eslint-parser: 1.1.0 yaml-eslint-parser: 1.1.0
transitivePeerDependencies: transitivePeerDependencies:
@@ -61,17 +65,17 @@ packages:
- typescript - typescript
dev: true dev: true
/@antfu/eslint-config-ts/0.35.2_7kw3g6rralp5ps6mg3uyzz6azm: /@antfu/eslint-config-ts/0.35.3_ycpbpc6yetojsgtrx3mwntkhsu:
resolution: {integrity: sha512-GiJtTCQ83L/vMkJlWWg2l0buH/LIOXl5szrsqtr8/Hl6ssjAMXnug8sDZMCqCIZtztzQCewBPx8ufp/MpzA2cQ==} resolution: {integrity: sha512-FS5hir2ghXYlJWAiB2bpT9oAr0kpSNmYbaJWWkztocJG95AORl4tWzxMTkLT+TxaOmhuwJszcrMTHy5RgHL8/w==}
peerDependencies: peerDependencies:
eslint: '>=7.4.0' eslint: '>=7.4.0'
typescript: '>=3.9' typescript: '>=3.9'
dependencies: dependencies:
'@antfu/eslint-config-basic': 0.35.2_khyrr7my4aaa3ssgqabkfgcc24 '@antfu/eslint-config-basic': 0.35.3_y46c5jzhegju5ppnhbs6d4llxi
'@typescript-eslint/eslint-plugin': 5.52.0_6cfvjsbua5ptj65675bqcn6oza '@typescript-eslint/eslint-plugin': 5.54.0_6mj2wypvdnknez7kws2nfdgupi
'@typescript-eslint/parser': 5.52.0_7kw3g6rralp5ps6mg3uyzz6azm '@typescript-eslint/parser': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu
eslint: 8.34.0 eslint: 8.35.0
eslint-plugin-jest: 27.2.1_7hfwvekd5cgjoxqyvesymwuacm eslint-plugin-jest: 27.2.1_aere4n7c7ynvp62ae3ihfxuwhu
typescript: 4.9.5 typescript: 4.9.5
transitivePeerDependencies: transitivePeerDependencies:
- eslint-import-resolver-typescript - eslint-import-resolver-typescript
@@ -80,15 +84,15 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@antfu/eslint-config-vue/0.35.2_khyrr7my4aaa3ssgqabkfgcc24: /@antfu/eslint-config-vue/0.35.3_y46c5jzhegju5ppnhbs6d4llxi:
resolution: {integrity: sha512-98k9D+n0bgr/9OqedAEOsflIWbXz4D92pejXqkUAtnRnB2Nq5dWrrFMJ59oJwTDKnEslpwANlgIXM11qdiTF0Q==} resolution: {integrity: sha512-BA3vGLyuzqtEUb9gfgE7YzBT+a4oUnQuUPasIUfN/BVXaEhRVYlMmUgxN4ekQLuzOgUjUH13lqplXtkLJ62t9g==}
peerDependencies: peerDependencies:
eslint: '>=7.4.0' eslint: '>=7.4.0'
dependencies: dependencies:
'@antfu/eslint-config-basic': 0.35.2_khyrr7my4aaa3ssgqabkfgcc24 '@antfu/eslint-config-basic': 0.35.3_y46c5jzhegju5ppnhbs6d4llxi
'@antfu/eslint-config-ts': 0.35.2_7kw3g6rralp5ps6mg3uyzz6azm '@antfu/eslint-config-ts': 0.35.3_ycpbpc6yetojsgtrx3mwntkhsu
eslint: 8.34.0 eslint: 8.35.0
eslint-plugin-vue: 9.9.0_eslint@8.34.0 eslint-plugin-vue: 9.9.0_eslint@8.35.0
local-pkg: 0.4.3 local-pkg: 0.4.3
transitivePeerDependencies: transitivePeerDependencies:
- '@typescript-eslint/eslint-plugin' - '@typescript-eslint/eslint-plugin'
@@ -100,24 +104,24 @@ packages:
- typescript - typescript
dev: true dev: true
/@antfu/eslint-config/0.35.2_7kw3g6rralp5ps6mg3uyzz6azm: /@antfu/eslint-config/0.35.3_ycpbpc6yetojsgtrx3mwntkhsu:
resolution: {integrity: sha512-bbm7Yh7VgNI9ZaYs/L1oSTQwPMIdvlRtqxPfaBLpYdEvnsOuT4yX84TO0YaZ4RFjE4jqxQCSE/eioxydmWnl4w==} resolution: {integrity: sha512-wd0ry/TNqaZmniqkKtZKoCvpl55x9YbHgL5Ug3H9rVuUSqaNi9G9AjYlynQqn4/M1EhYYWO597Lu7f/fC+csrg==}
peerDependencies: peerDependencies:
eslint: '>=7.4.0' eslint: '>=7.4.0'
dependencies: dependencies:
'@antfu/eslint-config-vue': 0.35.2_khyrr7my4aaa3ssgqabkfgcc24 '@antfu/eslint-config-vue': 0.35.3_y46c5jzhegju5ppnhbs6d4llxi
'@typescript-eslint/eslint-plugin': 5.52.0_6cfvjsbua5ptj65675bqcn6oza '@typescript-eslint/eslint-plugin': 5.54.0_6mj2wypvdnknez7kws2nfdgupi
'@typescript-eslint/parser': 5.52.0_7kw3g6rralp5ps6mg3uyzz6azm '@typescript-eslint/parser': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu
eslint: 8.34.0 eslint: 8.35.0
eslint-plugin-eslint-comments: 3.2.0_eslint@8.34.0 eslint-plugin-eslint-comments: 3.2.0_eslint@8.35.0
eslint-plugin-html: 7.1.0 eslint-plugin-html: 7.1.0
eslint-plugin-import: 2.27.5_mcvs2y73sfmcxqzpjj5lr7a2m4 eslint-plugin-import: 2.27.5_ajyizmi44oc3hrc35l6ndh7p4e
eslint-plugin-jsonc: 2.6.0_eslint@8.34.0 eslint-plugin-jsonc: 2.6.0_eslint@8.35.0
eslint-plugin-n: 15.6.1_eslint@8.34.0 eslint-plugin-n: 15.6.1_eslint@8.35.0
eslint-plugin-promise: 6.1.1_eslint@8.34.0 eslint-plugin-promise: 6.1.1_eslint@8.35.0
eslint-plugin-unicorn: 45.0.2_eslint@8.34.0 eslint-plugin-unicorn: 45.0.2_eslint@8.35.0
eslint-plugin-vue: 9.9.0_eslint@8.34.0 eslint-plugin-vue: 9.9.0_eslint@8.35.0
eslint-plugin-yml: 1.5.0_eslint@8.34.0 eslint-plugin-yml: 1.5.0_eslint@8.35.0
jsonc-eslint-parser: 2.1.0 jsonc-eslint-parser: 2.1.0
yaml-eslint-parser: 1.1.0 yaml-eslint-parser: 1.1.0
transitivePeerDependencies: transitivePeerDependencies:
@@ -146,6 +150,10 @@ packages:
chalk: 2.4.2 chalk: 2.4.2
js-tokens: 4.0.0 js-tokens: 4.0.0
/@dqbd/tiktoken/0.4.0:
resolution: {integrity: sha512-iaHgmwKAOqowBFZKxelyszoeGLoNw62eOULcmyme1aA1Ymr3JgYl0V7jwpuUm7fksalycZajx3loFn9TRUaviw==}
dev: false
/@esbuild-kit/cjs-loader/2.4.2: /@esbuild-kit/cjs-loader/2.4.2:
resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==} resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==}
dependencies: dependencies:
@@ -343,18 +351,18 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@eslint-community/eslint-utils/4.1.2_eslint@8.34.0: /@eslint-community/eslint-utils/4.1.2_eslint@8.35.0:
resolution: {integrity: sha512-7qELuQWWjVDdVsFQ5+beUl+KPczrEDA7S3zM4QUd/bJl7oXgsmpXaEVqrRTnOBqenOV4rWf2kVZk2Ot085zPWA==} resolution: {integrity: sha512-7qELuQWWjVDdVsFQ5+beUl+KPczrEDA7S3zM4QUd/bJl7oXgsmpXaEVqrRTnOBqenOV4rWf2kVZk2Ot085zPWA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies: dependencies:
eslint: 8.34.0 eslint: 8.35.0
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
dev: true dev: true
/@eslint/eslintrc/1.4.1: /@eslint/eslintrc/2.0.0:
resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} resolution: {integrity: sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
ajv: 6.12.6 ajv: 6.12.6
@@ -370,6 +378,11 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@eslint/js/8.35.0:
resolution: {integrity: sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/@humanwhocodes/config-array/0.11.8: /@humanwhocodes/config-array/0.11.8:
resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==}
engines: {node: '>=10.10.0'} engines: {node: '>=10.10.0'}
@@ -415,19 +428,19 @@ packages:
resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
dependencies: dependencies:
'@types/connect': 3.4.35 '@types/connect': 3.4.35
'@types/node': 18.14.0 '@types/node': 18.14.3
dev: true dev: true
/@types/connect/3.4.35: /@types/connect/3.4.35:
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
dependencies: dependencies:
'@types/node': 18.14.0 '@types/node': 18.14.3
dev: true dev: true
/@types/express-serve-static-core/4.17.33: /@types/express-serve-static-core/4.17.33:
resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==}
dependencies: dependencies:
'@types/node': 18.14.0 '@types/node': 18.14.3
'@types/qs': 6.9.7 '@types/qs': 6.9.7
'@types/range-parser': 1.2.4 '@types/range-parser': 1.2.4
dev: true dev: true
@@ -459,8 +472,8 @@ packages:
resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
dev: true dev: true
/@types/node/18.14.0: /@types/node/18.14.3:
resolution: {integrity: sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==} resolution: {integrity: sha512-1y36CC5iL5CMyKALzwX9cwwxcWIxvIBe3gzs4GrXWXEQ8klQnCZ2U/WDGiNrXHmQcUhnaun17XG9TEIDlGj2RA==}
dev: true dev: true
/@types/normalize-package-data/2.4.1: /@types/normalize-package-data/2.4.1:
@@ -482,15 +495,15 @@ packages:
resolution: {integrity: sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==} resolution: {integrity: sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==}
dependencies: dependencies:
'@types/mime': 3.0.1 '@types/mime': 3.0.1
'@types/node': 18.14.0 '@types/node': 18.14.3
dev: true dev: true
/@types/unist/2.0.6: /@types/unist/2.0.6:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: true dev: true
/@typescript-eslint/eslint-plugin/5.52.0_6cfvjsbua5ptj65675bqcn6oza: /@typescript-eslint/eslint-plugin/5.54.0_6mj2wypvdnknez7kws2nfdgupi:
resolution: {integrity: sha512-lHazYdvYVsBokwCdKOppvYJKaJ4S41CgKBcPvyd0xjZNbvQdhn/pnJlGtQksQ/NhInzdaeaSarlBjDXHuclEbg==} resolution: {integrity: sha512-+hSN9BdSr629RF02d7mMtXhAJvDTyCbprNYJKrXETlul/Aml6YZwd90XioVbjejQeHbb3R8Dg0CkRgoJDxo8aw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
'@typescript-eslint/parser': ^5.0.0 '@typescript-eslint/parser': ^5.0.0
@@ -500,12 +513,12 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/parser': 5.52.0_7kw3g6rralp5ps6mg3uyzz6azm '@typescript-eslint/parser': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu
'@typescript-eslint/scope-manager': 5.52.0 '@typescript-eslint/scope-manager': 5.54.0
'@typescript-eslint/type-utils': 5.52.0_7kw3g6rralp5ps6mg3uyzz6azm '@typescript-eslint/type-utils': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu
'@typescript-eslint/utils': 5.52.0_7kw3g6rralp5ps6mg3uyzz6azm '@typescript-eslint/utils': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu
debug: 4.3.4 debug: 4.3.4
eslint: 8.34.0 eslint: 8.35.0
grapheme-splitter: 1.0.4 grapheme-splitter: 1.0.4
ignore: 5.2.4 ignore: 5.2.4
natural-compare-lite: 1.4.0 natural-compare-lite: 1.4.0
@@ -517,8 +530,8 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/parser/5.52.0_7kw3g6rralp5ps6mg3uyzz6azm: /@typescript-eslint/parser/5.54.0_ycpbpc6yetojsgtrx3mwntkhsu:
resolution: {integrity: sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==} resolution: {integrity: sha512-aAVL3Mu2qTi+h/r04WI/5PfNWvO6pdhpeMRWk9R7rEV4mwJNzoWf5CCU5vDKBsPIFQFjEq1xg7XBI2rjiMXQbQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -527,26 +540,26 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/scope-manager': 5.52.0 '@typescript-eslint/scope-manager': 5.54.0
'@typescript-eslint/types': 5.52.0 '@typescript-eslint/types': 5.54.0
'@typescript-eslint/typescript-estree': 5.52.0_typescript@4.9.5 '@typescript-eslint/typescript-estree': 5.54.0_typescript@4.9.5
debug: 4.3.4 debug: 4.3.4
eslint: 8.34.0 eslint: 8.35.0
typescript: 4.9.5 typescript: 4.9.5
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/scope-manager/5.52.0: /@typescript-eslint/scope-manager/5.54.0:
resolution: {integrity: sha512-AR7sxxfBKiNV0FWBSARxM8DmNxrwgnYMPwmpkC1Pl1n+eT8/I2NAUPuwDy/FmDcC6F8pBfmOcaxcxRHspgOBMw==} resolution: {integrity: sha512-VTPYNZ7vaWtYna9M4oD42zENOBrb+ZYyCNdFs949GcN8Miwn37b8b7eMj+EZaq7VK9fx0Jd+JhmkhjFhvnovhg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
'@typescript-eslint/types': 5.52.0 '@typescript-eslint/types': 5.54.0
'@typescript-eslint/visitor-keys': 5.52.0 '@typescript-eslint/visitor-keys': 5.54.0
dev: true dev: true
/@typescript-eslint/type-utils/5.52.0_7kw3g6rralp5ps6mg3uyzz6azm: /@typescript-eslint/type-utils/5.54.0_ycpbpc6yetojsgtrx3mwntkhsu:
resolution: {integrity: sha512-tEKuUHfDOv852QGlpPtB3lHOoig5pyFQN/cUiZtpw99D93nEBjexRLre5sQZlkMoHry/lZr8qDAt2oAHLKA6Jw==} resolution: {integrity: sha512-WI+WMJ8+oS+LyflqsD4nlXMsVdzTMYTxl16myXPaCXnSgc7LWwMsjxQFZCK/rVmTZ3FN71Ct78ehO9bRC7erYQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: '*' eslint: '*'
@@ -555,23 +568,23 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 5.52.0_typescript@4.9.5 '@typescript-eslint/typescript-estree': 5.54.0_typescript@4.9.5
'@typescript-eslint/utils': 5.52.0_7kw3g6rralp5ps6mg3uyzz6azm '@typescript-eslint/utils': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu
debug: 4.3.4 debug: 4.3.4
eslint: 8.34.0 eslint: 8.35.0
tsutils: 3.21.0_typescript@4.9.5 tsutils: 3.21.0_typescript@4.9.5
typescript: 4.9.5 typescript: 4.9.5
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/types/5.52.0: /@typescript-eslint/types/5.54.0:
resolution: {integrity: sha512-oV7XU4CHYfBhk78fS7tkum+/Dpgsfi91IIDy7fjCyq2k6KB63M6gMC0YIvy+iABzmXThCRI6xpCEyVObBdWSDQ==} resolution: {integrity: sha512-nExy+fDCBEgqblasfeE3aQ3NuafBUxZxgxXcYfzYRZFHdVvk5q60KhCSkG0noHgHRo/xQ/BOzURLZAafFpTkmQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
/@typescript-eslint/typescript-estree/5.52.0_typescript@4.9.5: /@typescript-eslint/typescript-estree/5.54.0_typescript@4.9.5:
resolution: {integrity: sha512-WeWnjanyEwt6+fVrSR0MYgEpUAuROxuAH516WPjUblIrClzYJj0kBbjdnbQXLpgAN8qbEuGywiQsXUVDiAoEuQ==} resolution: {integrity: sha512-X2rJG97Wj/VRo5YxJ8Qx26Zqf0RRKsVHd4sav8NElhbZzhpBI8jU54i6hfo9eheumj4oO4dcRN1B/zIVEqR/MQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
typescript: '*' typescript: '*'
@@ -579,8 +592,8 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/types': 5.52.0 '@typescript-eslint/types': 5.54.0
'@typescript-eslint/visitor-keys': 5.52.0 '@typescript-eslint/visitor-keys': 5.54.0
debug: 4.3.4 debug: 4.3.4
globby: 11.1.0 globby: 11.1.0
is-glob: 4.0.3 is-glob: 4.0.3
@@ -591,31 +604,31 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/utils/5.52.0_7kw3g6rralp5ps6mg3uyzz6azm: /@typescript-eslint/utils/5.54.0_ycpbpc6yetojsgtrx3mwntkhsu:
resolution: {integrity: sha512-As3lChhrbwWQLNk2HC8Ree96hldKIqk98EYvypd3It8Q1f8d5zWyIoaZEp2va5667M4ZyE7X8UUR+azXrFl+NA==} resolution: {integrity: sha512-cuwm8D/Z/7AuyAeJ+T0r4WZmlnlxQ8wt7C7fLpFlKMR+dY6QO79Cq1WpJhvZbMA4ZeZGHiRWnht7ZJ8qkdAunw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies: dependencies:
'@types/json-schema': 7.0.11 '@types/json-schema': 7.0.11
'@types/semver': 7.3.13 '@types/semver': 7.3.13
'@typescript-eslint/scope-manager': 5.52.0 '@typescript-eslint/scope-manager': 5.54.0
'@typescript-eslint/types': 5.52.0 '@typescript-eslint/types': 5.54.0
'@typescript-eslint/typescript-estree': 5.52.0_typescript@4.9.5 '@typescript-eslint/typescript-estree': 5.54.0_typescript@4.9.5
eslint: 8.34.0 eslint: 8.35.0
eslint-scope: 5.1.1 eslint-scope: 5.1.1
eslint-utils: 3.0.0_eslint@8.34.0 eslint-utils: 3.0.0_eslint@8.35.0
semver: 7.3.8 semver: 7.3.8
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
- typescript - typescript
dev: true dev: true
/@typescript-eslint/visitor-keys/5.52.0: /@typescript-eslint/visitor-keys/5.54.0:
resolution: {integrity: sha512-qMwpw6SU5VHCPr99y274xhbm+PRViK/NATY6qzt+Et7+mThGuFSl/ompj2/hrBlRP/kq+BFdgagnOSgw9TB0eA==} resolution: {integrity: sha512-xu4wT7aRCakGINTLGeyGqDn+78BwFlggwBjnHa1ar/KaGagnmwLYmlrXIrgAaQ3AE1Vd6nLfKASm7LrFHNbKGA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
'@typescript-eslint/types': 5.52.0 '@typescript-eslint/types': 5.54.0
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
dev: true dev: true
@@ -641,6 +654,15 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/agent-base/6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
dependencies:
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: false
/ajv-formats/2.1.1: /ajv-formats/2.1.1:
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependenciesMeta: peerDependenciesMeta:
@@ -717,10 +739,6 @@ packages:
is-string: 1.0.7 is-string: 1.0.7
dev: true dev: true
/array-keyed-map/2.1.3:
resolution: {integrity: sha512-JIUwuFakO+jHjxyp4YgSiKXSZeC0U+R1jR94bXWBcVlFRBycqXlb+kH9JHxBGcxnVuSqx5bnn0Qz9xtSeKOjiA==}
dev: false
/array-union/2.1.0: /array-union/2.1.0:
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -878,15 +896,15 @@ packages:
resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==}
dev: true dev: true
/chatgpt/4.7.2: /chatgpt/5.0.1:
resolution: {integrity: sha512-c5CNqvB98IMEz/Byopwu5FlXGS3w/3iNiZITdDlcZLue4VSjEfzMRWrOrdGidzcE+ud2My6nO8/sSnY7W04WJA==} resolution: {integrity: sha512-Wy+/2XL0FobiJFaQ6N5WnhRCnOwrUJCpoVCn67qqhiWrM1QW6lgmpvtDDKKDyvj7D1MLMjc2xB/kK8aT27mL/w==}
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
dependencies: dependencies:
'@dqbd/tiktoken': 0.4.0
cac: 6.7.14 cac: 6.7.14
conf: 11.0.1 conf: 11.0.1
eventsource-parser: 0.0.5 eventsource-parser: 0.0.5
gpt3-tokenizer: 1.1.5
keyv: 4.5.2 keyv: 4.5.2
p-timeout: 6.1.1 p-timeout: 6.1.1
quick-lru: 6.1.1 quick-lru: 6.1.1
@@ -999,6 +1017,11 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/data-uri-to-buffer/4.0.1:
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
engines: {node: '>= 12'}
dev: false
/debounce-fn/5.1.2: /debounce-fn/5.1.2:
resolution: {integrity: sha512-Sr4SdOZ4vw6eQDvPYNxHogvrxmCIld/VenC5JbNrFwMiwd7lY/Z18ZFfo+EWNG4DD9nFlAujWAo/wGuOPHmy5A==} resolution: {integrity: sha512-Sr4SdOZ4vw6eQDvPYNxHogvrxmCIld/VenC5JbNrFwMiwd7lY/Z18ZFfo+EWNG4DD9nFlAujWAo/wGuOPHmy5A==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -1038,7 +1061,6 @@ packages:
optional: true optional: true
dependencies: dependencies:
ms: 2.1.2 ms: 2.1.2
dev: true
/deep-is/0.1.4: /deep-is/0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@@ -1261,7 +1283,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/eslint-module-utils/2.7.4_npjqex3ey3rgd34fjcuucz7la4: /eslint-module-utils/2.7.4_qynxowrxvm2kj5rbowcxf5maga:
resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==}
engines: {node: '>=4'} engines: {node: '>=4'}
peerDependencies: peerDependencies:
@@ -1282,43 +1304,43 @@ packages:
eslint-import-resolver-webpack: eslint-import-resolver-webpack:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/parser': 5.52.0_7kw3g6rralp5ps6mg3uyzz6azm '@typescript-eslint/parser': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu
debug: 3.2.7 debug: 3.2.7
eslint: 8.34.0 eslint: 8.35.0
eslint-import-resolver-node: 0.3.7 eslint-import-resolver-node: 0.3.7
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/eslint-plugin-antfu/0.35.2_7kw3g6rralp5ps6mg3uyzz6azm: /eslint-plugin-antfu/0.35.3_ycpbpc6yetojsgtrx3mwntkhsu:
resolution: {integrity: sha512-Q6FOcOakafU49PDRlq7jkrWmlTJ4u3AW7aGX+FqeQNaMgjXq0RzPGvS0Vyp7q/XDRLLoe0BjbGkamTeZXg8waw==} resolution: {integrity: sha512-90Xct24s2n3aQhuuFFcPLhF5E6lU5s225B0VXupSjvDTuF+CmSQQLQG6KcqcdpA8O6dMbeXB9zy3SJ4aO7lndw==}
dependencies: dependencies:
'@typescript-eslint/utils': 5.52.0_7kw3g6rralp5ps6mg3uyzz6azm '@typescript-eslint/utils': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu
transitivePeerDependencies: transitivePeerDependencies:
- eslint - eslint
- supports-color - supports-color
- typescript - typescript
dev: true dev: true
/eslint-plugin-es/4.1.0_eslint@8.34.0: /eslint-plugin-es/4.1.0_eslint@8.35.0:
resolution: {integrity: sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==} resolution: {integrity: sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==}
engines: {node: '>=8.10.0'} engines: {node: '>=8.10.0'}
peerDependencies: peerDependencies:
eslint: '>=4.19.1' eslint: '>=4.19.1'
dependencies: dependencies:
eslint: 8.34.0 eslint: 8.35.0
eslint-utils: 2.1.0 eslint-utils: 2.1.0
regexpp: 3.2.0 regexpp: 3.2.0
dev: true dev: true
/eslint-plugin-eslint-comments/3.2.0_eslint@8.34.0: /eslint-plugin-eslint-comments/3.2.0_eslint@8.35.0:
resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==}
engines: {node: '>=6.5.0'} engines: {node: '>=6.5.0'}
peerDependencies: peerDependencies:
eslint: '>=4.19.1' eslint: '>=4.19.1'
dependencies: dependencies:
escape-string-regexp: 1.0.5 escape-string-regexp: 1.0.5
eslint: 8.34.0 eslint: 8.35.0
ignore: 5.2.4 ignore: 5.2.4
dev: true dev: true
@@ -1328,7 +1350,7 @@ packages:
htmlparser2: 8.0.1 htmlparser2: 8.0.1
dev: true dev: true
/eslint-plugin-import/2.27.5_mcvs2y73sfmcxqzpjj5lr7a2m4: /eslint-plugin-import/2.27.5_ajyizmi44oc3hrc35l6ndh7p4e:
resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==}
engines: {node: '>=4'} engines: {node: '>=4'}
peerDependencies: peerDependencies:
@@ -1338,15 +1360,15 @@ packages:
'@typescript-eslint/parser': '@typescript-eslint/parser':
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/parser': 5.52.0_7kw3g6rralp5ps6mg3uyzz6azm '@typescript-eslint/parser': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu
array-includes: 3.1.6 array-includes: 3.1.6
array.prototype.flat: 1.3.1 array.prototype.flat: 1.3.1
array.prototype.flatmap: 1.3.1 array.prototype.flatmap: 1.3.1
debug: 3.2.7 debug: 3.2.7
doctrine: 2.1.0 doctrine: 2.1.0
eslint: 8.34.0 eslint: 8.35.0
eslint-import-resolver-node: 0.3.7 eslint-import-resolver-node: 0.3.7
eslint-module-utils: 2.7.4_npjqex3ey3rgd34fjcuucz7la4 eslint-module-utils: 2.7.4_qynxowrxvm2kj5rbowcxf5maga
has: 1.0.3 has: 1.0.3
is-core-module: 2.11.0 is-core-module: 2.11.0
is-glob: 4.0.3 is-glob: 4.0.3
@@ -1361,7 +1383,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/eslint-plugin-jest/27.2.1_7hfwvekd5cgjoxqyvesymwuacm: /eslint-plugin-jest/27.2.1_aere4n7c7ynvp62ae3ihfxuwhu:
resolution: {integrity: sha512-l067Uxx7ZT8cO9NJuf+eJHvt6bqJyz2Z29wykyEdz/OtmcELQl2MQGQLX8J94O1cSJWAwUSEvCjwjA7KEK3Hmg==} resolution: {integrity: sha512-l067Uxx7ZT8cO9NJuf+eJHvt6bqJyz2Z29wykyEdz/OtmcELQl2MQGQLX8J94O1cSJWAwUSEvCjwjA7KEK3Hmg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies: peerDependencies:
@@ -1374,48 +1396,48 @@ packages:
jest: jest:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/eslint-plugin': 5.52.0_6cfvjsbua5ptj65675bqcn6oza '@typescript-eslint/eslint-plugin': 5.54.0_6mj2wypvdnknez7kws2nfdgupi
'@typescript-eslint/utils': 5.52.0_7kw3g6rralp5ps6mg3uyzz6azm '@typescript-eslint/utils': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu
eslint: 8.34.0 eslint: 8.35.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
- typescript - typescript
dev: true dev: true
/eslint-plugin-jsonc/2.6.0_eslint@8.34.0: /eslint-plugin-jsonc/2.6.0_eslint@8.35.0:
resolution: {integrity: sha512-4bA9YTx58QaWalua1Q1b82zt7eZMB7i+ed8q8cKkbKP75ofOA2SXbtFyCSok7RY6jIXeCqQnKjN9If8zCgv6PA==} resolution: {integrity: sha512-4bA9YTx58QaWalua1Q1b82zt7eZMB7i+ed8q8cKkbKP75ofOA2SXbtFyCSok7RY6jIXeCqQnKjN9If8zCgv6PA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: '>=6.0.0' eslint: '>=6.0.0'
dependencies: dependencies:
eslint: 8.34.0 eslint: 8.35.0
eslint-utils: 3.0.0_eslint@8.34.0 eslint-utils: 3.0.0_eslint@8.35.0
jsonc-eslint-parser: 2.1.0 jsonc-eslint-parser: 2.1.0
natural-compare: 1.4.0 natural-compare: 1.4.0
dev: true dev: true
/eslint-plugin-markdown/3.0.0_eslint@8.34.0: /eslint-plugin-markdown/3.0.0_eslint@8.35.0:
resolution: {integrity: sha512-hRs5RUJGbeHDLfS7ELanT0e29Ocyssf/7kBM+p7KluY5AwngGkDf8Oyu4658/NZSGTTq05FZeWbkxXtbVyHPwg==} resolution: {integrity: sha512-hRs5RUJGbeHDLfS7ELanT0e29Ocyssf/7kBM+p7KluY5AwngGkDf8Oyu4658/NZSGTTq05FZeWbkxXtbVyHPwg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies: dependencies:
eslint: 8.34.0 eslint: 8.35.0
mdast-util-from-markdown: 0.8.5 mdast-util-from-markdown: 0.8.5
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/eslint-plugin-n/15.6.1_eslint@8.34.0: /eslint-plugin-n/15.6.1_eslint@8.35.0:
resolution: {integrity: sha512-R9xw9OtCRxxaxaszTQmQAlPgM+RdGjaL1akWuY/Fv9fRAi8Wj4CUKc6iYVG8QNRjRuo8/BqVYIpfqberJUEacA==} resolution: {integrity: sha512-R9xw9OtCRxxaxaszTQmQAlPgM+RdGjaL1akWuY/Fv9fRAi8Wj4CUKc6iYVG8QNRjRuo8/BqVYIpfqberJUEacA==}
engines: {node: '>=12.22.0'} engines: {node: '>=12.22.0'}
peerDependencies: peerDependencies:
eslint: '>=7.0.0' eslint: '>=7.0.0'
dependencies: dependencies:
builtins: 5.0.1 builtins: 5.0.1
eslint: 8.34.0 eslint: 8.35.0
eslint-plugin-es: 4.1.0_eslint@8.34.0 eslint-plugin-es: 4.1.0_eslint@8.35.0
eslint-utils: 3.0.0_eslint@8.34.0 eslint-utils: 3.0.0_eslint@8.35.0
ignore: 5.2.4 ignore: 5.2.4
is-core-module: 2.11.0 is-core-module: 2.11.0
minimatch: 3.1.2 minimatch: 3.1.2
@@ -1428,27 +1450,27 @@ packages:
engines: {node: '>=5.0.0'} engines: {node: '>=5.0.0'}
dev: true dev: true
/eslint-plugin-promise/6.1.1_eslint@8.34.0: /eslint-plugin-promise/6.1.1_eslint@8.35.0:
resolution: {integrity: sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==} resolution: {integrity: sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: ^7.0.0 || ^8.0.0 eslint: ^7.0.0 || ^8.0.0
dependencies: dependencies:
eslint: 8.34.0 eslint: 8.35.0
dev: true dev: true
/eslint-plugin-unicorn/45.0.2_eslint@8.34.0: /eslint-plugin-unicorn/45.0.2_eslint@8.35.0:
resolution: {integrity: sha512-Y0WUDXRyGDMcKLiwgL3zSMpHrXI00xmdyixEGIg90gHnj0PcHY4moNv3Ppje/kDivdAy5vUeUr7z211ImPv2gw==} resolution: {integrity: sha512-Y0WUDXRyGDMcKLiwgL3zSMpHrXI00xmdyixEGIg90gHnj0PcHY4moNv3Ppje/kDivdAy5vUeUr7z211ImPv2gw==}
engines: {node: '>=14.18'} engines: {node: '>=14.18'}
peerDependencies: peerDependencies:
eslint: '>=8.28.0' eslint: '>=8.28.0'
dependencies: dependencies:
'@babel/helper-validator-identifier': 7.19.1 '@babel/helper-validator-identifier': 7.19.1
'@eslint-community/eslint-utils': 4.1.2_eslint@8.34.0 '@eslint-community/eslint-utils': 4.1.2_eslint@8.35.0
ci-info: 3.8.0 ci-info: 3.8.0
clean-regexp: 1.0.0 clean-regexp: 1.0.0
eslint: 8.34.0 eslint: 8.35.0
esquery: 1.4.0 esquery: 1.4.2
indent-string: 4.0.0 indent-string: 4.0.0
is-builtin-module: 3.2.1 is-builtin-module: 3.2.1
jsesc: 3.0.2 jsesc: 3.0.2
@@ -1462,7 +1484,7 @@ packages:
strip-indent: 3.0.0 strip-indent: 3.0.0
dev: true dev: true
/eslint-plugin-unused-imports/2.0.0_vqeavzxdzk2atb75l6fx3anzpi: /eslint-plugin-unused-imports/2.0.0_hlu2tevvfejtijvruutuci6rky:
resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==} resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@@ -1472,37 +1494,37 @@ packages:
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/eslint-plugin': 5.52.0_6cfvjsbua5ptj65675bqcn6oza '@typescript-eslint/eslint-plugin': 5.54.0_6mj2wypvdnknez7kws2nfdgupi
eslint: 8.34.0 eslint: 8.35.0
eslint-rule-composer: 0.3.0 eslint-rule-composer: 0.3.0
dev: true dev: true
/eslint-plugin-vue/9.9.0_eslint@8.34.0: /eslint-plugin-vue/9.9.0_eslint@8.35.0:
resolution: {integrity: sha512-YbubS7eK0J7DCf0U2LxvVP7LMfs6rC6UltihIgval3azO3gyDwEGVgsCMe1TmDiEkl6GdMKfRpaME6QxIYtzDQ==} resolution: {integrity: sha512-YbubS7eK0J7DCf0U2LxvVP7LMfs6rC6UltihIgval3azO3gyDwEGVgsCMe1TmDiEkl6GdMKfRpaME6QxIYtzDQ==}
engines: {node: ^14.17.0 || >=16.0.0} engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 eslint: ^6.2.0 || ^7.0.0 || ^8.0.0
dependencies: dependencies:
eslint: 8.34.0 eslint: 8.35.0
eslint-utils: 3.0.0_eslint@8.34.0 eslint-utils: 3.0.0_eslint@8.35.0
natural-compare: 1.4.0 natural-compare: 1.4.0
nth-check: 2.1.1 nth-check: 2.1.1
postcss-selector-parser: 6.0.11 postcss-selector-parser: 6.0.11
semver: 7.3.8 semver: 7.3.8
vue-eslint-parser: 9.1.0_eslint@8.34.0 vue-eslint-parser: 9.1.0_eslint@8.35.0
xml-name-validator: 4.0.0 xml-name-validator: 4.0.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/eslint-plugin-yml/1.5.0_eslint@8.34.0: /eslint-plugin-yml/1.5.0_eslint@8.35.0:
resolution: {integrity: sha512-iygN054g+ZrnYmtOXMnT+sx9iDNXt89/m0+506cQHeG0+5jJN8hY5iOPQLd3yfd50AfK/mSasajBWruf1SoHpQ==} resolution: {integrity: sha512-iygN054g+ZrnYmtOXMnT+sx9iDNXt89/m0+506cQHeG0+5jJN8hY5iOPQLd3yfd50AfK/mSasajBWruf1SoHpQ==}
engines: {node: ^14.17.0 || >=16.0.0} engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: '>=6.0.0' eslint: '>=6.0.0'
dependencies: dependencies:
debug: 4.3.4 debug: 4.3.4
eslint: 8.34.0 eslint: 8.35.0
lodash: 4.17.21 lodash: 4.17.21
natural-compare: 1.4.0 natural-compare: 1.4.0
yaml-eslint-parser: 1.1.0 yaml-eslint-parser: 1.1.0
@@ -1538,13 +1560,13 @@ packages:
eslint-visitor-keys: 1.3.0 eslint-visitor-keys: 1.3.0
dev: true dev: true
/eslint-utils/3.0.0_eslint@8.34.0: /eslint-utils/3.0.0_eslint@8.35.0:
resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
peerDependencies: peerDependencies:
eslint: '>=5' eslint: '>=5'
dependencies: dependencies:
eslint: 8.34.0 eslint: 8.35.0
eslint-visitor-keys: 2.1.0 eslint-visitor-keys: 2.1.0
dev: true dev: true
@@ -1563,12 +1585,13 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
/eslint/8.34.0: /eslint/8.35.0:
resolution: {integrity: sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==} resolution: {integrity: sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true hasBin: true
dependencies: dependencies:
'@eslint/eslintrc': 1.4.1 '@eslint/eslintrc': 2.0.0
'@eslint/js': 8.35.0
'@humanwhocodes/config-array': 0.11.8 '@humanwhocodes/config-array': 0.11.8
'@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/module-importer': 1.0.1
'@nodelib/fs.walk': 1.2.8 '@nodelib/fs.walk': 1.2.8
@@ -1579,10 +1602,10 @@ packages:
doctrine: 3.0.0 doctrine: 3.0.0
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint-scope: 7.1.1 eslint-scope: 7.1.1
eslint-utils: 3.0.0_eslint@8.34.0 eslint-utils: 3.0.0_eslint@8.35.0
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
espree: 9.4.1 espree: 9.4.1
esquery: 1.4.0 esquery: 1.4.2
esutils: 2.0.3 esutils: 2.0.3
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
file-entry-cache: 6.0.1 file-entry-cache: 6.0.1
@@ -1627,8 +1650,8 @@ packages:
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
dev: true dev: true
/esquery/1.4.0: /esquery/1.4.2:
resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} resolution: {integrity: sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
dependencies: dependencies:
estraverse: 5.3.0 estraverse: 5.3.0
@@ -1748,6 +1771,14 @@ packages:
reusify: 1.0.4 reusify: 1.0.4
dev: true dev: true
/fetch-blob/3.2.0:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 3.2.1
dev: false
/file-entry-cache/6.0.1: /file-entry-cache/6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0} engines: {node: ^10.12.0 || >=12.0.0}
@@ -1819,6 +1850,13 @@ packages:
is-callable: 1.2.7 is-callable: 1.2.7
dev: true dev: true
/formdata-polyfill/4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
dependencies:
fetch-blob: 3.2.0
dev: false
/forwarded/0.2.0: /forwarded/0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@@ -1949,13 +1987,6 @@ packages:
get-intrinsic: 1.2.0 get-intrinsic: 1.2.0
dev: true dev: true
/gpt3-tokenizer/1.1.5:
resolution: {integrity: sha512-O9iCL8MqGR0Oe9wTh0YftzIbysypNQmS5a5JG3cB3M4LMYjlAVvNnf8LUzVY9MrI7tj+YLY356uHtO2lLX2HpA==}
engines: {node: '>=12'}
dependencies:
array-keyed-map: 2.1.3
dev: false
/grapheme-splitter/1.0.4: /grapheme-splitter/1.0.4:
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
dev: true dev: true
@@ -2086,6 +2117,10 @@ packages:
side-channel: 1.0.4 side-channel: 1.0.4
dev: true dev: true
/ip/2.0.0:
resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==}
dev: false
/ipaddr.js/1.9.1: /ipaddr.js/1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
@@ -2509,7 +2544,6 @@ packages:
/ms/2.1.2: /ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true
/ms/2.1.3: /ms/2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -2535,6 +2569,11 @@ packages:
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
dev: false dev: false
/node-domexception/1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
dev: false
/node-fetch/2.6.9: /node-fetch/2.6.9:
resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==} resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==}
engines: {node: 4.x || >=6.0.0} engines: {node: 4.x || >=6.0.0}
@@ -2547,6 +2586,15 @@ packages:
whatwg-url: 5.0.0 whatwg-url: 5.0.0
dev: false dev: false
/node-fetch/3.3.0:
resolution: {integrity: sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
data-uri-to-buffer: 4.0.1
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
dev: false
/normalize-package-data/2.5.0: /normalize-package-data/2.5.0:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
dependencies: dependencies:
@@ -2959,8 +3007,8 @@ packages:
glob: 7.2.3 glob: 7.2.3
dev: true dev: true
/rimraf/4.1.2: /rimraf/4.1.3:
resolution: {integrity: sha512-BlIbgFryTbw3Dz6hyoWFhKk+unCcHMSkZGrTFVAx2WmttdBSonsdtRlwiuTbDqTKr+UlXIUqJVS4QT5tUzGENQ==} resolution: {integrity: sha512-iyzalDLo3l5FZxxaIGUY7xI4Bf90Xt7pCipc1Mr7RsdU7H3538z+M0tlsUDrz0aHeGS9uNqiKHUJyTewwRP91Q==}
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
dev: true dev: true
@@ -3083,6 +3131,30 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/smart-buffer/4.2.0:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
dev: false
/socks-proxy-agent/7.0.0:
resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==}
engines: {node: '>= 10'}
dependencies:
agent-base: 6.0.2
debug: 4.3.4
socks: 2.7.1
transitivePeerDependencies:
- supports-color
dev: false
/socks/2.7.1:
resolution: {integrity: sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==}
engines: {node: '>= 10.13.0', npm: '>= 3.0.0'}
dependencies:
ip: 2.0.0
smart-buffer: 4.2.0
dev: false
/source-map-support/0.5.21: /source-map-support/0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
dependencies: dependencies:
@@ -3422,24 +3494,29 @@ packages:
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
dev: false dev: false
/vue-eslint-parser/9.1.0_eslint@8.34.0: /vue-eslint-parser/9.1.0_eslint@8.35.0:
resolution: {integrity: sha512-NGn/iQy8/Wb7RrRa4aRkokyCZfOUWk19OP5HP6JEozQFX5AoS/t+Z0ZN7FY4LlmWc4FNI922V7cvX28zctN8dQ==} resolution: {integrity: sha512-NGn/iQy8/Wb7RrRa4aRkokyCZfOUWk19OP5HP6JEozQFX5AoS/t+Z0ZN7FY4LlmWc4FNI922V7cvX28zctN8dQ==}
engines: {node: ^14.17.0 || >=16.0.0} engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: '>=6.0.0' eslint: '>=6.0.0'
dependencies: dependencies:
debug: 4.3.4 debug: 4.3.4
eslint: 8.34.0 eslint: 8.35.0
eslint-scope: 7.1.1 eslint-scope: 7.1.1
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
espree: 9.4.1 espree: 9.4.1
esquery: 1.4.0 esquery: 1.4.2
lodash: 4.17.21 lodash: 4.17.21
semver: 7.3.8 semver: 7.3.8
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/web-streams-polyfill/3.2.1:
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
engines: {node: '>= 8'}
dev: false
/webidl-conversions/3.0.1: /webidl-conversions/3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
dev: false dev: false

View File

@@ -1,81 +0,0 @@
import * as dotenv from 'dotenv'
import 'isomorphic-fetch'
import type { ChatGPTAPI, ChatMessage, SendMessageOptions } from 'chatgpt'
import { ChatGPTUnofficialProxyAPI } from 'chatgpt'
import { sendResponse } from './utils'
dotenv.config()
let apiModel: 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' | undefined
interface ChatContext {
conversationId?: string
parentMessageId?: string
}
const timeoutMs: number = !isNaN(+process.env.TIMEOUT_MS) ? +process.env.TIMEOUT_MS : 30 * 1000
if (!process.env.OPENAI_API_KEY && !process.env.OPENAI_ACCESS_TOKEN)
throw new Error('Missing OPENAI_API_KEY or OPENAI_ACCESS_TOKEN environment variable')
let api: ChatGPTAPI | ChatGPTUnofficialProxyAPI
// To use ESM in CommonJS, you can use a dynamic import
(async () => {
// More Info: https://github.com/transitive-bullshit/chatgpt-api
const { ChatGPTAPI } = await import('chatgpt')
if (process.env.OPENAI_API_KEY) {
api = new ChatGPTAPI({ apiKey: process.env.OPENAI_API_KEY })
apiModel = 'ChatGPTAPI'
}
else {
let options = {}
if (process.env.API_REVERSE_PROXY)
options = { apiReverseProxyUrl: process.env.API_REVERSE_PROXY }
api = new ChatGPTUnofficialProxyAPI({
accessToken: process.env.OPENAI_ACCESS_TOKEN,
...options,
})
apiModel = 'ChatGPTUnofficialProxyAPI'
}
})()
async function chatReply(
message: string,
lastContext?: { conversationId?: string; parentMessageId?: string },
) {
if (!message)
return sendResponse({ type: 'Fail', message: 'Message is empty' })
try {
let options: SendMessageOptions = { timeoutMs }
if (lastContext)
options = { ...lastContext }
const response = await api.sendMessage(message, { ...options })
return sendResponse({ type: 'Success', data: response })
}
catch (error: any) {
return sendResponse({ type: 'Fail', message: error.message })
}
}
async function chatConfig() {
return sendResponse({
type: 'Success',
data: {
apiModel,
reverseProxy: process.env.API_REVERSE_PROXY,
timeoutMs,
},
})
}
export type { ChatContext, ChatMessage }
export { chatReply, chatConfig }

View File

@@ -0,0 +1,125 @@
import * as dotenv from 'dotenv'
import 'isomorphic-fetch'
import type { ChatMessage, SendMessageOptions } from 'chatgpt'
import { ChatGPTAPI, ChatGPTUnofficialProxyAPI } from 'chatgpt'
import { SocksProxyAgent } from 'socks-proxy-agent'
import fetch from 'node-fetch'
import { sendResponse } from '../utils'
import type { ApiModel, ChatContext, ChatGPTAPIOptions, ChatGPTUnofficialProxyAPIOptions, ModelConfig } from '../types'
dotenv.config()
const timeoutMs: number = !isNaN(+process.env.TIMEOUT_MS) ? +process.env.TIMEOUT_MS : 30 * 1000
let apiModel: ApiModel
if (!process.env.OPENAI_API_KEY && !process.env.OPENAI_ACCESS_TOKEN)
throw new Error('Missing OPENAI_API_KEY or OPENAI_ACCESS_TOKEN environment variable')
let api: ChatGPTAPI | ChatGPTUnofficialProxyAPI
// To use ESM in CommonJS, you can use a dynamic import
(async () => {
// More Info: https://github.com/transitive-bullshit/chatgpt-api
if (process.env.OPENAI_API_KEY) {
const options: ChatGPTAPIOptions = {
apiKey: process.env.OPENAI_API_KEY,
debug: false,
}
api = new ChatGPTAPI({ ...options })
apiModel = 'ChatGPTAPI'
}
else {
const options: ChatGPTUnofficialProxyAPIOptions = {
accessToken: process.env.OPENAI_ACCESS_TOKEN,
debug: false,
}
if (process.env.SOCKS_PROXY_HOST && process.env.SOCKS_PROXY_PORT) {
const agent = new SocksProxyAgent({
hostname: process.env.SOCKS_PROXY_HOST,
port: process.env.SOCKS_PROXY_PORT,
})
options.fetch = (url, options) => {
return fetch(url, { agent, ...options })
}
}
if (process.env.API_REVERSE_PROXY)
options.apiReverseProxyUrl = process.env.API_REVERSE_PROXY
api = new ChatGPTUnofficialProxyAPI({
accessToken: process.env.OPENAI_ACCESS_TOKEN,
...options,
})
apiModel = 'ChatGPTUnofficialProxyAPI'
}
})()
async function chatReply(
message: string,
lastContext?: { conversationId?: string; parentMessageId?: string },
) {
if (!message)
return sendResponse({ type: 'Fail', message: 'Message is empty' })
try {
let options: SendMessageOptions = { timeoutMs }
if (lastContext)
options = { ...lastContext }
const response = await api.sendMessage(message, { ...options })
return sendResponse({ type: 'Success', data: response })
}
catch (error: any) {
return sendResponse({ type: 'Fail', message: error.message })
}
}
async function chatReplyProcess(
message: string,
lastContext?: { conversationId?: string; parentMessageId?: string },
process?: (chat: ChatMessage) => void,
) {
if (!message)
return sendResponse({ type: 'Fail', message: 'Message is empty' })
try {
let options: SendMessageOptions = { timeoutMs }
if (lastContext)
options = { ...lastContext }
const response = await api.sendMessage(message, {
...options,
onProgress: (partialResponse) => {
process?.(partialResponse)
},
})
return sendResponse({ type: 'Success', data: response })
}
catch (error: any) {
return sendResponse({ type: 'Fail', message: error.message })
}
}
async function chatConfig() {
return sendResponse({
type: 'Success',
data: {
apiModel,
reverseProxy: process.env.API_REVERSE_PROXY,
timeoutMs,
socksProxy: (process.env.SOCKS_PROXY_HOST && process.env.SOCKS_PROXY_PORT) ? (`${process.env.SOCKS_PROXY_HOST}:${process.env.SOCKS_PROXY_PORT}`) : '-',
} as ModelConfig,
})
}
export type { ChatContext, ChatMessage }
export { chatReply, chatReplyProcess, chatConfig }

View File

@@ -1,6 +1,6 @@
import express from 'express' import express from 'express'
import type { ChatContext } from './chatgpt' import type { ChatContext, ChatMessage } from './chatgpt'
import { chatConfig, chatReply } from './chatgpt' import { chatConfig, chatReply, chatReplyProcess } from './chatgpt'
const app = express() const app = express()
const router = express.Router() const router = express.Router()
@@ -26,6 +26,25 @@ router.post('/chat', async (req, res) => {
} }
}) })
router.post('/chat-process', async (req, res) => {
res.setHeader('Content-type', 'application/octet-stream')
try {
const { prompt, options = {} } = req.body as { prompt: string; options?: ChatContext }
let firstChunk = true
await chatReplyProcess(prompt, options, (chat: ChatMessage) => {
res.write(firstChunk ? JSON.stringify(chat) : `\n${JSON.stringify(chat)}`)
firstChunk = false
})
}
catch (error) {
res.write(JSON.stringify(error))
}
finally {
res.end()
}
})
router.post('/config', async (req, res) => { router.post('/config', async (req, res) => {
try { try {
const response = await chatConfig() const response = await chatConfig()

30
service/src/types.ts Normal file
View File

@@ -0,0 +1,30 @@
import type { FetchFn, openai } from 'chatgpt'
export interface ChatContext {
conversationId?: string
parentMessageId?: string
}
export interface ChatGPTAPIOptions {
apiKey: string
debug?: boolean
completionParams?: Partial<openai.CompletionParams>
}
export interface ChatGPTUnofficialProxyAPIOptions {
accessToken: string
apiReverseProxyUrl?: string
model?: string
debug?: boolean
headers?: Record<string, string>
fetch?: FetchFn
}
export interface ModelConfig {
apiModel?: ApiModel
reverseProxy?: string
timeoutMs?: number
socksProxy?: string
}
export type ApiModel = 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' | undefined

View File

@@ -1,10 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { NConfigProvider } from 'naive-ui' import { NConfigProvider } from 'naive-ui'
import { NaiveProvider } from '@/components/common' import { NaiveProvider } from '@/components/common'
import { useTheme } from '@/hooks/useTheme'
import { useLanguage } from '@/hooks/useLanguage'
const { theme, themeOverrides } = useTheme()
const { language } = useLanguage()
</script> </script>
<template> <template>
<NConfigProvider class="h-full"> <NConfigProvider
class="h-full"
:theme="theme"
:theme-overrides="themeOverrides"
:locale="language"
>
<NaiveProvider> <NaiveProvider>
<RouterView /> <RouterView />
</NaiveProvider> </NaiveProvider>

View File

@@ -1,4 +1,4 @@
import type { GenericAbortSignal } from 'axios' import type { AxiosProgressEvent, GenericAbortSignal } from 'axios'
import { post } from '@/utils/request' import { post } from '@/utils/request'
export function fetchChatAPI<T = any>( export function fetchChatAPI<T = any>(
@@ -18,3 +18,18 @@ export function fetchChatConfig<T = any>() {
url: '/config', url: '/config',
}) })
} }
export function fetchChatAPIProcess<T = any>(
params: {
prompt: string
options?: { conversationId?: string; parentMessageId?: string }
signal?: GenericAbortSignal
onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void },
) {
return post<T>({
url: '/chat-process',
data: { prompt: params.prompt, options: params.options },
signal: params.signal,
onDownloadProgress: params.onDownloadProgress,
})
}

View File

@@ -1 +0,0 @@
export { }

View File

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

View File

@@ -0,0 +1,62 @@
<script setup lang='ts'>
import { onMounted, ref } from 'vue'
import { NSpin } from 'naive-ui'
import { fetchChatConfig } from '@/api'
import pkg from '@/../package.json'
interface ConfigState {
timeoutMs?: number
reverseProxy?: string
apiModel?: string
socksProxy?: string
}
const loading = ref(false)
const config = ref<ConfigState>()
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>
免费并且没有任何形式分付费行为
</p>
<p>
如果你觉得此项目对你有帮助请在 Github 帮我点个 Star 或者给予一点赞助谢谢
</p>
</div>
<p>API方式{{ config?.apiModel ?? '-' }}</p>
<p>反向代理{{ config?.reverseProxy ?? '-' }}</p>
<p>超时时间{{ config?.timeoutMs ?? '-' }}</p>
<p>Socks代理{{ config?.socksProxy ?? '-' }}</p>
</div>
</NSpin>
</template>

View File

@@ -0,0 +1,144 @@
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { NButton, NInput, 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'
interface Emit {
(event: 'update'): void
}
const emit = defineEmits<Emit>()
const appStore = useAppStore()
const userStore = useUserStore()
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: 'English', key: 'en-US', value: 'en-US' },
]
function updateUserInfo(options: Partial<UserInfo>) {
userStore.updateUserInfo(options)
ms.success('Update success')
}
function handleReset() {
userStore.resetUserInfo()
ms.success('Reset success')
emit('update')
}
</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]">Avatar Link</span>
<div class="flex-1">
<NInput v-model:value="avatar" placeholder="" />
</div>
<NButton size="tiny" text type="primary" @click="updateUserInfo({ avatar })">
Save
</NButton>
</div>
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">Name</span>
<div class="w-[200px]">
<NInput v-model:value="name" placeholder="" />
</div>
<NButton size="tiny" text type="primary" @click="updateUserInfo({ name })">
Save
</NButton>
</div>
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">Description</span>
<div class="flex-1">
<NInput v-model:value="description" placeholder="" />
</div>
<NButton size="tiny" text type="primary" @click="updateUserInfo({ description })">
Save
</NButton>
</div>
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">Reset UserInfo</span>
<NButton text type="primary" @click="handleReset">
Reset
</NButton>
</div>
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">Theme</span>
<div class="flex items-center space-x-4">
<template v-for="item of themeOptions" :key="item.key">
<a
class="flex items-center justify-center h-8 px-4 border rounded-md cursor-pointer dark:border-neutral-700"
:class="item.key === theme && ['bg-[#4ca85e]', 'border-[#4ca85e]', 'text-white']"
@click="appStore.setTheme(item.key)"
>
<span class="text-xl">
<SvgIcon :icon="item.icon" />
</span>
</a>
</template>
</div>
</div>
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">Language</span>
<div class="flex items-center space-x-4">
<template v-for="item of languageOptions" :key="item.key">
<a
class="flex items-center justify-center h-8 px-4 border rounded-md cursor-pointer dark:border-neutral-700"
:class="item.key === language && ['bg-[#4ca85e]', 'border-[#4ca85e]', 'text-white']"
@click="appStore.setLanguage(item.key)"
>
<span class="text-sm">
{{ item.label }}
</span>
</a>
</template>
</div>
</div>
</div>
</div>
</template>

View File

@@ -1,8 +1,9 @@
<script setup lang='ts'> <script setup lang='ts'>
import { computed, ref, watch } from 'vue' import { computed, ref } from 'vue'
import { NCard, NModal } from 'naive-ui' import { NCard, NModal, NTabPane, NTabs } from 'naive-ui'
import pkg from '../../../../package.json' import General from './General.vue'
import { fetchChatConfig } from '@/api' import About from './About.vue'
import { SvgIcon } from '@/components/common'
interface Props { interface Props {
visible: boolean visible: boolean
@@ -12,16 +13,14 @@ interface Emit {
(e: 'update:visible', visible: boolean): void (e: 'update:visible', visible: boolean): void
} }
interface ConfigState {
timeoutMs?: number
reverseProxy?: string
apiModel?: string
}
const props = defineProps<Props>() const props = defineProps<Props>()
const emit = defineEmits<Emit>() const emit = defineEmits<Emit>()
const active = ref('General')
const reload = ref(false)
const show = computed({ const show = computed({
get() { get() {
return props.visible return props.visible
@@ -31,45 +30,35 @@ const show = computed({
}, },
}) })
const config = ref<ConfigState>() function handleReload() {
reload.value = true
async function fetchConfig() { setTimeout(() => {
try { reload.value = false
const { data } = await fetchChatConfig<ConfigState>() }, 0)
config.value = data
}
catch (error) {
// ...
}
} }
watch(
() => props.visible,
(val) => {
if (val)
fetchConfig()
},
)
</script> </script>
<template> <template>
<NModal v-model:show="show" style="width: 80%; max-width: 460px;"> <NModal v-model:show="show">
<NCard> <NCard role="dialog" aria-modal="true" :bordered="false" style="width: 100%; max-width: 640px">
<div class="space-y-4"> <NTabs v-model:value="active" type="line" animated>
<h2 class="text-xl font-bold text-center"> <NTabPane name="General" tab="General">
Version - {{ pkg.version }} <template #tab>
</h2> <SvgIcon class="text-lg" icon="ri:file-user-line" />
<hr> <span class="ml-2">General</span>
<p> </template>
此项目开源于 <div class="min-h-[100px]">
<a class="text-blue-600" href="https://github.com/Chanzhaoyu/chatgpt-web" target="_blank">Github</a> <General v-if="!reload" @update="handleReload" />
免费并且协议为 MIT其他来源均为盗版使用时请注意如果你觉得此项目对你有帮助请帮我点个 Star谢谢 </div>
</p> </NTabPane>
<hr> <NTabPane name="Config" tab="Config">
<p>API方式{{ config?.apiModel ?? '-' }}</p> <template #tab>
<p>反向代理{{ config?.reverseProxy ?? '-' }}</p> <SvgIcon class="text-lg" icon="ri:list-settings-line" />
<p>超时时间{{ config?.timeoutMs ?? '-' }}</p> <span class="ml-2">Config</span>
</div> </template>
<About />
</NTabPane>
</NTabs>
</NCard> </NCard>
</NModal> </NModal>
</template> </template>

View File

@@ -1,18 +1,39 @@
<script setup lang='ts'> <script setup lang='ts'>
import { GithubSite } from '@/components/custom' 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> </script>
<template> <template>
<div class="flex items-center"> <div class="flex items-center">
<div class="w-10 h-10 overflow-hidden rounded-full"> <div class="w-10 h-10 overflow-hidden rounded-full">
<img class="object-cover" src="@/assets/avatar.jpg" alt="avatar"> <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>
<div class="ml-2"> <div class="ml-2">
<h2 class="font-bold text-md"> <h2 class="font-bold text-md">
ChenZhaoYu {{ userInfo.name ?? 'ChenZhaoYu' }}
</h2> </h2>
<p class="text-xs text-gray-500"> <p class="text-xs text-gray-500">
<GithubSite /> <span
v-if="isString(userInfo.description) && userInfo.description !== ''"
v-html="userInfo.description"
/>
</p> </p>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="text-center text-neutral-500"> <div class="text-neutral-400">
<span> Star on</span> <span>Star on</span>
<a href="https://github.com/Chanzhaoyu/chatgpt-bot" target="_blank" class="text-blue-500"> <a href="https://github.com/Chanzhaoyu/chatgpt-bot" target="_blank" class="text-blue-500">
GitHub GitHub
</a> </a>

View File

@@ -0,0 +1,23 @@
import type { App, Directive } from 'vue'
import hljs from 'highlight.js'
import { includeCode } from '@/utils/format'
hljs.configure({ ignoreUnescapedHTML: true })
function highlightCode(el: HTMLElement) {
if (includeCode(el.textContent))
hljs.highlightBlock(el)
}
export default function setupHighlightDirective(app: App) {
const highLightDirective: Directive<HTMLElement> = {
mounted(el: HTMLElement) {
highlightCode(el)
},
updated(el: HTMLElement) {
highlightCode(el)
},
}
app.directive('highlight', highLightDirective)
}

View File

@@ -1 +1,6 @@
export function setupDirectives() {} import type { App } from 'vue'
import setupHighlightDirective from './highlight'
export function setupDirectives(app: App) {
setupHighlightDirective(app)
}

View File

@@ -0,0 +1,36 @@
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,
}
}

16
src/hooks/useLanguage.ts Normal file
View File

@@ -0,0 +1,16 @@
import { computed } from 'vue'
import { enUS, zhCN } from 'naive-ui'
import { useAppStore } from '@/store'
export function useLanguage() {
const appStore = useAppStore()
const language = computed(() => {
if (appStore.language === 'zh-CN')
return zhCN
else
return enUS
})
return { language }
}

43
src/hooks/useTheme.ts Normal file
View File

@@ -0,0 +1,43 @@
import type { GlobalThemeOverrides } from 'naive-ui'
import { computed, watch } from 'vue'
import { darkTheme, useOsTheme } from 'naive-ui'
import { useAppStore } from '@/store'
export function useTheme() {
const appStore = useAppStore()
const OsTheme = useOsTheme()
const isDark = computed(() => {
if (appStore.theme === 'auto')
return OsTheme.value === 'dark'
else
return appStore.theme === 'dark'
})
const theme = computed(() => {
return isDark.value ? darkTheme : undefined
})
const themeOverrides = computed<GlobalThemeOverrides>(() => {
if (isDark.value) {
return {
common: {},
}
}
return {}
})
watch(
() => isDark.value,
(dark) => {
if (dark)
document.documentElement.classList.add('dark')
else
document.documentElement.classList.remove('dark')
},
{ immediate: true },
)
return { theme, themeOverrides }
}

1
src/icons/403.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

1
src/icons/404.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -1,5 +1,6 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import { setupDirectives } from './directives'
import { setupAssets } from '@/plugins' import { setupAssets } from '@/plugins'
import { setupStore } from '@/store' import { setupStore } from '@/store'
import { setupRouter } from '@/router' import { setupRouter } from '@/router'
@@ -10,6 +11,8 @@ async function bootstrap() {
setupStore(app) setupStore(app)
setupDirectives(app)
await setupRouter(app) await setupRouter(app)
app.mount('#app') app.mount('#app')

View File

@@ -1,6 +1,7 @@
import 'highlight.js/styles/xcode.css' import '@/styles/lib/tailwind.css'
import 'github-markdown-css/github-markdown.css' import '@/styles/lib/highlight.less'
import '@/styles/global.css' import '@/styles/lib/github-markdown.less'
import '@/styles/global.less'
/** Tailwind's Preflight Style Override */ /** Tailwind's Preflight Style Override */
function naiveStyleOverride() { function naiveStyleOverride() {

View File

@@ -17,6 +17,24 @@ const routes: RouteRecordRaw[] = [
}, },
], ],
}, },
{
path: '/403',
name: '403',
component: () => import('@/views/exception/403/index.vue'),
},
{
path: '/404',
name: '404',
component: () => import('@/views/exception/404/index.vue'),
},
{
path: '/:pathMatch(.*)*',
name: 'notFound',
redirect: '/404',
},
] ]
export const router = createRouter({ export const router = createRouter({

View File

@@ -2,19 +2,25 @@ import { ss } from '@/utils/storage'
const LOCAL_NAME = 'appSetting' const LOCAL_NAME = 'appSetting'
export type Theme = 'light' | 'dark' | 'auto'
export type Language = 'zh-CN' | 'en-US'
export interface AppState { export interface AppState {
siderCollapsed: boolean siderCollapsed: boolean
theme: Theme
language: Language
} }
export function defaultSetting() { export function defaultSetting(): AppState {
return { siderCollapsed: false } return { siderCollapsed: false, theme: 'light', language: 'zh-CN' }
} }
export function getLocalSetting() { export function getLocalSetting(): AppState {
const localSetting: AppState | undefined = ss.get(LOCAL_NAME) const localSetting: AppState | undefined = ss.get(LOCAL_NAME)
return localSetting ?? defaultSetting() return { ...defaultSetting(), ...localSetting }
} }
export function setLocalSetting(setting: AppState) { export function setLocalSetting(setting: AppState): void {
ss.set(LOCAL_NAME, setting) ss.set(LOCAL_NAME, setting)
} }

View File

@@ -1,5 +1,5 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import type { AppState } from './helper' import type { AppState, Language, Theme } from './helper'
import { getLocalSetting, setLocalSetting } from './helper' import { getLocalSetting, setLocalSetting } from './helper'
export const useAppStore = defineStore('app-store', { export const useAppStore = defineStore('app-store', {
@@ -7,6 +7,22 @@ export const useAppStore = defineStore('app-store', {
actions: { actions: {
setSiderCollapsed(collapsed: boolean) { setSiderCollapsed(collapsed: boolean) {
this.siderCollapsed = collapsed 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) setLocalSetting(this.$state)
}, },
}, },

View File

@@ -6,6 +6,13 @@ export const useChatStore = defineStore('chat-store', {
state: (): Chat.ChatState => getLocalState(), state: (): Chat.ChatState => getLocalState(),
getters: { 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) { getChatByUuid(state: Chat.ChatState) {
return (uuid?: number) => { return (uuid?: number) => {
if (uuid) if (uuid)
@@ -68,6 +75,18 @@ export const useChatStore = defineStore('chat-store', {
return await this.reloadRoute(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) { addChatByUuid(uuid: number, chat: Chat.Chat) {
if (!uuid || uuid === 0) { if (!uuid || uuid === 0) {
if (this.history.length === 0) { if (this.history.length === 0) {
@@ -110,6 +129,22 @@ export const useChatStore = defineStore('chat-store', {
} }
}, },
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) { deleteChatByUuid(uuid: number, index: number) {
if (!uuid || uuid === 0) { if (!uuid || uuid === 0) {
if (this.chat.length) { if (this.chat.length) {

View File

@@ -1,2 +1,3 @@
export * from './app' export * from './app'
export * from './chat' export * from './chat'
export * from './user'

View File

@@ -0,0 +1,32 @@
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: 'ChenZhaoYu',
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)
}

View File

@@ -0,0 +1,22 @@
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)
},
},
})

5
src/styles/global.less Normal file
View File

@@ -0,0 +1,5 @@
html,
body,
#app {
height: 100%;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,203 @@
html.dark {
pre code.hljs {
display: block;
overflow-x: auto;
padding: 1em
}
code.hljs {
padding: 3px 5px
}
.hljs {
color: #abb2bf;
background: #282c34
}
.hljs-keyword,
.hljs-operator,
.hljs-pattern-match {
color: #f92672
}
.hljs-function,
.hljs-pattern-match .hljs-constructor {
color: #61aeee
}
.hljs-function .hljs-params {
color: #a6e22e
}
.hljs-function .hljs-params .hljs-typing {
color: #fd971f
}
.hljs-module-access .hljs-module {
color: #7e57c2
}
.hljs-constructor {
color: #e2b93d
}
.hljs-constructor .hljs-string {
color: #9ccc65
}
.hljs-comment,
.hljs-quote {
color: #b18eb1;
font-style: italic
}
.hljs-doctag,
.hljs-formula {
color: #c678dd
}
.hljs-deletion,
.hljs-name,
.hljs-section,
.hljs-selector-tag,
.hljs-subst {
color: #e06c75
}
.hljs-literal {
color: #56b6c2
}
.hljs-addition,
.hljs-attribute,
.hljs-meta .hljs-string,
.hljs-regexp,
.hljs-string {
color: #98c379
}
.hljs-built_in,
.hljs-class .hljs-title,
.hljs-title.class_ {
color: #e6c07b
}
.hljs-attr,
.hljs-number,
.hljs-selector-attr,
.hljs-selector-class,
.hljs-selector-pseudo,
.hljs-template-variable,
.hljs-type,
.hljs-variable {
color: #d19a66
}
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-symbol,
.hljs-title {
color: #61aeee
}
.hljs-emphasis {
font-style: italic
}
.hljs-strong {
font-weight: 700
}
.hljs-link {
text-decoration: underline
}
}
html {
pre code.hljs {
display: block;
overflow-x: auto;
padding: 1em
}
code.hljs {
padding: 3px 5px
}
.hljs {
color: #383a42;
background: #fafafa
}
.hljs-comment,
.hljs-quote {
color: #a0a1a7;
font-style: italic
}
.hljs-doctag,
.hljs-formula,
.hljs-keyword {
color: #a626a4
}
.hljs-deletion,
.hljs-name,
.hljs-section,
.hljs-selector-tag,
.hljs-subst {
color: #e45649
}
.hljs-literal {
color: #0184bb
}
.hljs-addition,
.hljs-attribute,
.hljs-meta .hljs-string,
.hljs-regexp,
.hljs-string {
color: #50a14f
}
.hljs-attr,
.hljs-number,
.hljs-selector-attr,
.hljs-selector-class,
.hljs-selector-pseudo,
.hljs-template-variable,
.hljs-type,
.hljs-variable {
color: #986801
}
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-symbol,
.hljs-title {
color: #4078f2
}
.hljs-built_in,
.hljs-class .hljs-title,
.hljs-title.class_ {
color: #c18401
}
.hljs-emphasis {
font-style: italic
}
.hljs-strong {
font-weight: 700
}
.hljs-link {
text-decoration: underline
}
}

View File

@@ -1,9 +1,3 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
html,
body,
#app {
height: 100%;
}

44
src/utils/format/index.ts Normal file
View File

@@ -0,0 +1,44 @@
/**
* 转义 HTML 字符
* @param source
*/
export function encodeHTML(source: string) {
return source
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
}
/**
* 判断是否为代码块
* @param text
*/
export function includeCode(text: string | null | undefined) {
const regexp = /^(?:\s{4}|\t).+/gm
return !!(text?.includes(' = ') || text?.match(regexp))
}
/**
* 复制文本
* @param options
*/
export function copyText(options: { text: string; origin?: boolean }) {
const props = { origin: true, ...options }
let input: HTMLInputElement | HTMLTextAreaElement
if (props.origin)
input = document.createElement('textarea')
else
input = document.createElement('input')
input.setAttribute('readonly', 'readonly')
input.value = props.text
document.body.appendChild(input)
input.select()
if (document.execCommand('copy'))
document.execCommand('copy')
document.body.removeChild(input)
}

View File

@@ -1,6 +0,0 @@
function includeCode(text: string | null | undefined) {
const regexp = /^(?:\s{4}|\t).+/gm
return !!(text?.includes(' = ') || text?.match(regexp))
}
export default includeCode

View File

@@ -2,7 +2,6 @@ import axios, { type AxiosResponse } from 'axios'
const service = axios.create({ const service = axios.create({
baseURL: import.meta.env.VITE_GLOB_API_URL, baseURL: import.meta.env.VITE_GLOB_API_URL,
timeout: !isNaN(+import.meta.env.VITE_GLOB_API_TIMEOUT) ? Number(import.meta.env.VITE_GLOB_API_TIMEOUT) : 60 * 1000,
}) })
service.interceptors.request.use( service.interceptors.request.use(

View File

@@ -8,9 +8,9 @@ defineProps<Props>()
<template> <template>
<img v-if="image" src="@/assets/avatar.jpg" class="object-cover w-full h-full " alt="avatar"> <img v-if="image" src="@/assets/avatar.jpg" class="object-cover w-full h-full " alt="avatar">
<span v-else class="text-[27px]"> <span v-else class="text-[27px] dark:text-white">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-hidden="true" width="1em" height="1em"> <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" /> <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> </svg>
</span> </span>
</template> </template>

View File

@@ -1,16 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed, ref } from 'vue'
import { marked } from 'marked' import { marked } from 'marked'
import hljs from 'highlight.js' import hljs from 'highlight.js'
import { useBasicLayout } from '@/hooks/useBasicLayout'
const props = defineProps<Props>() import { encodeHTML } from '@/utils/format'
marked.setOptions({
renderer: new marked.Renderer(),
highlight(code) {
return hljs.highlightAuto(code).value
},
})
interface Props { interface Props {
inversion?: boolean inversion?: boolean
@@ -19,32 +12,63 @@ interface Props {
loading?: boolean loading?: boolean
} }
const props = defineProps<Props>()
const { isMobile } = useBasicLayout()
const renderer = new marked.Renderer()
const textRef = ref<HTMLElement>()
renderer.html = (html) => {
return `<p>${encodeHTML(html)}</p>`
}
renderer.code = (code, language) => {
const validLang = !!(language && hljs.getLanguage(language))
if (validLang)
return `<pre><code class="hljs ${language}">${hljs.highlight(language, code).value}</code></pre>`
return `<pre style="background: none">${hljs.highlightAuto(code).value}</pre>`
}
marked.setOptions({
renderer,
highlight(code) {
return hljs.highlightAuto(code).value
},
})
const wrapClass = computed(() => { const wrapClass = computed(() => {
return [ return [
'text-wrap', 'text-wrap',
'p-3',
'min-w-[20px]', 'min-w-[20px]',
'rounded-md', 'rounded-md',
isMobile.value ? 'p-2' : 'p-3',
props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]', props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
props.inversion ? 'dark:bg-[#a1dc95]' : 'dark:bg-[#1e1e20]',
{ 'text-red-500': props.error }, { 'text-red-500': props.error },
] ]
}) })
const text = computed(() => { const text = computed(() => {
if (props.text) const value = props.text ?? ''
return marked(props.text) if (!props.inversion)
return '' return marked(value)
return value
}) })
defineExpose({ textRef })
</script> </script>
<template> <template>
<div :class="wrapClass"> <div class="text-black" :class="wrapClass">
<template v-if="loading"> <template v-if="loading">
<span class="w-[5px] h-[20px] block animate-blink" /> <span class="dark:text-white w-[4px] h-[20px] block animate-blink" />
</template> </template>
<template v-else> <template v-else>
<div class="leading-relaxed break-all"> <div ref="textRef" class="leading-relaxed break-all">
<div :class="[{ 'markdown-body': !inversion }]" v-html="text" /> <div v-if="!inversion" class="markdown-body" v-html="text" />
<div v-else class="whitespace-pre-wrap" v-text="text" />
</div> </div>
</template> </template>
</div> </div>

View File

@@ -1,7 +1,11 @@
<script setup lang='ts'> <script setup lang='ts'>
import { ref } from 'vue'
import { NDropdown, useMessage } from 'naive-ui'
import AvatarComponent from './Avatar.vue' import AvatarComponent from './Avatar.vue'
import TextComponent from './Text.vue' import TextComponent from './Text.vue'
import { SvgIcon } from '@/components/common' import { SvgIcon } from '@/components/common'
import { copyText } from '@/utils/format'
import { useIconRender } from '@/hooks/useIconRender'
interface Props { interface Props {
dateTime?: string dateTime?: string
@@ -13,16 +17,52 @@ interface Props {
interface Emit { interface Emit {
(ev: 'regenerate'): void (ev: 'regenerate'): void
(ev: 'copy'): void
(ev: 'delete'): void (ev: 'delete'): void
} }
defineProps<Props>() const props = defineProps<Props>()
const emit = defineEmits<Emit>() const emit = defineEmits<Emit>()
function handleDelete() { const ms = useMessage()
emit('delete')
const { iconRender } = useIconRender()
const textRef = ref<HTMLElement>()
const options = [
{
label: 'Copy Raw',
key: 'copyRaw',
icon: iconRender({ icon: 'ri:file-copy-2-line' }),
},
{
label: 'Copy Text',
key: 'copyText',
icon: iconRender({ icon: 'ri:file-copy-line' }),
},
{
label: 'Delete',
key: 'delete',
icon: iconRender({ icon: 'ri:delete-bin-line' }),
},
]
function handleSelect(key: 'copyRaw' | 'copyText' | 'delete') {
switch (key) {
case 'copyRaw':
if (textRef.value && (textRef.value as any).textRef) {
copyText({ text: (textRef.value as any).textRef.innerText })
ms.success('Copied Raw')
}
return
case 'copyText':
copyText({ text: props.text ?? '', origin: false })
ms.success('Copied Text')
return
case 'delete':
emit('delete')
}
} }
function handleRegenerate() { function handleRegenerate() {
@@ -31,19 +71,23 @@ function handleRegenerate() {
</script> </script>
<template> <template>
<div class="flex w-full mb-6" :class="[{ 'flex-row-reverse': inversion }]"> <div class="flex w-full mb-6 overflow-hidden" :class="[{ 'flex-row-reverse': inversion }]">
<div <div
class="flex items-center justify-center rounded-full overflow-hidden w-[32px] h-[32px]" class="flex items-center justify-center flex-shrink-0 h-8 overflow-hidden rounded-full basis-8"
:class="[inversion ? 'ml-3' : 'mr-3']" :class="[inversion ? 'ml-2' : 'mr-2']"
> >
<AvatarComponent :image="inversion" /> <AvatarComponent :image="inversion" />
</div> </div>
<div class="flex flex-col flex-1 text-sm" :class="[inversion ? 'items-end' : 'items-start']"> <div class="overflow-hidden text-sm " :class="[inversion ? 'items-end' : 'items-start']">
<span class="text-xs text-[#b4bbc4]"> <p class="text-xs text-[#b4bbc4]" :class="[inversion ? 'text-right' : 'text-left']">
{{ dateTime }} {{ dateTime }}
</span> </p>
<div class="flex items-end gap-2 mt-2" :class="[inversion ? 'flex-row-reverse' : 'flex-row']"> <div
class="flex items-end gap-1 mt-2"
:class="[inversion ? 'flex-row-reverse' : 'flex-row']"
>
<TextComponent <TextComponent
ref="textRef"
:inversion="inversion" :inversion="inversion"
:error="error" :error="error"
:text="text" :text="text"
@@ -52,17 +96,16 @@ function handleRegenerate() {
<div class="flex flex-col"> <div class="flex flex-col">
<button <button
v-if="!inversion" v-if="!inversion"
class="mb-2 transition text-neutral-400 hover:text-neutral-800" class="mb-2 transition text-neutral-300 hover:text-neutral-800 dark:hover:text-neutral-300"
@click="handleRegenerate" @click="handleRegenerate"
> >
<SvgIcon icon="ri:restart-line" /> <SvgIcon icon="ri:restart-line" />
</button> </button>
<button <NDropdown :placement="!inversion ? 'right' : 'left'" :options="options" @select="handleSelect">
class="mb-1 transition text-neutral-400 hover:text-neutral-800" <button class="transition text-neutral-300 hover:text-neutral-800 dark:hover:text-neutral-200">
@click="handleDelete" <SvgIcon icon="ri:more-2-fill" />
> </button>
<SvgIcon icon="ri:delete-bin-6-line" /> </NDropdown>
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -2,6 +2,10 @@
background-color: transparent; background-color: transparent;
font-size: 14px; font-size: 14px;
p {
white-space: pre-wrap;
}
ol { ol {
list-style-type: decimal; list-style-type: decimal;
} }
@@ -19,4 +23,16 @@
pre { pre {
background-color: #fff; background-color: #fff;
} }
code.hljs{
padding: 0;
}
}
html.dark {
.highlight pre,
pre {
background-color: #282c34;
}
} }

View File

@@ -3,6 +3,10 @@ import { useChatStore } from '@/store'
export function useChat() { export function useChat() {
const chatStore = useChatStore() const chatStore = useChatStore()
const getChatByUuidAndIndex = (uuid: number, index: number) => {
return chatStore.getChatByUuidAndIndex(uuid, index)
}
const addChat = (uuid: number, chat: Chat.Chat) => { const addChat = (uuid: number, chat: Chat.Chat) => {
chatStore.addChatByUuid(uuid, chat) chatStore.addChatByUuid(uuid, chat)
} }
@@ -11,8 +15,14 @@ export function useChat() {
chatStore.updateChatByUuid(uuid, index, chat) chatStore.updateChatByUuid(uuid, index, chat)
} }
const updateChatSome = (uuid: number, index: number, chat: Partial<Chat.Chat>) => {
chatStore.updateChatSomeByUuid(uuid, index, chat)
}
return { return {
addChat, addChat,
updateChat, updateChat,
updateChatSome,
getChatByUuidAndIndex,
} }
} }

View File

@@ -8,7 +8,7 @@ import { useChat } from './hooks/useChat'
import { HoverButton, SvgIcon } from '@/components/common' import { HoverButton, SvgIcon } from '@/components/common'
import { useBasicLayout } from '@/hooks/useBasicLayout' import { useBasicLayout } from '@/hooks/useBasicLayout'
import { useChatStore } from '@/store' import { useChatStore } from '@/store'
import { fetchChatAPI } from '@/api' import { fetchChatAPIProcess } from '@/api'
let controller = new AbortController() let controller = new AbortController()
@@ -19,7 +19,7 @@ const ms = useMessage()
const chatStore = useChatStore() const chatStore = useChatStore()
const { isMobile } = useBasicLayout() const { isMobile } = useBasicLayout()
const { addChat, updateChat } = useChat() const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex } = useChat()
const { scrollRef, scrollToBottom } = useScroll() const { scrollRef, scrollToBottom } = useScroll()
const { uuid } = route.params as { uuid: string } const { uuid } = route.params as { uuid: string }
@@ -71,7 +71,7 @@ async function onConversation() {
+uuid, +uuid,
{ {
dateTime: new Date().toLocaleString(), dateTime: new Date().toLocaleString(),
text: 'Aha, Thinking...', text: '',
loading: true, loading: true,
inversion: false, inversion: false,
error: false, error: false,
@@ -82,27 +82,70 @@ async function onConversation() {
scrollToBottom() scrollToBottom()
try { try {
const { data } = await fetchChatAPI<Chat.ConversationResponse>(message, options, controller.signal) await fetchChatAPIProcess<Chat.ConversationResponse>({
updateChat( prompt: message,
+uuid, options,
dataSources.value.length - 1, signal: controller.signal,
{ onDownloadProgress: ({ event }) => {
dateTime: new Date().toLocaleString(), const xhr = event.target
text: data.text ?? '', const { responseText } = xhr
inversion: false, // Always process the final line
error: false, const lastIndex = responseText.lastIndexOf('\n')
loading: false, let chunk = responseText
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id }, if (lastIndex !== -1)
requestOptions: { prompt: message, options: { ...options } }, chunk = responseText.substring(lastIndex)
try {
const data = JSON.parse(chunk)
updateChat(
+uuid,
dataSources.value.length - 1,
{
dateTime: new Date().toLocaleString(),
text: data.text ?? '',
inversion: false,
error: false,
loading: false,
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
requestOptions: { prompt: message, options: { ...options } },
},
)
scrollToBottom()
}
catch (error) {
//
}
}, },
) })
scrollToBottom()
} }
catch (error: any) { catch (error: any) {
let errorMessage = error?.message ?? 'Something went wrong, please try again later.' const errorMessage = error?.message ?? 'Something went wrong, please try again later.'
if (error.message === 'canceled') if (error.message === 'canceled') {
errorMessage = 'Request canceled. Please try again.' updateChatSome(
+uuid,
dataSources.value.length - 1,
{
loading: false,
},
)
scrollToBottom()
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( updateChat(
+uuid, +uuid,
@@ -146,7 +189,7 @@ async function onRegenerate(index: number) {
index, index,
{ {
dateTime: new Date().toLocaleString(), dateTime: new Date().toLocaleString(),
text: 'Aha, Let me think again...', text: '',
inversion: false, inversion: false,
error: false, error: false,
loading: true, loading: true,
@@ -156,26 +199,53 @@ async function onRegenerate(index: number) {
) )
try { try {
const { data } = await fetchChatAPI<Chat.ConversationResponse>(message, options, controller.signal) await fetchChatAPIProcess<Chat.ConversationResponse>({
updateChat( prompt: message,
+uuid, options,
index, signal: controller.signal,
{ onDownloadProgress: ({ event }) => {
dateTime: new Date().toLocaleString(), const xhr = event.target
text: data.text ?? '', const { responseText } = xhr
inversion: false, // Always process the final line
error: false, const lastIndex = responseText.lastIndexOf('\n')
loading: false, let chunk = responseText
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id }, if (lastIndex !== -1)
requestOptions: { prompt: message, ...options }, chunk = responseText.substring(lastIndex)
try {
const data = JSON.parse(chunk)
updateChat(
+uuid,
index,
{
dateTime: new Date().toLocaleString(),
text: data.text ?? '',
inversion: false,
error: false,
loading: false,
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
requestOptions: { prompt: message, ...options },
},
)
}
catch (error) {
//
}
}, },
) })
} }
catch (error: any) { catch (error: any) {
let errorMessage = 'Something went wrong, please try again later.' if (error.message === 'canceled') {
updateChatSome(
+uuid,
index,
{
loading: false,
},
)
return
}
if (error.message === 'canceled') const errorMessage = error?.message ?? 'Something went wrong, please try again later.'
errorMessage = 'Request canceled. Please try again.'
updateChat( updateChat(
+uuid, +uuid,
@@ -228,9 +298,11 @@ function handleClear() {
} }
function handleEnter(event: KeyboardEvent) { function handleEnter(event: KeyboardEvent) {
if (event.key === 'Enter' && !event.shiftKey) { if (!isMobile.value) {
event.preventDefault() if (event.key === 'Enter' && !event.shiftKey) {
handleSubmit() event.preventDefault()
handleSubmit()
}
} }
} }
@@ -241,13 +313,19 @@ function handleStop() {
} }
} }
const placeholder = computed(() => {
if (isMobile.value)
return 'Ask me anything...'
return 'Ask me anything... (Shift + Enter = line break)'
})
const buttonDisabled = computed(() => { const buttonDisabled = computed(() => {
return loading.value || !prompt.value || prompt.value.trim() === '' return loading.value || !prompt.value || prompt.value.trim() === ''
}) })
const wrapClass = computed(() => { const wrapClass = computed(() => {
if (isMobile.value) if (isMobile.value)
return ['pt-14', 'pb-14'] return ['pt-14', 'pb-16']
return [] return []
}) })
@@ -272,7 +350,12 @@ onUnmounted(() => {
<template> <template>
<div class="flex flex-col h-full" :class="wrapClass"> <div class="flex flex-col h-full" :class="wrapClass">
<main class="flex-1 overflow-hidden"> <main class="flex-1 overflow-hidden">
<div ref="scrollRef" class="h-full p-4 overflow-hidden overflow-y-auto" :class="[{ 'p-2': isMobile }]"> <div
id="scrollRef"
ref="scrollRef"
class="h-full overflow-hidden overflow-y-auto"
:class="[isMobile ? 'p-2' : 'p-4']"
>
<template v-if="!dataSources.length"> <template v-if="!dataSources.length">
<div class="flex items-center justify-center mt-4 text-center text-neutral-300"> <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" /> <SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
@@ -292,8 +375,8 @@ onUnmounted(() => {
@regenerate="onRegenerate(index)" @regenerate="onRegenerate(index)"
@delete="handleDelete(index)" @delete="handleDelete(index)"
/> />
<div class="flex justify-center"> <div class="sticky bottom-0 left-0 flex justify-center">
<NButton v-if="loading" ghost @click="handleStop"> <NButton v-if="loading" type="warning" @click="handleStop">
<template #icon> <template #icon>
<SvgIcon icon="ri:stop-circle-line" /> <SvgIcon icon="ri:stop-circle-line" />
</template> </template>
@@ -307,7 +390,7 @@ onUnmounted(() => {
<footer :class="footerClass"> <footer :class="footerClass">
<div class="flex items-center justify-between space-x-2"> <div class="flex items-center justify-between space-x-2">
<HoverButton @click="handleClear"> <HoverButton @click="handleClear">
<span class="text-xl text-[#4f555e]"> <span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="ri:delete-bin-line" /> <SvgIcon icon="ri:delete-bin-line" />
</span> </span>
</HoverButton> </HoverButton>
@@ -315,12 +398,14 @@ onUnmounted(() => {
v-model:value="prompt" v-model:value="prompt"
type="textarea" type="textarea"
:autosize="{ minRows: 1, maxRows: 2 }" :autosize="{ minRows: 1, maxRows: 2 }"
placeholder="Ask me anything..." :placeholder="placeholder"
@keypress="handleEnter" @keypress="handleEnter"
/> />
<NButton type="primary" :disabled="buttonDisabled" @click="handleSubmit"> <NButton type="primary" :disabled="buttonDisabled" @click="handleSubmit">
<template #icon> <template #icon>
<SvgIcon icon="ri:send-plane-fill" /> <span class="dark:text-black">
<SvgIcon icon="ri:send-plane-fill" />
</span>
</template> </template>
</NButton> </NButton>
</div> </div>

View File

@@ -20,7 +20,7 @@ const collapsed = computed(() => appStore.siderCollapsed)
const getMobileClass = computed(() => { const getMobileClass = computed(() => {
if (isMobile.value) if (isMobile.value)
return ['rounded-none', 'shadow-none'] return ['rounded-none', 'shadow-none']
return ['border', 'rounded-md', 'shadow-md'] return ['border', 'rounded-md', 'shadow-md', 'dark:border-neutral-800']
}) })
const getContainerClass = computed(() => { const getContainerClass = computed(() => {
@@ -32,7 +32,7 @@ const getContainerClass = computed(() => {
</script> </script>
<template> <template>
<div class="h-full" :class="[isMobile ? 'p-0' : 'p-4']"> <div class="h-full dark:bg-[#24272e] transition-all" :class="[isMobile ? 'p-0' : 'p-4']">
<div class="h-full overflow-hidden" :class="getMobileClass"> <div class="h-full overflow-hidden" :class="getMobileClass">
<NLayout class="z-40 transition" :class="getContainerClass" has-sider> <NLayout class="z-40 transition" :class="getContainerClass" has-sider>
<Sider /> <Sider />

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed, nextTick } from 'vue'
import { SvgIcon } from '@/components/common' import { SvgIcon } from '@/components/common'
import { useAppStore, useChatStore } from '@/store' import { useAppStore, useChatStore } from '@/store'
@@ -7,25 +7,48 @@ const appStore = useAppStore()
const chatStore = useChatStore() const chatStore = useChatStore()
const collapsed = computed(() => appStore.siderCollapsed) const collapsed = computed(() => appStore.siderCollapsed)
const currentChatHistory = computed(() => chatStore.getChatHistoryByCurrentActive)
function handleAdd() {
chatStore.addHistory({ title: 'New Chat', uuid: Date.now(), isEdit: false })
}
function handleUpdateCollapsed() { function handleUpdateCollapsed() {
appStore.setSiderCollapsed(!collapsed.value) appStore.setSiderCollapsed(!collapsed.value)
} }
function onScrollToTop() {
const scrollRef = document.querySelector('#scrollRef')
if (scrollRef)
nextTick(() => scrollRef.scrollTop = 0)
}
function onScrollToBottom() {
const scrollRef = document.querySelector('#scrollRef')
if (scrollRef)
nextTick(() => scrollRef.scrollTop = scrollRef.scrollHeight)
}
</script> </script>
<template> <template>
<header class="fixed top-0 left-0 right-0 z-30 border-b bg-white/80 backdrop-blur"> <header
class="fixed 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 h-14"> <div class="relative flex items-center justify-between h-14">
<button class="flex items-center justify-center w-11 h-11" @click="handleUpdateCollapsed"> <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-if="collapsed" class="text-2xl" icon="ri:align-justify" />
<SvgIcon v-else class="text-2xl" icon="ri:align-right" /> <SvgIcon v-else class="text-2xl" icon="ri:align-right" />
</button> </button>
<button class="flex items-center justify-center w-11 h-11" @click="handleAdd"> <h1
<SvgIcon class="text-2xl" icon="ri:add-fill" /> class="flex-1 px-4 overflow-hidden text-center cursor-pointer select-none text-ellipsis whitespace-nowrap"
@dblclick="onScrollToTop"
>
{{ currentChatHistory?.title ?? '' }}
</h1>
<button
class="flex items-center justify-center w-11 h-11"
@click="onScrollToBottom"
>
<SvgIcon class="text-2xl" icon="ri:arrow-down-s-line" />
</button> </button>
</div> </div>
</header> </header>

View File

@@ -1,19 +1,22 @@
<script setup lang='ts'> <script setup lang='ts'>
import { ref } from 'vue' import { defineAsyncComponent, ref } from 'vue'
import { HoverButton, Setting, SvgIcon, UserAvatar } from '@/components/common' import { HoverButton, SvgIcon, UserAvatar } from '@/components/common'
const Setting = defineAsyncComponent(() => import('@/components/common/Setting/index.vue'))
const show = ref(false) const show = ref(false)
</script> </script>
<template> <template>
<footer class="flex items-center justify-between min-w-0 p-4 overflow-hidden border-t"> <footer class="flex items-center justify-between min-w-0 p-4 overflow-hidden border-t dark:border-neutral-800">
<UserAvatar /> <UserAvatar />
<HoverButton tooltip="Setting" @click="show = true"> <HoverButton tooltip="Setting" @click="show = true">
<span class="text-xl text-[#4f555e]"> <span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="ri:settings-4-line" /> <SvgIcon icon="ri:settings-4-line" />
</span> </span>
</HoverButton> </HoverButton>
<Setting v-model:visible="show" /> <Setting v-if="show" v-model:visible="show" />
</footer> </footer>
</template> </template>

View File

@@ -55,8 +55,8 @@ function isActive(uuid: number) {
<template v-else> <template v-else>
<div v-for="(item, index) of dataSources" :key="index"> <div v-for="(item, index) of dataSources" :key="index">
<a <a
class="relative flex items-center gap-3 px-3 py-3 break-all border rounded-md cursor-pointer hover:bg-neutral-100 group" 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]', 'pr-14']" :class="isActive(item.uuid) && ['border-[#4b9e5f]', 'bg-neutral-100', 'text-[#4b9e5f]', 'dark:bg-[#24272e]', 'dark:border-[#4b9e5f]', 'pr-14']"
@click="handleSelect(item)" @click="handleSelect(item)"
> >
<span> <span>

View File

@@ -0,0 +1,34 @@
<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">
No permission
</h1>
<p class="text-base text-slate-500 dark:text-neutral-400">
The page you're trying access has restricted access.
Please refer to your system administrator
</p>
<div class="flex items-center justify-center text-center">
<div class="w-[300px]">
<div class="w-[300px]">
<img src="../../../icons/403.svg" alt="404">
</div>
</div>
</div>
<NButton type="primary" @click="goHome">
Go to Home
</NButton>
</div>
</div>
</template>

View File

@@ -0,0 +1,31 @@
<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 couldnt find the page youre looking for. Perhaps youve 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>

View File

@@ -1,5 +1,6 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
darkMode: 'class',
content: [ content: [
'./index.html', './index.html',
'./src/**/*.{vue,js,ts,jsx,tsx}', './src/**/*.{vue,js,ts,jsx,tsx}',
@@ -11,7 +12,7 @@ module.exports = {
}, },
keyframes: { keyframes: {
blink: { blink: {
'0%, 100%': { 'background-color': '#000' }, '0%, 100%': { 'background-color': 'currentColor' },
'50%': { 'background-color': 'transparent' }, '50%': { 'background-color': 'transparent' },
}, },
}, },

View File

@@ -16,7 +16,6 @@
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
}, },
// @vueuse/core 不能通过 vue-tsc 检查,所以这里需要忽略,以后将移除
"types": ["vite/client", "node", "naive-ui/volar", "web-bluetooth"] "types": ["vite/client", "node", "naive-ui/volar", "web-bluetooth"]
}, },
"exclude": ["node_modules", "dist", "service"] "exclude": ["node_modules", "dist", "service"]