Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
21fb4f817c | ||
![]() |
2c509c329f | ||
![]() |
4769c31d09 | ||
![]() |
89712aada7 | ||
![]() |
3e9db49aad | ||
![]() |
2dcd292dd4 | ||
![]() |
90f6989445 | ||
![]() |
1406292405 | ||
![]() |
628187f5c3 | ||
![]() |
cc91e95eed | ||
![]() |
abbdcf9c51 | ||
![]() |
b6fd9ae766 | ||
![]() |
1e2f893ef6 | ||
![]() |
10058f151c | ||
![]() |
09359c3c46 | ||
![]() |
ba83856173 | ||
![]() |
b84f7e4c72 | ||
![]() |
c0f8e4316e | ||
![]() |
bf5c0cdf04 | ||
![]() |
66cecb6049 | ||
![]() |
808ae600c2 | ||
![]() |
f40048fb08 | ||
![]() |
ac9536ab87 | ||
![]() |
938c91f635 | ||
![]() |
4ab9f709de | ||
![]() |
cb90d81c69 | ||
![]() |
fda6c6bb6a | ||
![]() |
6216d84ecd | ||
![]() |
ea887ad755 | ||
![]() |
c98a2a3cb8 | ||
![]() |
06f41c3758 |
3
.env
@@ -2,3 +2,6 @@
|
||||
VITE_GLOB_API_URL=/api
|
||||
|
||||
VITE_APP_API_BASE_URL=http://localhost:3002/
|
||||
|
||||
# Glob API Timeout (ms)
|
||||
VITE_GLOB_API_TIMEOUT=100000
|
||||
|
16
.github/workflows/build_docker.yml
vendored
@@ -3,6 +3,8 @@ name: build_docker
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
release:
|
||||
types: [created] # 表示在创建新的 Release 时触发
|
||||
|
||||
jobs:
|
||||
build_docker:
|
||||
@@ -11,11 +13,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: chenzhaoyu94/chatgpt-web
|
||||
|
||||
- run: |
|
||||
echo "本次构建的版本为:${GITHUB_REF_NAME} (但是这个变量目前上下文中无法获取到)"
|
||||
echo 本次构建的版本为:${{ github.ref_name }}
|
||||
env
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
@@ -32,6 +34,8 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/chatgpt-web:${{ github.ref_name }}
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/chatgpt-web:latest
|
||||
|
3
.vscode/settings.json
vendored
@@ -25,13 +25,16 @@
|
||||
"chatgpt",
|
||||
"chenzhaoyu",
|
||||
"commitlint",
|
||||
"davinci",
|
||||
"dockerhub",
|
||||
"esno",
|
||||
"GPTAPI",
|
||||
"iconify",
|
||||
"logprobs",
|
||||
"nodata",
|
||||
"OPENAI",
|
||||
"pinia",
|
||||
"Popconfirm",
|
||||
"rushstack",
|
||||
"Sider",
|
||||
"tailwindcss",
|
||||
|
140
CHANGELOG.md
@@ -1,3 +1,142 @@
|
||||
## v2.8.1
|
||||
|
||||
`2023-02-27`
|
||||
|
||||
### BugFix
|
||||
- 修复 `API` 版本不是 `Markdown` 时,普通 `HTML` 代码会被渲染的问题 [#146]
|
||||
|
||||
## v2.8.0
|
||||
|
||||
`2023-02-27`
|
||||
|
||||
- 感谢 [puppywang](https://github.com/Chanzhaoyu/chatgpt-web/commit/628187f5c3348bda0d0518f90699a86525d19018) 修复了 `2.7.0` 版本中关于流输出数据的问题(使用 `nginx` 需要自行配置 `octet-stream` 相关内容)
|
||||
|
||||
- 关于为什么使用 `octet-stream` 而不是 `sse`,是因为更好的兼容之前的模式。
|
||||
|
||||
- 建议更新到此版本获得比较完整的体验
|
||||
|
||||
### Enhancement
|
||||
- 优化了部份代码和类型提示
|
||||
- 输入框添加换行提示
|
||||
- 移动端输入框现在回车为换行,而不是直接提交
|
||||
- 移动端双击标题返回顶部,箭头返回底部
|
||||
|
||||
### BugFix
|
||||
- 流输出数据下的问题[#122]
|
||||
- 修复了 `API Key` 下部份代码不换行的问题
|
||||
- 修复移动端深色模式部份样式问题[#123][#126]
|
||||
- 修复主题模式图标不一致的问题[#126]
|
||||
|
||||
## v2.7.3
|
||||
|
||||
`2023-02-25`
|
||||
|
||||
### Feature
|
||||
- 适配系统深色模式 [#118](https://github.com/Chanzhaoyu/chatgpt-web/issues/103)
|
||||
### BugFix
|
||||
- 修复用户消息能被渲染为 `HTML` 问题 [#117](https://github.com/Chanzhaoyu/chatgpt-web/issues/117)
|
||||
|
||||
## v2.7.2
|
||||
|
||||
`2023-02-24`
|
||||
### Enhancement
|
||||
- 消息使用 [github-markdown-css](https://www.npmjs.com/package/github-markdown-css) 进行美化,现在支持全语法
|
||||
- 移除测试无用函数
|
||||
|
||||
## v2.7.1
|
||||
|
||||
`2023-02-23`
|
||||
|
||||
因为消息流在 `accessToken` 中存在解析失败和消息不完整等一系列的问题,调整回正常消息形式
|
||||
|
||||
### Feature
|
||||
- 现在可以中断请求过长没有答复的消息
|
||||
- 现在可以删除单条消息
|
||||
- 设置中显示当前版本信息
|
||||
|
||||
### BugFix
|
||||
- 回退 `2.7.0` 的消息不稳定的问题
|
||||
|
||||
## v2.7.0
|
||||
|
||||
`2023-02-23`
|
||||
|
||||
### Feature
|
||||
- 使用消息流返回信息,反应更迅速
|
||||
|
||||
### Enhancement
|
||||
- 样式的一点小改动
|
||||
|
||||
## v2.6.2
|
||||
|
||||
`2023-02-22`
|
||||
### BugFix
|
||||
- 还原修改代理导致的异常问题
|
||||
|
||||
## v2.6.1
|
||||
|
||||
`2023-02-22`
|
||||
|
||||
### Feature
|
||||
- 新增 `Railway` 部署模版
|
||||
|
||||
### BugFix
|
||||
- 手动打包 `Proxy` 问题
|
||||
|
||||
## v2.6.0
|
||||
|
||||
`2023-02-21`
|
||||
### Feature
|
||||
- 新增对 `网页 accessToken` 调用 `ChatGPT`,更智能不过不太稳定 [#51](https://github.com/Chanzhaoyu/chatgpt-web/issues/51)
|
||||
- 前端页面设置按钮显示查看当前后端服务配置
|
||||
|
||||
### Enhancement
|
||||
- 新增 `TIMEOUT_MS` 环境变量设定后端超时时常(单位:毫秒)[#62](https://github.com/Chanzhaoyu/chatgpt-web/issues/62)
|
||||
|
||||
## v2.5.2
|
||||
|
||||
`2023-02-21`
|
||||
### Feature
|
||||
- 增加对 `markdown` 格式的支持 [Demo](https://github.com/Chanzhaoyu/chatgpt-web/pull/77)
|
||||
### BugFix
|
||||
- 重载会话时滚动条保持
|
||||
|
||||
## v2.5.1
|
||||
|
||||
`2023-02-21`
|
||||
|
||||
### Enhancement
|
||||
- 调整路由模式为 `hash`
|
||||
- 调整新增会话添加到
|
||||
- 调整移动端样式
|
||||
|
||||
|
||||
## v2.5.0
|
||||
|
||||
`2023-02-20`
|
||||
|
||||
### Feature
|
||||
- 会话 `loading` 现在显示为光标动画
|
||||
- 会话现在可以再次生成回复
|
||||
- 会话异常可以再次进行请求
|
||||
- 所有删除选项添加确认操作
|
||||
|
||||
### Enhancement
|
||||
- 调整 `chat` 为路由页面而不是组件形式
|
||||
- 更新依赖至最新
|
||||
- 调整移动端体验
|
||||
|
||||
### BugFix
|
||||
- 修复移动端左侧菜单显示不完整的问题
|
||||
|
||||
## v2.4.1
|
||||
|
||||
`2023-02-18`
|
||||
|
||||
### Enhancement
|
||||
- 调整部份移动端上的样式
|
||||
- 输入框支持换行
|
||||
|
||||
## v2.4.0
|
||||
|
||||
`2023-02-17`
|
||||
@@ -6,6 +145,7 @@
|
||||
- 响应式支持移动端
|
||||
### Enhancement
|
||||
- 修改部份描述错误
|
||||
|
||||
## v2.3.3
|
||||
|
||||
`2023-02-16`
|
||||
|
204
README.md
@@ -1,20 +1,74 @@
|
||||
# ChatGPT Web
|
||||
|
||||
使用 express 和 vue3 搭建的 ChartGPT 演示网页
|
||||
> 使用 `express` 和 `vue3` 搭建的支持 `ChatGPT` 双模型演示网页
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||

|
||||
- [ChatGPT Web](#chatgpt-web)
|
||||
- [介绍](#介绍)
|
||||
- [待实现路线](#待实现路线)
|
||||
- [前置要求](#前置要求)
|
||||
- [Node](#node)
|
||||
- [PNPM](#pnpm)
|
||||
- [填写密钥](#填写密钥)
|
||||
- [安装依赖](#安装依赖)
|
||||
- [后端](#后端)
|
||||
- [前端](#前端)
|
||||
- [测试环境运行](#测试环境运行)
|
||||
- [后端服务](#后端服务)
|
||||
- [前端网页](#前端网页)
|
||||
- [打包](#打包)
|
||||
- [使用 Docker](#使用-docker)
|
||||
- [Docker 参数示例](#docker-参数示例)
|
||||
- [Docker build \& Run](#docker-build--run)
|
||||
- [Docker compose](#docker-compose)
|
||||
- [使用 Railway 部署](#使用-railway-部署)
|
||||
- [Railway 环境变量](#railway-环境变量)
|
||||
- [手动打包](#手动打包)
|
||||
- [后端服务](#后端服务-1)
|
||||
- [前端网页](#前端网页-1)
|
||||
- [常见问题](#常见问题)
|
||||
- [参与贡献](#参与贡献)
|
||||
- [赞助](#赞助)
|
||||
- [License](#license)
|
||||
## 介绍
|
||||
|
||||
> 提示:目前 `OpenAI` 开放的模型最高只有 `GPT-3`,和现在网页所使用的 `GPT-3.5` 或 `GPT-4` 有很大差距,需要等官方开放最新的模型接口。
|
||||
支持双模型,提供了两种非官方 `ChatGPT API` 方法
|
||||
|
||||
| 方式 | 免费? | 可靠性 | 质量 |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
| `ChatGPTAPI(GPT-3)` | 否 | 可靠 | 较笨 |
|
||||
| `ChatGPTUnofficialProxyAPI(网页 accessToken)` | 是 | 相对不可靠 | 聪明 |
|
||||
|
||||
对比:
|
||||
1. `ChatGPTAPI` 使用 `text-davinci-003` 通过官方`OpenAI`补全`API`模拟`ChatGPT`(最稳健的方法,但它不是免费的,并且没有使用针对聊天进行微调的模型)
|
||||
2. `ChatGPTUnofficialProxyAPI` 使用非官方代理服务器访问 `ChatGPT` 的后端`API`,绕过`Cloudflare`(使用真实的的`ChatGPT`,非常轻量级,但依赖于第三方服务器,并且有速率限制)
|
||||
|
||||
[查看详情](https://github.com/Chanzhaoyu/chatgpt-web/issues/138)
|
||||
|
||||
切换方式:
|
||||
1. 进入 `service/.env` 文件
|
||||
2. 使用 `OpenAI API Key` 请填写 `OPENAI_API_KEY` 字段 [(获取 apiKey)](https://platform.openai.com/overview)
|
||||
3. 使用 `Web API` 请填写 `OPENAI_ACCESS_TOKEN` 字段 [(获取 accessToken)](https://chat.openai.com/api/auth/session)
|
||||
4. 同时存在时以 `OpenAI API Key` 优先
|
||||
|
||||
反向代理:
|
||||
|
||||
`ChatGPTUnofficialProxyAPI`时可用 [详情](https://github.com/transitive-bullshit/chatgpt-api#reverse-proxy)
|
||||
|
||||
```shell
|
||||
# service/.env
|
||||
API_REVERSE_PROXY=
|
||||
```
|
||||
|
||||
## 待实现路线
|
||||
[✓] 双模型
|
||||
|
||||
[✓] 多会话储存和上下文逻辑
|
||||
|
||||
[✓] 对代码等消息类型的格式化美化处理
|
||||
|
||||
[✗] 用户模块(注册、登录、个人中心)
|
||||
|
||||
[✗] 界面多语言
|
||||
|
||||
[✗] 界面主题
|
||||
@@ -25,7 +79,7 @@
|
||||
|
||||
### Node
|
||||
|
||||
`node` 需要 `^16 || ^18` 版本(或者 `node >= 14` 需要安装 [fetch polyfill](https://github.com/developit/unfetch#usage-as-a-polyfill)),使用 [nvm](https://github.com/nvm-sh/nvm) 可管理本地多个 `node` 版本
|
||||
`node` 需要 `^16 || ^18` 版本(`node >= 14` 需要安装 [fetch polyfill](https://github.com/developit/unfetch#usage-as-a-polyfill)),使用 [nvm](https://github.com/nvm-sh/nvm) 可管理本地多个 `node` 版本
|
||||
|
||||
```shell
|
||||
node -v
|
||||
@@ -37,19 +91,24 @@ node -v
|
||||
npm install pnpm -g
|
||||
```
|
||||
|
||||
### OpenAI API Key
|
||||
注册并获取 [OpenAI API key](https://platform.openai.com/overview) 并填写到本地环境变量
|
||||
### 填写密钥
|
||||
获取 `Openai Api Key` 或 `accessToken` 并填写本地环境变量 [跳转](#介绍)
|
||||
|
||||
```
|
||||
# service/.env 文件
|
||||
|
||||
OPENAI_API_KEY='Your key'
|
||||
# OpenAI API Key - https://platform.openai.com/overview
|
||||
OPENAI_API_KEY=
|
||||
|
||||
# change this to an `accessToken` extracted from the ChatGPT site's `https://chat.openai.com/api/auth/session` response
|
||||
OPENAI_ACCESS_TOKEN=
|
||||
```
|
||||
|
||||
## 安装依赖
|
||||
|
||||
> 为了简便 `后端开发人员` 的了解负担,所以并没有采用前端 `workspace` 模式,而是分文件夹存放。如果只需要前端页面做二次开发,删除 `service` 文件夹即可。
|
||||
|
||||
### 后端服务
|
||||
### 后端
|
||||
|
||||
进入文件夹 `/service` 运行以下命令
|
||||
|
||||
@@ -57,14 +116,13 @@ OPENAI_API_KEY='Your key'
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### 网页
|
||||
### 前端
|
||||
根目录下运行以下命令
|
||||
```shell
|
||||
pnpm bootstrap
|
||||
```
|
||||
|
||||
|
||||
## 运行
|
||||
## 测试环境运行
|
||||
### 后端服务
|
||||
|
||||
进入文件夹 `/service` 运行以下命令
|
||||
@@ -73,7 +131,7 @@ pnpm bootstrap
|
||||
pnpm start
|
||||
```
|
||||
|
||||
### 网页
|
||||
### 前端网页
|
||||
根目录下运行以下命令
|
||||
```shell
|
||||
pnpm dev
|
||||
@@ -81,10 +139,76 @@ pnpm dev
|
||||
|
||||
## 打包
|
||||
|
||||
### 后端服务
|
||||
### 使用 Docker
|
||||
|
||||
#### Docker 参数示例
|
||||
|
||||
- `OPENAI_API_KEY` 二选一
|
||||
- `OPENAI_ACCESS_TOKEN` 二选一,同时存在时,`OPENAI_API_KEY` 优先
|
||||
- `API_REVERSE_PROXY` 可选,设置 `OPENAI_ACCESS_TOKEN` 时可用 [参考](#介绍)
|
||||
- `TIMEOUT_MS` 超时,单位毫秒,可选
|
||||
|
||||

|
||||
|
||||
#### Docker build & Run
|
||||
|
||||
```bash
|
||||
docker build -t chatgpt-web .
|
||||
|
||||
# 前台运行
|
||||
docker run --name chatgpt-web --rm -it -p 3002:3002 --env OPENAI_API_KEY=your_api_key chatgpt-web
|
||||
|
||||
# 后台运行
|
||||
docker run --name chatgpt-web -d -p 3002:3002 --env OPENAI_API_KEY=your_api_key chatgpt-web
|
||||
|
||||
# 运行地址
|
||||
http://localhost:3002/
|
||||
```
|
||||
|
||||
#### Docker compose
|
||||
|
||||
[Hub 地址](https://hub.docker.com/repository/docker/chenzhaoyu94/chatgpt-web/general)
|
||||
|
||||
```yml
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
app:
|
||||
image: chenzhaoyu94/chatgpt-web # 总是使用latest,更新时重新pull该tag镜像即可
|
||||
ports:
|
||||
- 3002:3002
|
||||
environment:
|
||||
# 二选一
|
||||
OPENAI_API_KEY: xxxxxx
|
||||
# 二选一
|
||||
OPENAI_ACCESS_TOKEN: xxxxxx
|
||||
# 反向代理,可选
|
||||
API_REVERSE_PROXY: xxx
|
||||
# 超时,单位毫秒,可选
|
||||
TIMEOUT_MS: 60000
|
||||
```
|
||||
|
||||
### 使用 Railway 部署
|
||||
|
||||
[](https://railway.app/new/template/yytmgc)
|
||||
|
||||
#### Railway 环境变量
|
||||
|
||||
| 环境变量名称 | 必填 | 备注 |
|
||||
| --------------------------- | ---- | ----------------------- |
|
||||
| `PORT` | 必填 | 默认 `3002` |
|
||||
| `TIMEOUT_MS` | 可选 | 超时时间,单位毫秒, |
|
||||
| `OPENAI_API_KEY` | `OpenAI API` 二选一 | 使用 `OpenAI API` 所需的 `apiKey` [(获取 apiKey)](https://platform.openai.com/overview) |
|
||||
| `OPENAI_ACCESS_TOKEN` | `Web API` 二选一 | 使用 `Web API` 所需的 `accessToken` [(获取 accessToken)](https://chat.openai.com/api/auth/session) |
|
||||
| `API_REVERSE_PROXY` | 可选,`Web API` 时可用 | `Web API` 反向代理地址 [详情](https://github.com/transitive-bullshit/chatgpt-api#reverse-proxy) |
|
||||
|
||||
> 注意: `Railway` 修改环境变量会重新 `Deploy`
|
||||
|
||||
### 手动打包
|
||||
#### 后端服务
|
||||
> 如果你不需要本项目的 `node` 接口,可以省略如下操作
|
||||
|
||||
复制 `service` 文件夹到你有 `node` 服务环境的服务器上。(搜索关键字:`express部署`)
|
||||
复制 `service` 文件夹到你有 `node` 服务环境的服务器上。
|
||||
|
||||
```shell
|
||||
# 安装
|
||||
@@ -99,34 +223,11 @@ pnpm prod
|
||||
|
||||
PS: 不进行打包,直接在服务器上运行 `pnpm start` 也可
|
||||
|
||||
## Docker build & run
|
||||
#### 前端网页
|
||||
|
||||
[参考信息](https://github.com/Chanzhaoyu/chatgpt-web/pull/42)
|
||||
1、修改根目录下 `.env` 内 `VITE_APP_API_BASE_URL` 为你的实际后端接口地址
|
||||
|
||||
```bash
|
||||
docker build -t chatgpt-web .
|
||||
```
|
||||
|
||||
## Docker compose
|
||||
|
||||
[Hub 地址](https://hub.docker.com/repository/docker/chenzhaoyu94/chatgpt-web/general)
|
||||
|
||||
```yml
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
app:
|
||||
image: chenzhaoyu94/chatgpt-web:main
|
||||
ports:
|
||||
- 3002:3002
|
||||
environment:
|
||||
OPENAI_API_KEY: xxxxxx
|
||||
```
|
||||
|
||||
|
||||
### 网页
|
||||
|
||||
根目录下运行以下命令,然后将 `dist` 文件夹复制到你的托管服务器上
|
||||
2、根目录下运行以下命令,然后将 `dist` 文件夹内的文件复制到你网站服务的根目录下
|
||||
|
||||
[参考信息](https://cn.vitejs.dev/guide/static-deploy.html#building-the-app)
|
||||
|
||||
@@ -134,7 +235,7 @@ services:
|
||||
pnpm build
|
||||
```
|
||||
|
||||
### 常见问题
|
||||
## 常见问题
|
||||
Q: 为什么 `Git` 提交总是报错?
|
||||
|
||||
A: 因为有提交信息验证,请遵循 [Commit 指南](./CONTRIBUTING.md)
|
||||
@@ -157,5 +258,20 @@ A: `vscode` 请安装项目推荐插件,或手动安装 `Eslint` 插件。
|
||||
<img src="https://contrib.rocks/image?repo=Chanzhaoyu/chatgpt-web" />
|
||||
</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
|
||||
MIT © [ChenZhaoYu](./license)
|
||||
|
1
config/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './proxy'
|
16
config/proxy.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { ProxyOptions } from 'vite'
|
||||
|
||||
export function createViteProxy(isOpenProxy: boolean, viteEnv: ImportMetaEnv) {
|
||||
if (!isOpenProxy)
|
||||
return
|
||||
|
||||
const proxy: Record<string, string | ProxyOptions> = {
|
||||
'/api': {
|
||||
target: viteEnv.VITE_APP_API_BASE_URL,
|
||||
changeOrigin: true,
|
||||
rewrite: path => path.replace('/api/', '/'),
|
||||
},
|
||||
}
|
||||
|
||||
return proxy
|
||||
}
|
BIN
docs/alipay.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
docs/c1-2.8.0.png
Normal file
After Width: | Height: | Size: 123 KiB |
BIN
docs/c1.png
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 410 KiB |
BIN
docs/c2-2.8.0.png
Normal file
After Width: | Height: | Size: 396 KiB |
BIN
docs/c2.png
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 1.1 MiB |
BIN
docs/cover-2.png
Before Width: | Height: | Size: 200 KiB |
BIN
docs/cover.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
docs/cover2.png
Normal file
After Width: | Height: | Size: 518 KiB |
BIN
docs/docker.png
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
docs/wechat.png
Normal file
After Width: | Height: | Size: 80 KiB |
@@ -1,6 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-cmn-Hans">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
@@ -9,7 +8,7 @@
|
||||
<title>ChatGPT Web</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body class="dark:bg-black">
|
||||
<div id="app">
|
||||
<style>
|
||||
.loading-wrap {
|
||||
@@ -61,6 +60,12 @@
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #121212;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="loading-wrap">
|
||||
<div class="balls">
|
||||
|
18
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chatgpt-web",
|
||||
"version": "2.4.0",
|
||||
"version": "2.8.1",
|
||||
"private": false,
|
||||
"description": "ChatGPT Web",
|
||||
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
|
||||
@@ -23,8 +23,9 @@
|
||||
"common:prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^9.12.0",
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"highlight.js": "^11.7.0",
|
||||
"marked": "^4.2.12",
|
||||
"naive-ui": "^2.34.3",
|
||||
"pinia": "^2.0.30",
|
||||
"vue": "^3.2.47",
|
||||
@@ -32,11 +33,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^0.35.2",
|
||||
"@commitlint/cli": "^17.4.3",
|
||||
"@commitlint/config-conventional": "^17.4.3",
|
||||
"@commitlint/cli": "^17.4.4",
|
||||
"@commitlint/config-conventional": "^17.4.4",
|
||||
"@iconify/vue": "^4.1.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/node": "^18.13.0",
|
||||
"@types/marked": "^4.0.8",
|
||||
"@types/node": "^18.14.0",
|
||||
"@types/web-bluetooth": "^0.0.16",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
@@ -49,10 +51,10 @@
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.21",
|
||||
"rimraf": "^4.1.2",
|
||||
"tailwindcss": "^3.2.6",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"typescript": "~4.9.5",
|
||||
"vite": "^4.1.1",
|
||||
"vue-tsc": "^1.1.0"
|
||||
"vite": "^4.1.2",
|
||||
"vue-tsc": "^1.1.4"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx,vue}": [
|
||||
|
230
pnpm-lock.yaml
generated
@@ -2,14 +2,15 @@ lockfileVersion: 5.4
|
||||
|
||||
specifiers:
|
||||
'@antfu/eslint-config': ^0.35.2
|
||||
'@commitlint/cli': ^17.4.3
|
||||
'@commitlint/config-conventional': ^17.4.3
|
||||
'@commitlint/cli': ^17.4.4
|
||||
'@commitlint/config-conventional': ^17.4.4
|
||||
'@iconify/vue': ^4.1.0
|
||||
'@types/crypto-js': ^4.1.1
|
||||
'@types/node': ^18.13.0
|
||||
'@types/marked': ^4.0.8
|
||||
'@types/node': ^18.14.0
|
||||
'@types/web-bluetooth': ^0.0.16
|
||||
'@vitejs/plugin-vue': ^4.0.0
|
||||
'@vueuse/core': ^9.12.0
|
||||
'@vueuse/core': ^9.13.0
|
||||
autoprefixer: ^10.4.13
|
||||
axios: ^1.3.3
|
||||
crypto-js: ^4.1.1
|
||||
@@ -18,21 +19,23 @@ specifiers:
|
||||
husky: ^8.0.3
|
||||
less: ^4.1.3
|
||||
lint-staged: ^13.1.2
|
||||
marked: ^4.2.12
|
||||
naive-ui: ^2.34.3
|
||||
npm-run-all: ^4.1.5
|
||||
pinia: ^2.0.30
|
||||
postcss: ^8.4.21
|
||||
rimraf: ^4.1.2
|
||||
tailwindcss: ^3.2.6
|
||||
tailwindcss: ^3.2.7
|
||||
typescript: ~4.9.5
|
||||
vite: ^4.1.1
|
||||
vite: ^4.1.2
|
||||
vue: ^3.2.47
|
||||
vue-router: ^4.1.6
|
||||
vue-tsc: ^1.1.0
|
||||
vue-tsc: ^1.1.4
|
||||
|
||||
dependencies:
|
||||
'@vueuse/core': 9.12.0_vue@3.2.47
|
||||
'@vueuse/core': 9.13.0_vue@3.2.47
|
||||
highlight.js: 11.7.0
|
||||
marked: 4.2.12
|
||||
naive-ui: 2.34.3_vue@3.2.47
|
||||
pinia: 2.0.30_hmuptsblhheur2tugfgucj7gc4
|
||||
vue: 3.2.47
|
||||
@@ -40,13 +43,14 @@ dependencies:
|
||||
|
||||
devDependencies:
|
||||
'@antfu/eslint-config': 0.35.2_7kw3g6rralp5ps6mg3uyzz6azm
|
||||
'@commitlint/cli': 17.4.3
|
||||
'@commitlint/config-conventional': 17.4.3
|
||||
'@commitlint/cli': 17.4.4
|
||||
'@commitlint/config-conventional': 17.4.4
|
||||
'@iconify/vue': 4.1.0_vue@3.2.47
|
||||
'@types/crypto-js': 4.1.1
|
||||
'@types/node': 18.13.0
|
||||
'@types/marked': 4.0.8
|
||||
'@types/node': 18.14.0
|
||||
'@types/web-bluetooth': 0.0.16
|
||||
'@vitejs/plugin-vue': 4.0.0_vite@4.1.1+vue@3.2.47
|
||||
'@vitejs/plugin-vue': 4.0.0_vite@4.1.2+vue@3.2.47
|
||||
autoprefixer: 10.4.13_postcss@8.4.21
|
||||
axios: 1.3.3
|
||||
crypto-js: 4.1.1
|
||||
@@ -57,10 +61,10 @@ devDependencies:
|
||||
npm-run-all: 4.1.5
|
||||
postcss: 8.4.21
|
||||
rimraf: 4.1.2
|
||||
tailwindcss: 3.2.6_postcss@8.4.21
|
||||
tailwindcss: 3.2.7_postcss@8.4.21
|
||||
typescript: 4.9.5
|
||||
vite: 4.1.1_edl3ajnhen4c5iqs57t4gqrzly
|
||||
vue-tsc: 1.1.0_typescript@4.9.5
|
||||
vite: 4.1.2_lxiyzdpogvsuukhx3wcpsdczbq
|
||||
vue-tsc: 1.1.4_typescript@4.9.5
|
||||
|
||||
packages:
|
||||
|
||||
@@ -199,16 +203,16 @@ packages:
|
||||
'@babel/helper-validator-identifier': 7.19.1
|
||||
to-fast-properties: 2.0.0
|
||||
|
||||
/@commitlint/cli/17.4.3:
|
||||
resolution: {integrity: sha512-IPTS7AZuBHgD0gl24El8HwuDM9zJN9JLa5KmZUQoFD1BQeGGdzAYJOnAr85CeJWpTDok0BGHDL0+4odnH0iTyA==}
|
||||
/@commitlint/cli/17.4.4:
|
||||
resolution: {integrity: sha512-HwKlD7CPVMVGTAeFZylVNy14Vm5POVY0WxPkZr7EXLC/os0LH/obs6z4HRvJtH/nHCMYBvUBQhGwnufKfTjd5g==}
|
||||
engines: {node: '>=v14'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@commitlint/format': 17.4.0
|
||||
'@commitlint/lint': 17.4.3
|
||||
'@commitlint/load': 17.4.2
|
||||
'@commitlint/read': 17.4.2
|
||||
'@commitlint/types': 17.4.0
|
||||
'@commitlint/format': 17.4.4
|
||||
'@commitlint/lint': 17.4.4
|
||||
'@commitlint/load': 17.4.4
|
||||
'@commitlint/read': 17.4.4
|
||||
'@commitlint/types': 17.4.4
|
||||
execa: 5.1.1
|
||||
lodash.isfunction: 3.0.9
|
||||
resolve-from: 5.0.0
|
||||
@@ -219,26 +223,26 @@ packages:
|
||||
- '@swc/wasm'
|
||||
dev: true
|
||||
|
||||
/@commitlint/config-conventional/17.4.3:
|
||||
resolution: {integrity: sha512-8EsY2iDw74hCk3hIQSg7/E0R8/KtPjnFPZVwmmHxcjhZjkSykmxysefICPDnbI3xgxfov0zwL1WKDHM8zglJdw==}
|
||||
/@commitlint/config-conventional/17.4.4:
|
||||
resolution: {integrity: sha512-u6ztvxqzi6NuhrcEDR7a+z0yrh11elY66nRrQIpqsqW6sZmpxYkDLtpRH8jRML+mmxYQ8s4qqF06Q/IQx5aJeQ==}
|
||||
engines: {node: '>=v14'}
|
||||
dependencies:
|
||||
conventional-changelog-conventionalcommits: 5.0.0
|
||||
dev: true
|
||||
|
||||
/@commitlint/config-validator/17.4.0:
|
||||
resolution: {integrity: sha512-Sa/+8KNpDXz4zT4bVbz2fpFjvgkPO6u2V2fP4TKgt6FjmOw2z3eEX859vtfeaTav/ukBw0/0jr+5ZTZp9zCBhA==}
|
||||
/@commitlint/config-validator/17.4.4:
|
||||
resolution: {integrity: sha512-bi0+TstqMiqoBAQDvdEP4AFh0GaKyLFlPPEObgI29utoKEYoPQTvF0EYqIwYYLEoJYhj5GfMIhPHJkTJhagfeg==}
|
||||
engines: {node: '>=v14'}
|
||||
dependencies:
|
||||
'@commitlint/types': 17.4.0
|
||||
'@commitlint/types': 17.4.4
|
||||
ajv: 8.12.0
|
||||
dev: true
|
||||
|
||||
/@commitlint/ensure/17.4.0:
|
||||
resolution: {integrity: sha512-7oAxt25je0jeQ/E0O/M8L3ADb1Cvweu/5lc/kYF8g/kXatI0wxGE5La52onnAUAWeWlsuvBNar15WcrmDmr5Mw==}
|
||||
/@commitlint/ensure/17.4.4:
|
||||
resolution: {integrity: sha512-AHsFCNh8hbhJiuZ2qHv/m59W/GRE9UeOXbkOqxYMNNg9pJ7qELnFcwj5oYpa6vzTSHtPGKf3C2yUFNy1GGHq6g==}
|
||||
engines: {node: '>=v14'}
|
||||
dependencies:
|
||||
'@commitlint/types': 17.4.0
|
||||
'@commitlint/types': 17.4.4
|
||||
lodash.camelcase: 4.3.0
|
||||
lodash.kebabcase: 4.1.1
|
||||
lodash.snakecase: 4.1.1
|
||||
@@ -251,49 +255,49 @@ packages:
|
||||
engines: {node: '>=v14'}
|
||||
dev: true
|
||||
|
||||
/@commitlint/format/17.4.0:
|
||||
resolution: {integrity: sha512-Z2bWAU5+f1YZh9W76c84J8iLIWIvvm+mzqogTz0Nsc1x6EHW0Z2gI38g5HAjB0r0I3ZjR15IDEJKhsxyblcyhA==}
|
||||
/@commitlint/format/17.4.4:
|
||||
resolution: {integrity: sha512-+IS7vpC4Gd/x+uyQPTAt3hXs5NxnkqAZ3aqrHd5Bx/R9skyCAWusNlNbw3InDbAK6j166D9asQM8fnmYIa+CXQ==}
|
||||
engines: {node: '>=v14'}
|
||||
dependencies:
|
||||
'@commitlint/types': 17.4.0
|
||||
'@commitlint/types': 17.4.4
|
||||
chalk: 4.1.2
|
||||
dev: true
|
||||
|
||||
/@commitlint/is-ignored/17.4.2:
|
||||
resolution: {integrity: sha512-1b2Y2qJ6n7bHG9K6h8S4lBGUl6kc7mMhJN9gy1SQfUZqe92ToDjUTtgNWb6LbzR1X8Cq4SEus4VU8Z/riEa94Q==}
|
||||
/@commitlint/is-ignored/17.4.4:
|
||||
resolution: {integrity: sha512-Y3eo1SFJ2JQDik4rWkBC4tlRIxlXEFrRWxcyrzb1PUT2k3kZ/XGNuCDfk/u0bU2/yS0tOA/mTjFsV+C4qyACHw==}
|
||||
engines: {node: '>=v14'}
|
||||
dependencies:
|
||||
'@commitlint/types': 17.4.0
|
||||
'@commitlint/types': 17.4.4
|
||||
semver: 7.3.8
|
||||
dev: true
|
||||
|
||||
/@commitlint/lint/17.4.3:
|
||||
resolution: {integrity: sha512-GnPsqEYmXIB/MaBhRMzkiDJWyjuLrKad4xoxKO4N6Kc19iqjR4DPc/bl2dxeW9kUmtrAtefOzIEzJAevpA5y2w==}
|
||||
/@commitlint/lint/17.4.4:
|
||||
resolution: {integrity: sha512-qgkCRRFjyhbMDWsti/5jRYVJkgYZj4r+ZmweZObnbYqPUl5UKLWMf9a/ZZisOI4JfiPmRktYRZ2JmqlSvg+ccw==}
|
||||
engines: {node: '>=v14'}
|
||||
dependencies:
|
||||
'@commitlint/is-ignored': 17.4.2
|
||||
'@commitlint/parse': 17.4.2
|
||||
'@commitlint/rules': 17.4.3
|
||||
'@commitlint/types': 17.4.0
|
||||
'@commitlint/is-ignored': 17.4.4
|
||||
'@commitlint/parse': 17.4.4
|
||||
'@commitlint/rules': 17.4.4
|
||||
'@commitlint/types': 17.4.4
|
||||
dev: true
|
||||
|
||||
/@commitlint/load/17.4.2:
|
||||
resolution: {integrity: sha512-Si++F85rJ9t4hw6JcOw1i2h0fdpdFQt0YKwjuK4bk9KhFjyFkRxvR3SB2dPaMs+EwWlDrDBGL+ygip1QD6gmPw==}
|
||||
/@commitlint/load/17.4.4:
|
||||
resolution: {integrity: sha512-z6uFIQ7wfKX5FGBe1AkOF4l/ShOQsaa1ml/nLMkbW7R/xF8galGS7Zh0yHvzVp/srtfS0brC+0bUfQfmpMPFVQ==}
|
||||
engines: {node: '>=v14'}
|
||||
dependencies:
|
||||
'@commitlint/config-validator': 17.4.0
|
||||
'@commitlint/config-validator': 17.4.4
|
||||
'@commitlint/execute-rule': 17.4.0
|
||||
'@commitlint/resolve-extends': 17.4.0
|
||||
'@commitlint/types': 17.4.0
|
||||
'@types/node': 18.13.0
|
||||
'@commitlint/resolve-extends': 17.4.4
|
||||
'@commitlint/types': 17.4.4
|
||||
'@types/node': 18.14.0
|
||||
chalk: 4.1.2
|
||||
cosmiconfig: 8.0.0
|
||||
cosmiconfig-typescript-loader: 4.3.0_p7cp6dsfhdrlk7mvuxd3wodbsu
|
||||
cosmiconfig-typescript-loader: 4.3.0_ipkhww4xc5z2tt2x53vp2mt2be
|
||||
lodash.isplainobject: 4.0.6
|
||||
lodash.merge: 4.6.2
|
||||
lodash.uniq: 4.5.0
|
||||
resolve-from: 5.0.0
|
||||
ts-node: 10.9.1_4bewfcp2iebiwuold25d6rgcsy
|
||||
ts-node: 10.9.1_tncu2ai53lzgmizdedur7lbibe
|
||||
typescript: 4.9.5
|
||||
transitivePeerDependencies:
|
||||
- '@swc/core'
|
||||
@@ -305,46 +309,46 @@ packages:
|
||||
engines: {node: '>=v14'}
|
||||
dev: true
|
||||
|
||||
/@commitlint/parse/17.4.2:
|
||||
resolution: {integrity: sha512-DK4EwqhxfXpyCA+UH8TBRIAXAfmmX4q9QRBz/2h9F9sI91yt6mltTrL6TKURMcjUVmgaB80wgS9QybNIyVBIJA==}
|
||||
/@commitlint/parse/17.4.4:
|
||||
resolution: {integrity: sha512-EKzz4f49d3/OU0Fplog7nwz/lAfXMaDxtriidyGF9PtR+SRbgv4FhsfF310tKxs6EPj8Y+aWWuX3beN5s+yqGg==}
|
||||
engines: {node: '>=v14'}
|
||||
dependencies:
|
||||
'@commitlint/types': 17.4.0
|
||||
'@commitlint/types': 17.4.4
|
||||
conventional-changelog-angular: 5.0.13
|
||||
conventional-commits-parser: 3.2.4
|
||||
dev: true
|
||||
|
||||
/@commitlint/read/17.4.2:
|
||||
resolution: {integrity: sha512-hasYOdbhEg+W4hi0InmXHxtD/1favB4WdwyFxs1eOy/DvMw6+2IZBmATgGOlqhahsypk4kChhxjAFJAZ2F+JBg==}
|
||||
/@commitlint/read/17.4.4:
|
||||
resolution: {integrity: sha512-B2TvUMJKK+Svzs6eji23WXsRJ8PAD+orI44lVuVNsm5zmI7O8RSGJMvdEZEikiA4Vohfb+HevaPoWZ7PiFZ3zA==}
|
||||
engines: {node: '>=v14'}
|
||||
dependencies:
|
||||
'@commitlint/top-level': 17.4.0
|
||||
'@commitlint/types': 17.4.0
|
||||
'@commitlint/types': 17.4.4
|
||||
fs-extra: 11.1.0
|
||||
git-raw-commits: 2.0.11
|
||||
minimist: 1.2.8
|
||||
dev: true
|
||||
|
||||
/@commitlint/resolve-extends/17.4.0:
|
||||
resolution: {integrity: sha512-3JsmwkrCzoK8sO22AzLBvNEvC1Pmdn/65RKXzEtQMy6oYMl0Snrq97a5bQQEFETF0VsvbtUuKttLqqgn99OXRQ==}
|
||||
/@commitlint/resolve-extends/17.4.4:
|
||||
resolution: {integrity: sha512-znXr1S0Rr8adInptHw0JeLgumS11lWbk5xAWFVno+HUFVN45875kUtqjrI6AppmD3JI+4s0uZlqqlkepjJd99A==}
|
||||
engines: {node: '>=v14'}
|
||||
dependencies:
|
||||
'@commitlint/config-validator': 17.4.0
|
||||
'@commitlint/types': 17.4.0
|
||||
'@commitlint/config-validator': 17.4.4
|
||||
'@commitlint/types': 17.4.4
|
||||
import-fresh: 3.3.0
|
||||
lodash.mergewith: 4.6.2
|
||||
resolve-from: 5.0.0
|
||||
resolve-global: 1.0.0
|
||||
dev: true
|
||||
|
||||
/@commitlint/rules/17.4.3:
|
||||
resolution: {integrity: sha512-xHReDfE3Z+O9p1sXeEhPRSk4FifBsC4EbXzvQ4aa0ykQe+n/iZDd4CrFC/Oiv2K9BU4ZnFHak30IbMLa4ks1Rw==}
|
||||
/@commitlint/rules/17.4.4:
|
||||
resolution: {integrity: sha512-0tgvXnHi/mVcyR8Y8mjTFZIa/FEQXA4uEutXS/imH2v1UNkYDSEMsK/68wiXRpfW1euSgEdwRkvE1z23+yhNrQ==}
|
||||
engines: {node: '>=v14'}
|
||||
dependencies:
|
||||
'@commitlint/ensure': 17.4.0
|
||||
'@commitlint/ensure': 17.4.4
|
||||
'@commitlint/message': 17.4.2
|
||||
'@commitlint/to-lines': 17.4.0
|
||||
'@commitlint/types': 17.4.0
|
||||
'@commitlint/types': 17.4.4
|
||||
execa: 5.1.1
|
||||
dev: true
|
||||
|
||||
@@ -360,8 +364,8 @@ packages:
|
||||
find-up: 5.0.0
|
||||
dev: true
|
||||
|
||||
/@commitlint/types/17.4.0:
|
||||
resolution: {integrity: sha512-2NjAnq5IcxY9kXtUeO2Ac0aPpvkuOmwbH/BxIm36XXK5LtWFObWJWjXOA+kcaABMrthjWu6la+FUpyYFMHRvbA==}
|
||||
/@commitlint/types/17.4.4:
|
||||
resolution: {integrity: sha512-amRN8tRLYOsxRr6mTnGGGvB5EmW/4DDjLMgiwK3CCVEmN6Sr/6xePGEpWaspKkckILuUORCwe6VfDBw6uj4axQ==}
|
||||
engines: {node: '>=v14'}
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
@@ -735,6 +739,10 @@ packages:
|
||||
resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
|
||||
dev: false
|
||||
|
||||
/@types/marked/4.0.8:
|
||||
resolution: {integrity: sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==}
|
||||
dev: true
|
||||
|
||||
/@types/mdast/3.0.10:
|
||||
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
|
||||
dependencies:
|
||||
@@ -745,8 +753,8 @@ packages:
|
||||
resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
|
||||
dev: true
|
||||
|
||||
/@types/node/18.13.0:
|
||||
resolution: {integrity: sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==}
|
||||
/@types/node/18.14.0:
|
||||
resolution: {integrity: sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==}
|
||||
dev: true
|
||||
|
||||
/@types/normalize-package-data/2.4.1:
|
||||
@@ -894,40 +902,40 @@ packages:
|
||||
eslint-visitor-keys: 3.3.0
|
||||
dev: true
|
||||
|
||||
/@vitejs/plugin-vue/4.0.0_vite@4.1.1+vue@3.2.47:
|
||||
/@vitejs/plugin-vue/4.0.0_vite@4.1.2+vue@3.2.47:
|
||||
resolution: {integrity: sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
vite: ^4.0.0
|
||||
vue: ^3.2.25
|
||||
dependencies:
|
||||
vite: 4.1.1_edl3ajnhen4c5iqs57t4gqrzly
|
||||
vite: 4.1.2_lxiyzdpogvsuukhx3wcpsdczbq
|
||||
vue: 3.2.47
|
||||
dev: true
|
||||
|
||||
/@volar/language-core/1.2.0-alpha.11:
|
||||
resolution: {integrity: sha512-OfbPmmFa4LUA8kJCg77V9ud4NASjJ3VKJ79QCQSfHa5SwXeZ5w7lvQe2yILFBjZ3JDB5EfFnHZUSct6ziK3x5Q==}
|
||||
/@volar/language-core/1.2.0-alpha.16:
|
||||
resolution: {integrity: sha512-aIktWe9Zg0M+u/RIXHCuL+IoLRHTrpsbTib8olrg4etlurHDXahoVhoEnH9wmlliray0iigIo2z5vwueYInp3g==}
|
||||
dependencies:
|
||||
'@volar/source-map': 1.2.0-alpha.11
|
||||
'@volar/source-map': 1.2.0-alpha.16
|
||||
dev: true
|
||||
|
||||
/@volar/source-map/1.2.0-alpha.11:
|
||||
resolution: {integrity: sha512-GCRqcq2bn8Gf9N/qbdl8GgfGbmYuuSIB8arhl+gRZfCIWvT5NhIRVlG5GX0lkgpp02lA8ZYWZ0GLGOkwz7+DMQ==}
|
||||
/@volar/source-map/1.2.0-alpha.16:
|
||||
resolution: {integrity: sha512-/AK3VqnFqONd221COI2ZnEvfgBulfoQkjA/ZjPOXpsOkWri99TLcfZY/NTQRytp7Hx6EP/1p1DDeyGuMCUYjgA==}
|
||||
dependencies:
|
||||
muggle-string: 0.2.2
|
||||
dev: true
|
||||
|
||||
/@volar/typescript/1.2.0-alpha.11:
|
||||
resolution: {integrity: sha512-tJ20326E/Xi1lvvuWX57boVJtzhStNF3HjBu4orjl9PqCXUbhqWwP+jRYzyb+nLbHqGPmEBvHKYjAO3GsJ/YXg==}
|
||||
/@volar/typescript/1.2.0-alpha.16:
|
||||
resolution: {integrity: sha512-ltlTLHIkLxgmTVBZmOnhmnlNzEj2lpvlBmmaV2GWYTrBUMt0z1OgeCq0Utlj9HjjrGPhwWxZNkv86ZABgrMA3Q==}
|
||||
dependencies:
|
||||
'@volar/language-core': 1.2.0-alpha.11
|
||||
'@volar/language-core': 1.2.0-alpha.16
|
||||
dev: true
|
||||
|
||||
/@volar/vue-language-core/1.1.0:
|
||||
resolution: {integrity: sha512-1zTAyeGiyNKYE9s+i3dUpmuvY/Cz1U7LjIh9d5FX3p0NWpaBrzYvSh0gQY+nRaz67or7Y9qYSUCaHLKOmeolzg==}
|
||||
/@volar/vue-language-core/1.1.4:
|
||||
resolution: {integrity: sha512-2C2CwHvaT5AzNzDbYZQ85lNr4jACZARoZMZBLuU5+JyIwhWeAfxvyAeoE3VbgfgycN5t6X4uBqx/Wzh1QLgD8Q==}
|
||||
dependencies:
|
||||
'@volar/language-core': 1.2.0-alpha.11
|
||||
'@volar/source-map': 1.2.0-alpha.11
|
||||
'@volar/language-core': 1.2.0-alpha.16
|
||||
'@volar/source-map': 1.2.0-alpha.16
|
||||
'@vue/compiler-dom': 3.2.47
|
||||
'@vue/compiler-sfc': 3.2.47
|
||||
'@vue/reactivity': 3.2.47
|
||||
@@ -937,11 +945,11 @@ packages:
|
||||
vue-template-compiler: 2.7.14
|
||||
dev: true
|
||||
|
||||
/@volar/vue-typescript/1.1.0:
|
||||
resolution: {integrity: sha512-smtfaePuNpVzXEypJayORtl8muvBdtV1FDWjces1WLYbbtcnmfWtdACW9xY0dkVk0LoE/LZTEmLBCQrRJ6hS1w==}
|
||||
/@volar/vue-typescript/1.1.4:
|
||||
resolution: {integrity: sha512-x5i5TUUXb1PM0rM80Y8XUeMBUcoS3/TjR3WTxvvEUIol9uEOPp6uxxQQ67uSv7ocN6vB0LugJqS6FA7Z93oL0Q==}
|
||||
dependencies:
|
||||
'@volar/typescript': 1.2.0-alpha.11
|
||||
'@volar/vue-language-core': 1.1.0
|
||||
'@volar/typescript': 1.2.0-alpha.16
|
||||
'@volar/vue-language-core': 1.1.4
|
||||
dev: true
|
||||
|
||||
/@vue/compiler-core/3.2.47:
|
||||
@@ -1021,24 +1029,24 @@ packages:
|
||||
/@vue/shared/3.2.47:
|
||||
resolution: {integrity: sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==}
|
||||
|
||||
/@vueuse/core/9.12.0_vue@3.2.47:
|
||||
resolution: {integrity: sha512-h/Di8Bvf6xRcvS/PvUVheiMYYz3U0tH3X25YxONSaAUBa841ayMwxkuzx/DGUMCW/wHWzD8tRy2zYmOC36r4sg==}
|
||||
/@vueuse/core/9.13.0_vue@3.2.47:
|
||||
resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.16
|
||||
'@vueuse/metadata': 9.12.0
|
||||
'@vueuse/shared': 9.12.0_vue@3.2.47
|
||||
'@vueuse/metadata': 9.13.0
|
||||
'@vueuse/shared': 9.13.0_vue@3.2.47
|
||||
vue-demi: 0.13.11_vue@3.2.47
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
dev: false
|
||||
|
||||
/@vueuse/metadata/9.12.0:
|
||||
resolution: {integrity: sha512-9oJ9MM9lFLlmvxXUqsR1wLt1uF7EVbP5iYaHJYqk+G2PbMjY6EXvZeTjbdO89HgoF5cI6z49o2zT/jD9SVoNpQ==}
|
||||
/@vueuse/metadata/9.13.0:
|
||||
resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
|
||||
dev: false
|
||||
|
||||
/@vueuse/shared/9.12.0_vue@3.2.47:
|
||||
resolution: {integrity: sha512-TWuJLACQ0BVithVTRbex4Wf1a1VaRuSpVeyEd4vMUWl54PzlE0ciFUshKCXnlLuD0lxIaLK4Ypj3NXYzZh4+SQ==}
|
||||
/@vueuse/shared/9.13.0_vue@3.2.47:
|
||||
resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
|
||||
dependencies:
|
||||
vue-demi: 0.13.11_vue@3.2.47
|
||||
transitivePeerDependencies:
|
||||
@@ -1530,7 +1538,7 @@ packages:
|
||||
is-what: 3.14.1
|
||||
dev: true
|
||||
|
||||
/cosmiconfig-typescript-loader/4.3.0_p7cp6dsfhdrlk7mvuxd3wodbsu:
|
||||
/cosmiconfig-typescript-loader/4.3.0_ipkhww4xc5z2tt2x53vp2mt2be:
|
||||
resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==}
|
||||
engines: {node: '>=12', npm: '>=6'}
|
||||
peerDependencies:
|
||||
@@ -1539,9 +1547,9 @@ packages:
|
||||
ts-node: '>=10'
|
||||
typescript: '>=3'
|
||||
dependencies:
|
||||
'@types/node': 18.13.0
|
||||
'@types/node': 18.14.0
|
||||
cosmiconfig: 8.0.0
|
||||
ts-node: 10.9.1_4bewfcp2iebiwuold25d6rgcsy
|
||||
ts-node: 10.9.1_tncu2ai53lzgmizdedur7lbibe
|
||||
typescript: 4.9.5
|
||||
dev: true
|
||||
|
||||
@@ -3229,6 +3237,12 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/marked/4.2.12:
|
||||
resolution: {integrity: sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==}
|
||||
engines: {node: '>= 12'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/mdast-util-from-markdown/0.8.5:
|
||||
resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==}
|
||||
dependencies:
|
||||
@@ -4290,8 +4304,8 @@ packages:
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/tailwindcss/3.2.6_postcss@8.4.21:
|
||||
resolution: {integrity: sha512-BfgQWZrtqowOQMC2bwaSNe7xcIjdDEgixWGYOd6AL0CbKHJlvhfdbINeAW76l1sO+1ov/MJ93ODJ9yluRituIw==}
|
||||
/tailwindcss/3.2.7_postcss@8.4.21:
|
||||
resolution: {integrity: sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==}
|
||||
engines: {node: '>=12.13.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -4363,7 +4377,7 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/ts-node/10.9.1_4bewfcp2iebiwuold25d6rgcsy:
|
||||
/ts-node/10.9.1_tncu2ai53lzgmizdedur7lbibe:
|
||||
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -4382,7 +4396,7 @@ packages:
|
||||
'@tsconfig/node12': 1.0.11
|
||||
'@tsconfig/node14': 1.0.3
|
||||
'@tsconfig/node16': 1.0.3
|
||||
'@types/node': 18.13.0
|
||||
'@types/node': 18.14.0
|
||||
acorn: 8.8.2
|
||||
acorn-walk: 8.2.0
|
||||
arg: 4.1.3
|
||||
@@ -4527,8 +4541,8 @@ packages:
|
||||
vue: 3.2.47
|
||||
dev: false
|
||||
|
||||
/vite/4.1.1_edl3ajnhen4c5iqs57t4gqrzly:
|
||||
resolution: {integrity: sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==}
|
||||
/vite/4.1.2_lxiyzdpogvsuukhx3wcpsdczbq:
|
||||
resolution: {integrity: sha512-MWDb9Rfy3DI8omDQySbMK93nQqStwbsQWejXRY2EBzEWKmLAXWb1mkI9Yw2IJrc+oCvPCI1Os5xSSIBYY6DEAw==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -4552,7 +4566,7 @@ packages:
|
||||
terser:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/node': 18.13.0
|
||||
'@types/node': 18.14.0
|
||||
esbuild: 0.16.17
|
||||
less: 4.1.3
|
||||
postcss: 8.4.21
|
||||
@@ -4620,14 +4634,14 @@ packages:
|
||||
he: 1.2.0
|
||||
dev: true
|
||||
|
||||
/vue-tsc/1.1.0_typescript@4.9.5:
|
||||
resolution: {integrity: sha512-+JqTcuScA6OfyaVH3ezeCh2i2wRgzUScZ6EdTZ3AW69Nb+rmRyOAxmAjL6MPam8YCdwmmdfAUhmN/BNGVp5vQg==}
|
||||
/vue-tsc/1.1.4_typescript@4.9.5:
|
||||
resolution: {integrity: sha512-CMG8KZsBBPyulYie05XxJCfq/yAPiB/uMMeHmog09aLm2Kml82C6tUSRgQz8ujM4JoCrpDqVCd9G0NuM9aLt1g==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
dependencies:
|
||||
'@volar/vue-language-core': 1.1.0
|
||||
'@volar/vue-typescript': 1.1.0
|
||||
'@volar/vue-language-core': 1.1.4
|
||||
'@volar/vue-typescript': 1.1.4
|
||||
typescript: 4.9.5
|
||||
dev: true
|
||||
|
||||
|
@@ -1,2 +1,11 @@
|
||||
# OpenAI API Key - https://platform.openai.com/overview
|
||||
OPENAI_API_KEY=
|
||||
|
||||
# change this to an `accessToken` extracted from the ChatGPT site's `https://chat.openai.com/api/auth/session` response
|
||||
OPENAI_ACCESS_TOKEN=
|
||||
|
||||
# Reverse Proxy
|
||||
API_REVERSE_PROXY=
|
||||
|
||||
# timeout
|
||||
TIMEOUT_MS=60000
|
||||
|
@@ -15,6 +15,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "esno ./src/index.ts",
|
||||
"dev": "esno watch ./src/index.ts",
|
||||
"prod": "esno ./build/index.js",
|
||||
"build": "pnpm clean && tsup",
|
||||
"clean": "rimraf build",
|
||||
@@ -23,19 +24,21 @@
|
||||
"common:cleanup": "rimraf node_modules && rimraf pnpm-lock.yaml"
|
||||
},
|
||||
"dependencies": {
|
||||
"chatgpt": "^4.3.2",
|
||||
"chatgpt": "^4.7.2",
|
||||
"dotenv": "^16.0.3",
|
||||
"esno": "^0.16.3",
|
||||
"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": {
|
||||
"@antfu/eslint-config": "^0.35.2",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/node": "^18.13.0",
|
||||
"@types/node": "^18.14.0",
|
||||
"eslint": "^8.34.0",
|
||||
"rimraf": "^4.1.2",
|
||||
"tsup": "^6.6.2",
|
||||
"tsup": "^6.6.3",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
321
service/pnpm-lock.yaml
generated
@@ -3,28 +3,32 @@ lockfileVersion: 5.4
|
||||
specifiers:
|
||||
'@antfu/eslint-config': ^0.35.2
|
||||
'@types/express': ^4.17.17
|
||||
'@types/node': ^18.13.0
|
||||
chatgpt: ^4.3.2
|
||||
'@types/node': ^18.14.0
|
||||
chatgpt: ^4.7.2
|
||||
dotenv: ^16.0.3
|
||||
eslint: ^8.34.0
|
||||
esno: ^0.16.3
|
||||
express: ^4.18.2
|
||||
isomorphic-fetch: ^3.0.0
|
||||
node-fetch: ^3.3.0
|
||||
rimraf: ^4.1.2
|
||||
tsup: ^6.6.2
|
||||
socks-proxy-agent: ^7.0.0
|
||||
tsup: ^6.6.3
|
||||
typescript: ^4.9.5
|
||||
|
||||
dependencies:
|
||||
chatgpt: 4.4.0
|
||||
chatgpt: 4.7.2
|
||||
dotenv: 16.0.3
|
||||
esno: 0.16.3
|
||||
express: 4.18.2
|
||||
isomorphic-fetch: 3.0.0
|
||||
node-fetch: 3.3.0
|
||||
socks-proxy-agent: 7.0.0
|
||||
|
||||
devDependencies:
|
||||
'@antfu/eslint-config': 0.35.2_7kw3g6rralp5ps6mg3uyzz6azm
|
||||
'@types/express': 4.17.17
|
||||
'@types/node': 18.13.0
|
||||
'@types/node': 18.14.0
|
||||
eslint: 8.34.0
|
||||
rimraf: 4.1.2
|
||||
tsup: 6.6.3_typescript@4.9.5
|
||||
@@ -133,12 +137,10 @@ packages:
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/highlight': 7.18.6
|
||||
dev: true
|
||||
|
||||
/@babel/helper-validator-identifier/7.19.1:
|
||||
resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: true
|
||||
|
||||
/@babel/highlight/7.18.6:
|
||||
resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==}
|
||||
@@ -147,7 +149,6 @@ packages:
|
||||
'@babel/helper-validator-identifier': 7.19.1
|
||||
chalk: 2.4.2
|
||||
js-tokens: 4.0.0
|
||||
dev: true
|
||||
|
||||
/@esbuild-kit/cjs-loader/2.4.2:
|
||||
resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==}
|
||||
@@ -418,19 +419,19 @@ packages:
|
||||
resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
|
||||
dependencies:
|
||||
'@types/connect': 3.4.35
|
||||
'@types/node': 18.13.0
|
||||
'@types/node': 18.14.0
|
||||
dev: true
|
||||
|
||||
/@types/connect/3.4.35:
|
||||
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
|
||||
dependencies:
|
||||
'@types/node': 18.13.0
|
||||
'@types/node': 18.14.0
|
||||
dev: true
|
||||
|
||||
/@types/express-serve-static-core/4.17.33:
|
||||
resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==}
|
||||
dependencies:
|
||||
'@types/node': 18.13.0
|
||||
'@types/node': 18.14.0
|
||||
'@types/qs': 6.9.7
|
||||
'@types/range-parser': 1.2.4
|
||||
dev: true
|
||||
@@ -462,13 +463,12 @@ packages:
|
||||
resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
|
||||
dev: true
|
||||
|
||||
/@types/node/18.13.0:
|
||||
resolution: {integrity: sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==}
|
||||
/@types/node/18.14.0:
|
||||
resolution: {integrity: sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==}
|
||||
dev: true
|
||||
|
||||
/@types/normalize-package-data/2.4.1:
|
||||
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
|
||||
dev: true
|
||||
|
||||
/@types/qs/6.9.7:
|
||||
resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==}
|
||||
@@ -486,7 +486,7 @@ packages:
|
||||
resolution: {integrity: sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==}
|
||||
dependencies:
|
||||
'@types/mime': 3.0.1
|
||||
'@types/node': 18.13.0
|
||||
'@types/node': 18.14.0
|
||||
dev: true
|
||||
|
||||
/@types/unist/2.0.6:
|
||||
@@ -645,6 +645,24 @@ packages:
|
||||
hasBin: 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:
|
||||
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
||||
peerDependenciesMeta:
|
||||
ajv:
|
||||
optional: true
|
||||
dependencies:
|
||||
ajv: 8.12.0
|
||||
dev: false
|
||||
|
||||
/ajv/6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
dependencies:
|
||||
@@ -654,6 +672,15 @@ packages:
|
||||
uri-js: 4.4.1
|
||||
dev: true
|
||||
|
||||
/ajv/8.12.0:
|
||||
resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
json-schema-traverse: 1.0.0
|
||||
require-from-string: 2.0.2
|
||||
uri-js: 4.4.1
|
||||
dev: false
|
||||
|
||||
/ansi-regex/5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -664,7 +691,6 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
color-convert: 1.9.3
|
||||
dev: true
|
||||
|
||||
/ansi-styles/4.3.0:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
@@ -704,6 +730,10 @@ packages:
|
||||
is-string: 1.0.7
|
||||
dev: true
|
||||
|
||||
/array-keyed-map/2.1.3:
|
||||
resolution: {integrity: sha512-JIUwuFakO+jHjxyp4YgSiKXSZeC0U+R1jR94bXWBcVlFRBycqXlb+kH9JHxBGcxnVuSqx5bnn0Qz9xtSeKOjiA==}
|
||||
dev: false
|
||||
|
||||
/array-union/2.1.0:
|
||||
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -729,6 +759,13 @@ packages:
|
||||
es-shim-unscopables: 1.0.0
|
||||
dev: true
|
||||
|
||||
/atomically/2.0.1:
|
||||
resolution: {integrity: sha512-sxBhVZUFBFhqSAsYMM3X2oaUi2NVDJ8U026FsIusM8gYXls9AYs/eXzgGrufs1Qjpkxi9zunds+75QUFz+m7UQ==}
|
||||
dependencies:
|
||||
stubborn-fs: 1.2.4
|
||||
when-exit: 2.1.0
|
||||
dev: false
|
||||
|
||||
/available-typed-arrays/1.0.5:
|
||||
resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -814,7 +851,6 @@ packages:
|
||||
/cac/6.7.14:
|
||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/call-bind/1.0.2:
|
||||
resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
|
||||
@@ -834,7 +870,6 @@ packages:
|
||||
ansi-styles: 3.2.1
|
||||
escape-string-regexp: 1.0.5
|
||||
supports-color: 5.5.0
|
||||
dev: true
|
||||
|
||||
/chalk/4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
@@ -856,15 +891,19 @@ packages:
|
||||
resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==}
|
||||
dev: true
|
||||
|
||||
/chatgpt/4.4.0:
|
||||
resolution: {integrity: sha512-rS1F423hR5marIB7hvZ4KyczK9oFloWY+AR4WKIt+HLyiRgOOWv6V4olu1HvaoOayc5+A/N2WwX0SzuU1rI9Rg==}
|
||||
engines: {node: '>=18'}
|
||||
/chatgpt/4.7.2:
|
||||
resolution: {integrity: sha512-c5CNqvB98IMEz/Byopwu5FlXGS3w/3iNiZITdDlcZLue4VSjEfzMRWrOrdGidzcE+ud2My6nO8/sSnY7W04WJA==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
conf: 11.0.1
|
||||
eventsource-parser: 0.0.5
|
||||
gpt-3-encoder: 1.1.4
|
||||
gpt3-tokenizer: 1.1.5
|
||||
keyv: 4.5.2
|
||||
p-timeout: 6.1.1
|
||||
quick-lru: 6.1.1
|
||||
read-pkg-up: 9.1.0
|
||||
uuid: 9.0.0
|
||||
dev: false
|
||||
|
||||
@@ -899,7 +938,6 @@ packages:
|
||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||
dependencies:
|
||||
color-name: 1.1.3
|
||||
dev: true
|
||||
|
||||
/color-convert/2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
@@ -910,7 +948,6 @@ packages:
|
||||
|
||||
/color-name/1.1.3:
|
||||
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
|
||||
dev: true
|
||||
|
||||
/color-name/1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
@@ -925,6 +962,20 @@ packages:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
dev: true
|
||||
|
||||
/conf/11.0.1:
|
||||
resolution: {integrity: sha512-WlLiQboEjKx0bYx2IIRGedBgNjLAxtwPaCSnsjWPST5xR0DB4q8lcsO/bEH9ZRYNcj63Y9vj/JG/5Fg6uWzI0Q==}
|
||||
engines: {node: '>=14.16'}
|
||||
dependencies:
|
||||
ajv: 8.12.0
|
||||
ajv-formats: 2.1.1
|
||||
atomically: 2.0.1
|
||||
debounce-fn: 5.1.2
|
||||
dot-prop: 7.2.0
|
||||
env-paths: 3.0.0
|
||||
json-schema-typed: 8.0.1
|
||||
semver: 7.3.8
|
||||
dev: false
|
||||
|
||||
/content-disposition/0.5.4:
|
||||
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -961,6 +1012,18 @@ packages:
|
||||
hasBin: 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:
|
||||
resolution: {integrity: sha512-Sr4SdOZ4vw6eQDvPYNxHogvrxmCIld/VenC5JbNrFwMiwd7lY/Z18ZFfo+EWNG4DD9nFlAujWAo/wGuOPHmy5A==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
mimic-fn: 4.0.0
|
||||
dev: false
|
||||
|
||||
/debug/2.6.9:
|
||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||
peerDependencies:
|
||||
@@ -993,7 +1056,6 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
dev: true
|
||||
|
||||
/deep-is/0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
@@ -1065,6 +1127,13 @@ packages:
|
||||
domhandler: 5.0.3
|
||||
dev: true
|
||||
|
||||
/dot-prop/7.2.0:
|
||||
resolution: {integrity: sha512-Ol/IPXUARn9CSbkrdV4VJo7uCy1I3VuSiWCaFSg+8BdUOzF9n3jefIpcgAydvUZbTdEBZs2vEiTiS9m61ssiDA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dependencies:
|
||||
type-fest: 2.19.0
|
||||
dev: false
|
||||
|
||||
/dotenv/16.0.3:
|
||||
resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -1084,11 +1153,15 @@ packages:
|
||||
engines: {node: '>=0.12'}
|
||||
dev: true
|
||||
|
||||
/env-paths/3.0.0:
|
||||
resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dev: false
|
||||
|
||||
/error-ex/1.3.2:
|
||||
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
|
||||
dependencies:
|
||||
is-arrayish: 0.2.1
|
||||
dev: true
|
||||
|
||||
/es-abstract/1.21.1:
|
||||
resolution: {integrity: sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==}
|
||||
@@ -1189,7 +1262,6 @@ packages:
|
||||
/escape-string-regexp/1.0.5:
|
||||
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
dev: true
|
||||
|
||||
/escape-string-regexp/4.0.0:
|
||||
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||
@@ -1667,7 +1739,6 @@ packages:
|
||||
|
||||
/fast-deep-equal/3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
dev: true
|
||||
|
||||
/fast-glob/3.2.12:
|
||||
resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
|
||||
@@ -1694,6 +1765,14 @@ packages:
|
||||
reusify: 1.0.4
|
||||
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:
|
||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||
engines: {node: ^10.12.0 || >=12.0.0}
|
||||
@@ -1739,6 +1818,14 @@ packages:
|
||||
path-exists: 4.0.0
|
||||
dev: true
|
||||
|
||||
/find-up/6.3.0:
|
||||
resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dependencies:
|
||||
locate-path: 7.2.0
|
||||
path-exists: 5.0.0
|
||||
dev: false
|
||||
|
||||
/flat-cache/3.0.4:
|
||||
resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}
|
||||
engines: {node: ^10.12.0 || >=12.0.0}
|
||||
@@ -1757,6 +1844,13 @@ packages:
|
||||
is-callable: 1.2.7
|
||||
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:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -1887,8 +1981,11 @@ packages:
|
||||
get-intrinsic: 1.2.0
|
||||
dev: true
|
||||
|
||||
/gpt-3-encoder/1.1.4:
|
||||
resolution: {integrity: sha512-fSQRePV+HUAhCn7+7HL7lNIXNm6eaFWFbNLOOGtmSJ0qJycyQvj60OvRlH7mee8xAMjBDNRdMXlMwjAbMTDjkg==}
|
||||
/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:
|
||||
@@ -1902,7 +1999,6 @@ packages:
|
||||
/has-flag/3.0.0:
|
||||
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/has-flag/4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
@@ -1941,6 +2037,13 @@ packages:
|
||||
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
||||
dev: true
|
||||
|
||||
/hosted-git-info/4.1.0:
|
||||
resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
dev: false
|
||||
|
||||
/htmlparser2/8.0.1:
|
||||
resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==}
|
||||
dependencies:
|
||||
@@ -2015,6 +2118,10 @@ packages:
|
||||
side-channel: 1.0.4
|
||||
dev: true
|
||||
|
||||
/ip/2.0.0:
|
||||
resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==}
|
||||
dev: false
|
||||
|
||||
/ipaddr.js/1.9.1:
|
||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
@@ -2041,7 +2148,6 @@ packages:
|
||||
|
||||
/is-arrayish/0.2.1:
|
||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||
dev: true
|
||||
|
||||
/is-bigint/1.0.4:
|
||||
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
|
||||
@@ -2080,7 +2186,6 @@ packages:
|
||||
resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
|
||||
dependencies:
|
||||
has: 1.0.3
|
||||
dev: true
|
||||
|
||||
/is-date-object/1.0.5:
|
||||
resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
|
||||
@@ -2205,7 +2310,6 @@ packages:
|
||||
|
||||
/js-tokens/4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
dev: true
|
||||
|
||||
/js-yaml/4.1.0:
|
||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||
@@ -2231,12 +2335,19 @@ packages:
|
||||
|
||||
/json-parse-even-better-errors/2.3.1:
|
||||
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
||||
dev: true
|
||||
|
||||
/json-schema-traverse/0.4.1:
|
||||
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
||||
dev: true
|
||||
|
||||
/json-schema-traverse/1.0.0:
|
||||
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||
dev: false
|
||||
|
||||
/json-schema-typed/8.0.1:
|
||||
resolution: {integrity: sha512-XQmWYj2Sm4kn4WeTYvmpKEbyPsL7nBsb647c7pMe6l02/yx2+Jfc4dT6UZkEXnIUb5LhD55r2HPsJ1milQ4rDg==}
|
||||
dev: false
|
||||
|
||||
/json-stable-stringify-without-jsonify/1.0.1:
|
||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||
dev: true
|
||||
@@ -2279,7 +2390,6 @@ packages:
|
||||
|
||||
/lines-and-columns/1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
dev: true
|
||||
|
||||
/load-tsconfig/0.2.3:
|
||||
resolution: {integrity: sha512-iyT2MXws+dc2Wi6o3grCFtGXpeMvHmJqS27sMPGtV2eUu4PeFnG+33I8BlFK1t1NWMjOpcx9bridn5yxLDX2gQ==}
|
||||
@@ -2305,6 +2415,13 @@ packages:
|
||||
p-locate: 5.0.0
|
||||
dev: true
|
||||
|
||||
/locate-path/7.2.0:
|
||||
resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dependencies:
|
||||
p-locate: 6.0.0
|
||||
dev: false
|
||||
|
||||
/lodash.merge/4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
dev: true
|
||||
@@ -2322,7 +2439,6 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
yallist: 4.0.0
|
||||
dev: true
|
||||
|
||||
/mdast-util-from-markdown/0.8.5:
|
||||
resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==}
|
||||
@@ -2403,6 +2519,11 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/mimic-fn/4.0.0:
|
||||
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/min-indent/1.0.1:
|
||||
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -2424,7 +2545,6 @@ packages:
|
||||
|
||||
/ms/2.1.2:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||
dev: true
|
||||
|
||||
/ms/2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
@@ -2450,6 +2570,11 @@ packages:
|
||||
engines: {node: '>= 0.6'}
|
||||
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:
|
||||
resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
@@ -2462,6 +2587,15 @@ packages:
|
||||
whatwg-url: 5.0.0
|
||||
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:
|
||||
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
|
||||
dependencies:
|
||||
@@ -2471,6 +2605,16 @@ packages:
|
||||
validate-npm-package-license: 3.0.4
|
||||
dev: true
|
||||
|
||||
/normalize-package-data/3.0.3:
|
||||
resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
hosted-git-info: 4.1.0
|
||||
is-core-module: 2.11.0
|
||||
semver: 7.3.8
|
||||
validate-npm-package-license: 3.0.4
|
||||
dev: false
|
||||
|
||||
/normalize-path/3.0.0:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -2567,6 +2711,13 @@ packages:
|
||||
yocto-queue: 0.1.0
|
||||
dev: true
|
||||
|
||||
/p-limit/4.0.0:
|
||||
resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dependencies:
|
||||
yocto-queue: 1.0.0
|
||||
dev: false
|
||||
|
||||
/p-locate/4.1.0:
|
||||
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2581,6 +2732,13 @@ packages:
|
||||
p-limit: 3.1.0
|
||||
dev: true
|
||||
|
||||
/p-locate/6.0.0:
|
||||
resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dependencies:
|
||||
p-limit: 4.0.0
|
||||
dev: false
|
||||
|
||||
/p-timeout/6.1.1:
|
||||
resolution: {integrity: sha512-yqz2Wi4fiFRpMmK0L2pGAU49naSUaP23fFIQL2Y6YT+qDGPoFwpvgQM/wzc6F8JoenUkIlAFa4Ql7NguXBxI7w==}
|
||||
engines: {node: '>=14.16'}
|
||||
@@ -2617,7 +2775,6 @@ packages:
|
||||
error-ex: 1.3.2
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
lines-and-columns: 1.2.4
|
||||
dev: true
|
||||
|
||||
/parseurl/1.3.3:
|
||||
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||
@@ -2629,6 +2786,11 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/path-exists/5.0.0:
|
||||
resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dev: false
|
||||
|
||||
/path-is-absolute/1.0.1:
|
||||
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -2707,7 +2869,6 @@ packages:
|
||||
/punycode/2.3.0:
|
||||
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/qs/6.11.0:
|
||||
resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
|
||||
@@ -2749,6 +2910,15 @@ packages:
|
||||
type-fest: 0.8.1
|
||||
dev: true
|
||||
|
||||
/read-pkg-up/9.1.0:
|
||||
resolution: {integrity: sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dependencies:
|
||||
find-up: 6.3.0
|
||||
read-pkg: 7.1.0
|
||||
type-fest: 2.19.0
|
||||
dev: false
|
||||
|
||||
/read-pkg/5.2.0:
|
||||
resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2759,6 +2929,16 @@ packages:
|
||||
type-fest: 0.6.0
|
||||
dev: true
|
||||
|
||||
/read-pkg/7.1.0:
|
||||
resolution: {integrity: sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==}
|
||||
engines: {node: '>=12.20'}
|
||||
dependencies:
|
||||
'@types/normalize-package-data': 2.4.1
|
||||
normalize-package-data: 3.0.3
|
||||
parse-json: 5.2.0
|
||||
type-fest: 2.19.0
|
||||
dev: false
|
||||
|
||||
/readdirp/3.6.0:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
@@ -2792,6 +2972,11 @@ packages:
|
||||
jsesc: 0.5.0
|
||||
dev: true
|
||||
|
||||
/require-from-string/2.0.2:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/resolve-from/4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -2881,7 +3066,6 @@ packages:
|
||||
hasBin: true
|
||||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
dev: true
|
||||
|
||||
/send/0.18.0:
|
||||
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
|
||||
@@ -2948,6 +3132,30 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
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:
|
||||
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
||||
dependencies:
|
||||
@@ -2972,22 +3180,18 @@ packages:
|
||||
dependencies:
|
||||
spdx-expression-parse: 3.0.1
|
||||
spdx-license-ids: 3.0.12
|
||||
dev: true
|
||||
|
||||
/spdx-exceptions/2.3.0:
|
||||
resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==}
|
||||
dev: true
|
||||
|
||||
/spdx-expression-parse/3.0.1:
|
||||
resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}
|
||||
dependencies:
|
||||
spdx-exceptions: 2.3.0
|
||||
spdx-license-ids: 3.0.12
|
||||
dev: true
|
||||
|
||||
/spdx-license-ids/3.0.12:
|
||||
resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==}
|
||||
dev: true
|
||||
|
||||
/statuses/2.0.1:
|
||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||
@@ -3039,6 +3243,10 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/stubborn-fs/1.2.4:
|
||||
resolution: {integrity: sha512-KRa4nIRJ8q6uApQbPwYZVhOof8979fw4xbajBWa5kPJFa4nyY3aFaMWVyIVCDnkNCCG/3HLipUZ4QaNlYsmX1w==}
|
||||
dev: false
|
||||
|
||||
/sucrase/3.29.0:
|
||||
resolution: {integrity: sha512-bZPAuGA5SdFHuzqIhTAqt9fvNEo9rESqXIG3oiKdF8K4UmkQxC4KlNL3lVyAErXp+mPvUqZ5l13qx6TrDIGf3A==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3057,7 +3265,6 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
has-flag: 3.0.0
|
||||
dev: true
|
||||
|
||||
/supports-color/7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
@@ -3211,6 +3418,11 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/type-fest/2.19.0:
|
||||
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
|
||||
engines: {node: '>=12.20'}
|
||||
dev: false
|
||||
|
||||
/type-is/1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -3257,7 +3469,6 @@ packages:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
dependencies:
|
||||
punycode: 2.3.0
|
||||
dev: true
|
||||
|
||||
/util-deprecate/1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
@@ -3278,7 +3489,6 @@ packages:
|
||||
dependencies:
|
||||
spdx-correct: 3.1.1
|
||||
spdx-expression-parse: 3.0.1
|
||||
dev: true
|
||||
|
||||
/vary/1.1.2:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
@@ -3303,6 +3513,11 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/web-streams-polyfill/3.2.1:
|
||||
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
|
||||
engines: {node: '>= 8'}
|
||||
dev: false
|
||||
|
||||
/webidl-conversions/3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
dev: false
|
||||
@@ -3330,6 +3545,10 @@ packages:
|
||||
webidl-conversions: 4.0.2
|
||||
dev: true
|
||||
|
||||
/when-exit/2.1.0:
|
||||
resolution: {integrity: sha512-H85ulNwUBU1e6PGxkWUDgxnbohSXD++ah6Xw1VHAN7CtypcbZaC4aYjQ+C2PMVaDkURDuOinNAT+Lnz3utWXxQ==}
|
||||
dev: false
|
||||
|
||||
/which-boxed-primitive/1.0.2:
|
||||
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
|
||||
dependencies:
|
||||
@@ -3376,7 +3595,6 @@ packages:
|
||||
|
||||
/yallist/4.0.0:
|
||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
dev: true
|
||||
|
||||
/yaml-eslint-parser/1.1.0:
|
||||
resolution: {integrity: sha512-b464Q1fYiX1oYx2kE8k4mEp6S9Prk+tfDsY/IPxQ0FCjEuj3AKko5Skf3/yQJeYTTDyjDE+aWIJemnv29HvEWQ==}
|
||||
@@ -3401,3 +3619,8 @@ packages:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/yocto-queue/1.0.0:
|
||||
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
|
||||
engines: {node: '>=12.20'}
|
||||
dev: false
|
||||
|
@@ -1,27 +1,61 @@
|
||||
import * as dotenv from 'dotenv'
|
||||
import 'isomorphic-fetch'
|
||||
import type { ChatGPTAPI, SendMessageOptions } from 'chatgpt'
|
||||
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'
|
||||
|
||||
export interface ChatContext {
|
||||
conversationId?: string
|
||||
parentMessageId?: string
|
||||
}
|
||||
import type { ApiModel, ChatContext, ChatGPTAPIOptions, ChatGPTUnofficialProxyAPIOptions, ModelConfig } from './types'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const apiKey = process.env.OPENAI_API_KEY
|
||||
const timeoutMs: number = !isNaN(+process.env.TIMEOUT_MS) ? +process.env.TIMEOUT_MS : 30 * 1000
|
||||
|
||||
if (apiKey === undefined)
|
||||
throw new Error('OPENAI_API_KEY is not defined')
|
||||
let apiModel: ApiModel
|
||||
|
||||
let api: ChatGPTAPI
|
||||
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')
|
||||
api = new ChatGPTAPI({ apiKey: process.env.OPENAI_API_KEY })
|
||||
|
||||
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(
|
||||
@@ -32,7 +66,7 @@ async function chatReply(
|
||||
return sendResponse({ type: 'Fail', message: 'Message is empty' })
|
||||
|
||||
try {
|
||||
let options: SendMessageOptions = { timeoutMs: 30 * 1000 }
|
||||
let options: SendMessageOptions = { timeoutMs }
|
||||
|
||||
if (lastContext)
|
||||
options = { ...lastContext }
|
||||
@@ -46,4 +80,46 @@ async function chatReply(
|
||||
}
|
||||
}
|
||||
|
||||
export { chatReply }
|
||||
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 }
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import express from 'express'
|
||||
import type { ChatContext } from './chatgpt'
|
||||
import { chatReply } from './chatgpt'
|
||||
import type { ChatContext, ChatMessage } from './chatgpt'
|
||||
import { chatConfig, chatReply, chatReplyProcess } from './chatgpt'
|
||||
|
||||
const app = express()
|
||||
const router = express.Router()
|
||||
@@ -26,6 +26,35 @@ 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) => {
|
||||
try {
|
||||
const response = await chatConfig()
|
||||
res.send(response)
|
||||
}
|
||||
catch (error) {
|
||||
res.send(error)
|
||||
}
|
||||
})
|
||||
|
||||
app.use('', router)
|
||||
app.use('/api', router)
|
||||
|
||||
|
30
service/src/types.ts
Normal 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
|
@@ -1,10 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { NConfigProvider } from 'naive-ui'
|
||||
import { NaiveProvider } from '@/components/common'
|
||||
import { useTheme } from '@/hooks/useTheme'
|
||||
|
||||
const { theme, themeOverrides } = useTheme()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NConfigProvider class="h-full">
|
||||
<NConfigProvider
|
||||
class="h-full"
|
||||
:theme="theme"
|
||||
:theme-overrides="themeOverrides"
|
||||
>
|
||||
<NaiveProvider>
|
||||
<RouterView />
|
||||
</NaiveProvider>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { GenericAbortSignal } from 'axios'
|
||||
import type { AxiosProgressEvent, GenericAbortSignal } from 'axios'
|
||||
import { post } from '@/utils/request'
|
||||
|
||||
export function fetchChatAPI<T = any>(
|
||||
@@ -12,3 +12,24 @@ export function fetchChatAPI<T = any>(
|
||||
signal,
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchChatConfig<T = any>() {
|
||||
return post<T>({
|
||||
url: '/config',
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchChatAPIProcess<T = any>(
|
||||
params: {
|
||||
prompt: string
|
||||
options?: { conversationId?: string; parentMessageId?: string }
|
||||
signal?: GenericAbortSignal
|
||||
onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void },
|
||||
) {
|
||||
return post<T>({
|
||||
url: '/chat-process',
|
||||
data: { prompt: params.prompt, options: params.options },
|
||||
signal: params.signal,
|
||||
onDownloadProgress: params.onDownloadProgress,
|
||||
})
|
||||
}
|
||||
|
@@ -1,22 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
interface Props {
|
||||
reversal?: boolean
|
||||
error?: boolean
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-2 mt-2 rounded-md" :class="[reversal ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]']">
|
||||
<span v-highlight class="leading-relaxed whitespace-pre-wrap">
|
||||
<slot />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.hljs {
|
||||
background-color: #fff0 !important;
|
||||
}
|
||||
</style>
|
@@ -1,32 +0,0 @@
|
||||
<script setup lang='ts'>
|
||||
import Avatar from './Avatar.vue'
|
||||
import Text from './Text.vue'
|
||||
|
||||
interface Props {
|
||||
message?: string
|
||||
dateTime?: string
|
||||
reversal?: boolean
|
||||
error?: boolean
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full mb-6" :class="[{ 'flex-row-reverse': reversal }]">
|
||||
<div
|
||||
class="flex items-center justify-center rounded-full overflow-hidden w-[32px] h-[32px]"
|
||||
:class="[reversal ? 'ml-3' : 'mr-3']"
|
||||
>
|
||||
<Avatar :image="reversal" />
|
||||
</div>
|
||||
<div class="flex flex-col flex-1 text-sm" :class="[reversal ? 'items-end' : 'items-start']">
|
||||
<span class="text-xs text-[#b4bbc4]">
|
||||
{{ dateTime }}
|
||||
</span>
|
||||
<Text :reversal="reversal" :error="error">
|
||||
{{ message }}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@@ -1,26 +0,0 @@
|
||||
import { useHistoryStore } from '@/store'
|
||||
|
||||
export function useChat() {
|
||||
const historyStore = useHistoryStore()
|
||||
|
||||
function addChat(
|
||||
message: string,
|
||||
args?: { reversal?: boolean; error?: boolean; options?: Chat.ChatOptions },
|
||||
) {
|
||||
historyStore.addChat(
|
||||
{
|
||||
dateTime: new Date().toLocaleString(),
|
||||
message,
|
||||
reversal: args?.reversal ?? false,
|
||||
error: args?.error ?? false,
|
||||
options: args?.options ?? undefined,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
function clearChat() {
|
||||
historyStore.clearChat()
|
||||
}
|
||||
|
||||
return { addChat, clearChat }
|
||||
}
|
@@ -1,180 +0,0 @@
|
||||
<script setup lang='ts'>
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import type { MessageReactive } from 'naive-ui'
|
||||
import { NButton, NInput, useMessage } from 'naive-ui'
|
||||
import { Message } from './components'
|
||||
import { Layout } from './layout'
|
||||
import { useChat } from './hooks/useChat'
|
||||
import { fetchChatAPI } from '@/api'
|
||||
import { HoverButton, SvgIcon } from '@/components/common'
|
||||
import { useHistoryStore } from '@/store'
|
||||
|
||||
let controller = new AbortController()
|
||||
|
||||
const ms = useMessage()
|
||||
|
||||
const historyStore = useHistoryStore()
|
||||
|
||||
let messageReactive: MessageReactive | null = null
|
||||
|
||||
const scrollRef = ref<HTMLDivElement>()
|
||||
|
||||
const { addChat, clearChat } = useChat()
|
||||
|
||||
const prompt = ref('')
|
||||
const loading = ref(false)
|
||||
|
||||
const currentActive = computed(() => historyStore.active)
|
||||
const heartbeat = computed(() => historyStore.heartbeat)
|
||||
|
||||
const list = computed<Chat.Chat[]>(() => historyStore.getCurrentChat)
|
||||
const chatList = computed<Chat.Chat[]>(() => list.value.filter(item => (!item.reversal && !item.error)))
|
||||
|
||||
async function handleSubmit() {
|
||||
if (loading.value)
|
||||
return
|
||||
|
||||
controller = new AbortController()
|
||||
|
||||
const message = prompt.value.trim()
|
||||
|
||||
if (!message || !message.length) {
|
||||
ms.warning('Please enter a message')
|
||||
return
|
||||
}
|
||||
|
||||
addMessage(message, { reversal: true })
|
||||
prompt.value = ''
|
||||
|
||||
let options: Chat.ChatOptions = {}
|
||||
const lastContext = chatList.value[chatList.value.length - 1]?.options
|
||||
|
||||
if (lastContext)
|
||||
options = { ...lastContext }
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
createMessage()
|
||||
const { data } = await fetchChatAPI(message, options, controller.signal)
|
||||
addMessage(data?.text ?? '', { options: { conversationId: data.conversationId, parentMessageId: data.id } })
|
||||
}
|
||||
catch (error: any) {
|
||||
if (error.message !== 'canceled')
|
||||
addMessage(`${error.message ?? 'Request failed, please try again later.'}`, { error: true })
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
removeMessage()
|
||||
}
|
||||
}
|
||||
|
||||
function handleEnter(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter')
|
||||
handleSubmit()
|
||||
}
|
||||
|
||||
function addMessage(
|
||||
message: string,
|
||||
args?: { reversal?: boolean; error?: boolean; options?: Chat.ChatOptions },
|
||||
) {
|
||||
addChat(message, args)
|
||||
scrollToBottom()
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
nextTick(() => scrollRef.value && (scrollRef.value.scrollTop = scrollRef.value.scrollHeight))
|
||||
}
|
||||
|
||||
function handleClear() {
|
||||
handleCancel()
|
||||
clearChat()
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
controller.abort()
|
||||
controller = new AbortController()
|
||||
loading.value = false
|
||||
removeMessage()
|
||||
}
|
||||
|
||||
function createMessage() {
|
||||
if (!messageReactive) {
|
||||
messageReactive = ms.loading('Thinking...', {
|
||||
duration: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function removeMessage() {
|
||||
if (messageReactive) {
|
||||
messageReactive.destroy()
|
||||
messageReactive = null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
scrollToBottom()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
handleCancel()
|
||||
})
|
||||
|
||||
watch(
|
||||
heartbeat,
|
||||
() => {
|
||||
handleCancel()
|
||||
scrollToBottom()
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
currentActive,
|
||||
(_, oldActive) => {
|
||||
if (oldActive !== null) {
|
||||
handleCancel()
|
||||
scrollToBottom()
|
||||
}
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Layout>
|
||||
<div class="flex flex-col h-full">
|
||||
<main class="flex-1 overflow-hidden">
|
||||
<div ref="scrollRef" class="h-full p-4 overflow-hidden overflow-y-auto">
|
||||
<template v-if="!list.length">
|
||||
<div class="flex items-center justify-center mt-4 text-center text-neutral-300">
|
||||
<SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
|
||||
<span>Aha~</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div>
|
||||
<Message
|
||||
v-for="(item, index) of list" :key="index" :date-time="item.dateTime" :message="item.message"
|
||||
:reversal="item.reversal" :error="item.error"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="p-4">
|
||||
<div class="flex items-center justify-between space-x-2">
|
||||
<HoverButton tooltip="Clear conversations">
|
||||
<span class="text-xl text-[#4f555e]" @click="handleClear">
|
||||
<SvgIcon icon="ri:delete-bin-line" />
|
||||
</span>
|
||||
</HoverButton>
|
||||
<NInput v-model:value="prompt" placeholder="Type a message..." @keypress="handleEnter" />
|
||||
<NButton type="primary" :disabled="loading" @click="handleSubmit">
|
||||
<template #icon>
|
||||
<SvgIcon icon="ri:send-plane-fill" />
|
||||
</template>
|
||||
</NButton>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</Layout>
|
||||
</template>
|
@@ -1,36 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
import { useAppStore, useHistoryStore } from '@/store'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const historyStore = useHistoryStore()
|
||||
|
||||
const collapsed = computed(() => appStore.siderCollapsed)
|
||||
|
||||
function handleAdd() {
|
||||
historyStore.addHistory({
|
||||
title: 'New Chat',
|
||||
isEdit: false,
|
||||
data: [],
|
||||
})
|
||||
}
|
||||
|
||||
function handleUpdateCollapsed() {
|
||||
appStore.setSiderCollapsed(!collapsed.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="fixed top-0 left-0 right-0 z-50 border-b bg-white/80 backdrop-blur">
|
||||
<div class="relative flex items-center justify-between px-4 h-14">
|
||||
<button class="flex items-center justify-center w-11 h-11" @click="handleUpdateCollapsed">
|
||||
<SvgIcon v-if="collapsed" class="text-2xl" icon="ri:align-justify" />
|
||||
<SvgIcon v-else class="text-2xl" icon="ri:align-right" />
|
||||
</button>
|
||||
<button class="flex items-center justify-center w-11 h-11" @click="handleAdd">
|
||||
<SvgIcon class="text-2xl" icon="ri:add-fill" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
@@ -1,3 +0,0 @@
|
||||
import Layout from './Layout.vue'
|
||||
|
||||
export { Layout }
|
@@ -1,79 +0,0 @@
|
||||
<script setup lang='ts'>
|
||||
import { ref } from 'vue'
|
||||
import { NInput, NScrollbar } from 'naive-ui'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
import { useHistoryStore } from '@/store'
|
||||
|
||||
const historyStore = useHistoryStore()
|
||||
|
||||
const dataSources = ref(historyStore.historyChat)
|
||||
|
||||
function handleSelect(index: number) {
|
||||
historyStore.chooseHistory(index)
|
||||
}
|
||||
|
||||
function handleEdit(index: number, isEdit: boolean, event?: MouseEvent) {
|
||||
historyStore.editHistory(index, isEdit)
|
||||
event?.stopPropagation()
|
||||
}
|
||||
|
||||
function handleRemove(index: number, event?: MouseEvent) {
|
||||
historyStore.removeHistory(index)
|
||||
event?.stopPropagation()
|
||||
}
|
||||
|
||||
function handleEnter(index: number, isEdit: boolean, event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
handleEdit(index, isEdit)
|
||||
event.stopPropagation()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NScrollbar class="px-4">
|
||||
<div class="flex flex-col gap-2 text-sm">
|
||||
<template v-if="!dataSources.length">
|
||||
<div class="flex flex-col items-center mt-4 text-center text-neutral-300">
|
||||
<SvgIcon icon="ri:inbox-line" class="mb-2 text-3xl" />
|
||||
<span>No history</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-for="(item, index) of dataSources" :key="index">
|
||||
<a
|
||||
class="relative flex items-center gap-3 px-3 py-3 break-all border rounded-md cursor-pointer hover:bg-neutral-100 group"
|
||||
:class="historyStore.active === index && ['border-[#4b9e5f]', 'bg-neutral-100', 'text-[#4b9e5f]', 'pr-14']"
|
||||
@click="handleSelect(index)"
|
||||
>
|
||||
<span>
|
||||
<SvgIcon icon="ri:message-3-line" />
|
||||
</span>
|
||||
<div class="relative flex-1 overflow-hidden break-all text-ellipsis whitespace-nowrap">
|
||||
<NInput
|
||||
v-if="item.isEdit" v-model:value="item.title" size="tiny"
|
||||
@keypress="handleEnter(index, false, $event)"
|
||||
/>
|
||||
<span v-else>{{ item.title }}</span>
|
||||
</div>
|
||||
<div v-if="historyStore.active === index" class="absolute z-10 flex visible right-1">
|
||||
<template v-if="item.isEdit">
|
||||
<button class="p-1" @click="handleEdit(index, false, $event)">
|
||||
<SvgIcon icon="ri:save-line" />
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button class="p-1">
|
||||
<SvgIcon icon="ri:edit-line" @click="handleEdit(index, true, $event)" />
|
||||
</button>
|
||||
<button class="p-1" @click="handleRemove(index, $event)">
|
||||
<SvgIcon icon="ri:delete-bin-line" />
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</NScrollbar>
|
||||
</template>
|
@@ -1,3 +1 @@
|
||||
import Chat from './Chat/index.vue'
|
||||
|
||||
export { Chat }
|
||||
export { }
|
||||
|
@@ -12,7 +12,7 @@ function handleClick() {
|
||||
|
||||
<template>
|
||||
<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"
|
||||
>
|
||||
<slot />
|
||||
|
77
src/components/common/Setting/index.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<script setup lang='ts'>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { NCard, NModal } from 'naive-ui'
|
||||
import pkg from '../../../../package.json'
|
||||
import { fetchChatConfig } from '@/api'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
interface Emit {
|
||||
(e: 'update:visible', visible: boolean): void
|
||||
}
|
||||
|
||||
interface ConfigState {
|
||||
timeoutMs?: number
|
||||
reverseProxy?: string
|
||||
apiModel?: string
|
||||
socksProxy?: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const show = computed({
|
||||
get() {
|
||||
return props.visible
|
||||
},
|
||||
set(visible: boolean) {
|
||||
emit('update:visible', visible)
|
||||
},
|
||||
})
|
||||
|
||||
const config = ref<ConfigState>()
|
||||
|
||||
async function fetchConfig() {
|
||||
try {
|
||||
const { data } = await fetchChatConfig<ConfigState>()
|
||||
config.value = data
|
||||
}
|
||||
catch (error) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
if (val)
|
||||
fetchConfig()
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal v-model:show="show" style="width: 80%; max-width: 460px;">
|
||||
<NCard>
|
||||
<div class="space-y-4">
|
||||
<h2 class="text-xl font-bold text-center">
|
||||
Version - {{ pkg.version }}
|
||||
</h2>
|
||||
<hr>
|
||||
<p>
|
||||
此项目开源于
|
||||
<a class="text-blue-600" href="https://github.com/Chanzhaoyu/chatgpt-web" target="_blank">Github</a>
|
||||
如果你觉得此项目对你有帮助,请帮我点个 Star,谢谢!
|
||||
</p>
|
||||
<hr>
|
||||
<p>API方式:{{ config?.apiModel ?? '-' }}</p>
|
||||
<p>反向代理:{{ config?.reverseProxy ?? '-' }}</p>
|
||||
<p>超时时间:{{ config?.timeoutMs ?? '-' }}</p>
|
||||
<p>Socks代理:{{ config?.socksProxy ?? '-' }}</p>
|
||||
</div>
|
||||
</NCard>
|
||||
</NModal>
|
||||
</template>
|
@@ -2,5 +2,6 @@ import HoverButton from './HoverButton/index.vue'
|
||||
import NaiveProvider from './NaiveProvider/index.vue'
|
||||
import SvgIcon from './SvgIcon/index.vue'
|
||||
import UserAvatar from './UserAvatar/index.vue'
|
||||
import Setting from './Setting/index.vue'
|
||||
|
||||
export { HoverButton, NaiveProvider, SvgIcon, UserAvatar }
|
||||
export { HoverButton, NaiveProvider, SvgIcon, UserAvatar, Setting }
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="text-center text-neutral-500">
|
||||
<span>❤️ Star on</span>
|
||||
<div class="text-neutral-400">
|
||||
<span>Star on</span>
|
||||
<a href="https://github.com/Chanzhaoyu/chatgpt-bot" target="_blank" class="text-blue-500">
|
||||
GitHub
|
||||
</a>
|
||||
|
@@ -1,13 +1,21 @@
|
||||
import type { App, Directive } from 'vue'
|
||||
import hljs from 'highlight.js'
|
||||
import { includeCode } from '@/utils/format'
|
||||
|
||||
const regexp = /^(?:\s{4}|\t).+/gm
|
||||
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) {
|
||||
if (el.textContent?.indexOf(' = ') !== -1 || el.textContent.match(regexp))
|
||||
hljs.highlightBlock(el)
|
||||
highlightCode(el)
|
||||
},
|
||||
updated(el: HTMLElement) {
|
||||
highlightCode(el)
|
||||
},
|
||||
}
|
||||
|
||||
|
43
src/hooks/useTheme.ts
Normal 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,5 +1,7 @@
|
||||
import 'highlight.js/styles/xcode.css'
|
||||
import '@/styles/global.css'
|
||||
import '@/styles/lib/tailwind.css'
|
||||
import '@/styles/lib/github-markdown.less'
|
||||
import '@/styles/global.less'
|
||||
|
||||
/** Tailwind's Preflight Style Override */
|
||||
function naiveStyleOverride() {
|
||||
|
@@ -1,17 +1,26 @@
|
||||
import type { App } from 'vue'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import { ChatLayout } from '@/views/chat/layout'
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: () => import('@/views/home/index.vue'),
|
||||
name: 'Root',
|
||||
component: ChatLayout,
|
||||
redirect: '/chat',
|
||||
children: [
|
||||
{
|
||||
path: '/chat/:uuid?',
|
||||
name: 'Chat',
|
||||
component: () => import('@/views/chat/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||
})
|
||||
|
@@ -2,19 +2,22 @@ import { ss } from '@/utils/storage'
|
||||
|
||||
const LOCAL_NAME = 'appSetting'
|
||||
|
||||
export type Theme = 'light' | 'dark' | 'auto'
|
||||
|
||||
export interface AppState {
|
||||
siderCollapsed: boolean
|
||||
theme: Theme
|
||||
}
|
||||
|
||||
export function defaultSetting() {
|
||||
return { siderCollapsed: false }
|
||||
export function defaultSetting(): AppState {
|
||||
return { siderCollapsed: false, theme: 'light' }
|
||||
}
|
||||
|
||||
export function getLocalSetting() {
|
||||
export function getLocalSetting(): AppState {
|
||||
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)
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { AppState } from './helper'
|
||||
import type { AppState, Theme } from './helper'
|
||||
import { getLocalSetting, setLocalSetting } from './helper'
|
||||
|
||||
export const useAppStore = defineStore('app-store', {
|
||||
@@ -7,6 +7,15 @@ export const useAppStore = defineStore('app-store', {
|
||||
actions: {
|
||||
setSiderCollapsed(collapsed: boolean) {
|
||||
this.siderCollapsed = collapsed
|
||||
this.recordState()
|
||||
},
|
||||
|
||||
setTheme(theme: Theme) {
|
||||
this.theme = theme
|
||||
this.recordState()
|
||||
},
|
||||
|
||||
recordState() {
|
||||
setLocalSetting(this.$state)
|
||||
},
|
||||
},
|
||||
|
17
src/store/modules/chat/helper.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ss } from '@/utils/storage'
|
||||
|
||||
const LOCAL_NAME = 'chatStorage'
|
||||
|
||||
export function defaultState(): Chat.ChatState {
|
||||
const uuid = Date.now()
|
||||
return { active: uuid, history: [{ uuid, title: 'New Chat', isEdit: false }], chat: [{ uuid, data: [] }] }
|
||||
}
|
||||
|
||||
export function getLocalState(): Chat.ChatState {
|
||||
const localState = ss.get(LOCAL_NAME)
|
||||
return localState ?? defaultState()
|
||||
}
|
||||
|
||||
export function setLocalState(state: Chat.ChatState) {
|
||||
ss.set(LOCAL_NAME, state)
|
||||
}
|
173
src/store/modules/chat/index.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { getLocalState, setLocalState } from './helper'
|
||||
import { router } from '@/router'
|
||||
|
||||
export const useChatStore = defineStore('chat-store', {
|
||||
state: (): Chat.ChatState => getLocalState(),
|
||||
|
||||
getters: {
|
||||
getChatHistoryByCurrentActive(state: Chat.ChatState) {
|
||||
const index = state.history.findIndex(item => item.uuid === state.active)
|
||||
if (index !== -1)
|
||||
return state.history[index]
|
||||
return null
|
||||
},
|
||||
|
||||
getChatByUuid(state: Chat.ChatState) {
|
||||
return (uuid?: number) => {
|
||||
if (uuid)
|
||||
return state.chat.find(item => item.uuid === uuid)?.data ?? []
|
||||
return state.chat.find(item => item.uuid === state.active)?.data ?? []
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
addHistory(history: Chat.History, chatData: Chat.Chat[] = []) {
|
||||
this.history.unshift(history)
|
||||
this.chat.unshift({ uuid: history.uuid, data: chatData })
|
||||
this.active = history.uuid
|
||||
this.reloadRoute(history.uuid)
|
||||
},
|
||||
|
||||
updateHistory(uuid: number, edit: Partial<Chat.History>) {
|
||||
const index = this.history.findIndex(item => item.uuid === uuid)
|
||||
if (index !== -1) {
|
||||
this.history[index] = { ...this.history[index], ...edit }
|
||||
this.recordState()
|
||||
}
|
||||
},
|
||||
|
||||
async deleteHistory(index: number) {
|
||||
this.history.splice(index, 1)
|
||||
this.chat.splice(index, 1)
|
||||
|
||||
if (this.history.length === 0) {
|
||||
this.active = null
|
||||
this.reloadRoute()
|
||||
return
|
||||
}
|
||||
|
||||
if (index > 0 && index <= this.history.length) {
|
||||
const uuid = this.history[index - 1].uuid
|
||||
this.active = uuid
|
||||
this.reloadRoute(uuid)
|
||||
return
|
||||
}
|
||||
|
||||
if (index === 0) {
|
||||
if (this.history.length > 0) {
|
||||
const uuid = this.history[0].uuid
|
||||
this.active = uuid
|
||||
this.reloadRoute(uuid)
|
||||
}
|
||||
}
|
||||
|
||||
if (index > this.history.length) {
|
||||
const uuid = this.history[this.history.length - 1].uuid
|
||||
this.active = uuid
|
||||
this.reloadRoute(uuid)
|
||||
}
|
||||
},
|
||||
|
||||
async setActive(uuid: number) {
|
||||
this.active = uuid
|
||||
return await this.reloadRoute(uuid)
|
||||
},
|
||||
|
||||
getChatByUuidAndIndex(uuid: number, index: number) {
|
||||
if (!uuid || uuid === 0) {
|
||||
if (this.chat.length)
|
||||
return this.chat[0].data[index]
|
||||
return null
|
||||
}
|
||||
const chatIndex = this.chat.findIndex(item => item.uuid === uuid)
|
||||
if (chatIndex !== -1)
|
||||
return this.chat[chatIndex].data[index]
|
||||
return null
|
||||
},
|
||||
|
||||
addChatByUuid(uuid: number, chat: Chat.Chat) {
|
||||
if (!uuid || uuid === 0) {
|
||||
if (this.history.length === 0) {
|
||||
const uuid = Date.now()
|
||||
this.history.push({ uuid, title: chat.text, isEdit: false })
|
||||
this.chat.push({ uuid, data: [chat] })
|
||||
this.active = uuid
|
||||
this.recordState()
|
||||
}
|
||||
else {
|
||||
this.chat[0].data.push(chat)
|
||||
if (this.history[0].title === 'New Chat')
|
||||
this.history[0].title = chat.text
|
||||
this.recordState()
|
||||
}
|
||||
}
|
||||
|
||||
const index = this.chat.findIndex(item => item.uuid === uuid)
|
||||
if (index !== -1) {
|
||||
this.chat[index].data.push(chat)
|
||||
if (this.history[index].title === 'New Chat')
|
||||
this.history[index].title = chat.text
|
||||
this.recordState()
|
||||
}
|
||||
},
|
||||
|
||||
updateChatByUuid(uuid: number, index: number, chat: Chat.Chat) {
|
||||
if (!uuid || uuid === 0) {
|
||||
if (this.chat.length) {
|
||||
this.chat[0].data[index] = chat
|
||||
this.recordState()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const chatIndex = this.chat.findIndex(item => item.uuid === uuid)
|
||||
if (chatIndex !== -1) {
|
||||
this.chat[chatIndex].data[index] = chat
|
||||
this.recordState()
|
||||
}
|
||||
},
|
||||
|
||||
deleteChatByUuid(uuid: number, index: number) {
|
||||
if (!uuid || uuid === 0) {
|
||||
if (this.chat.length) {
|
||||
this.chat[0].data.splice(index, 1)
|
||||
this.recordState()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const chatIndex = this.chat.findIndex(item => item.uuid === uuid)
|
||||
if (chatIndex !== -1) {
|
||||
this.chat[chatIndex].data.splice(index, 1)
|
||||
this.recordState()
|
||||
}
|
||||
},
|
||||
|
||||
clearChatByUuid(uuid: number) {
|
||||
if (!uuid || uuid === 0) {
|
||||
if (this.chat.length) {
|
||||
this.chat[0].data = []
|
||||
this.recordState()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const index = this.chat.findIndex(item => item.uuid === uuid)
|
||||
if (index !== -1) {
|
||||
this.chat[index].data = []
|
||||
this.recordState()
|
||||
}
|
||||
},
|
||||
|
||||
async reloadRoute(uuid?: number) {
|
||||
this.recordState()
|
||||
await router.push({ name: 'Chat', params: { uuid } })
|
||||
},
|
||||
|
||||
recordState() {
|
||||
setLocalState(this.$state)
|
||||
},
|
||||
},
|
||||
})
|
@@ -1,22 +0,0 @@
|
||||
import { ss } from '@/utils/storage'
|
||||
|
||||
const LOCAL_NAME = 'historyChat'
|
||||
|
||||
export interface HistoryState {
|
||||
historyChat: Chat.HistoryChat[]
|
||||
active: number | null
|
||||
heartbeat: boolean
|
||||
}
|
||||
|
||||
export function defaultSetting() {
|
||||
return { historyChat: [], active: null, heartbeat: false }
|
||||
}
|
||||
|
||||
export function getLocalHistory() {
|
||||
const localSetting: HistoryState | undefined = ss.get(LOCAL_NAME)
|
||||
return localSetting ?? defaultSetting()
|
||||
}
|
||||
|
||||
export function setLocalHistory(data: HistoryState) {
|
||||
ss.set(LOCAL_NAME, data)
|
||||
}
|
@@ -1,83 +0,0 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { HistoryState } from './helper'
|
||||
import { getLocalHistory, setLocalHistory } from './helper'
|
||||
|
||||
export const useHistoryStore = defineStore('history-store', {
|
||||
state: (): HistoryState => getLocalHistory(),
|
||||
getters: {
|
||||
getCurrentHistory(state): Chat.HistoryChat {
|
||||
if (state.historyChat.length) {
|
||||
if (state.active === null || state.active >= state.historyChat.length || state.active < 0)
|
||||
state.active = 0
|
||||
return state.historyChat[state.active] ?? { title: '', isEdit: false, data: [] }
|
||||
}
|
||||
state.active = null
|
||||
return { title: '', isEdit: false, data: [] }
|
||||
},
|
||||
getCurrentChat(): Chat.Chat[] {
|
||||
return this.getCurrentHistory.data ?? []
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
addChat(data: Chat.Chat) {
|
||||
if (this.active === null) {
|
||||
this.historyChat.push({ title: data.message, isEdit: false, data: [data] })
|
||||
this.active = this.historyChat.length - 1
|
||||
}
|
||||
else {
|
||||
if (this.historyChat[this.active].title === 'New Chat')
|
||||
this.historyChat[this.active].title = data.message
|
||||
|
||||
this.historyChat[this.active].data.push(data)
|
||||
}
|
||||
setLocalHistory(this.$state)
|
||||
},
|
||||
|
||||
clearChat() {
|
||||
if (this.active !== null) {
|
||||
this.historyChat[this.active].data = []
|
||||
setLocalHistory(this.$state)
|
||||
}
|
||||
},
|
||||
|
||||
addHistory(data: Chat.HistoryChat) {
|
||||
this.historyChat.push(data)
|
||||
this.active = this.historyChat.length - 1
|
||||
setLocalHistory(this.$state)
|
||||
},
|
||||
|
||||
editHistory(index: number, isEdit: boolean) {
|
||||
this.historyChat[index].isEdit = isEdit
|
||||
setLocalHistory(this.$state)
|
||||
},
|
||||
|
||||
removeHistory(index: number) {
|
||||
this.historyChat.splice(index, 1)
|
||||
|
||||
if (this.active === index) {
|
||||
if (this.historyChat.length === 0)
|
||||
this.active = null
|
||||
else if (this.active === this.historyChat.length)
|
||||
this.active = this.historyChat.length - 1
|
||||
}
|
||||
|
||||
if (this.historyChat.length === 0)
|
||||
this.active = null
|
||||
|
||||
this.toggleHeartbeat()
|
||||
|
||||
setLocalHistory(this.$state)
|
||||
},
|
||||
|
||||
chooseHistory(index: number) {
|
||||
if (this.active === index)
|
||||
return
|
||||
this.active = index
|
||||
setLocalHistory(this.$state)
|
||||
},
|
||||
|
||||
toggleHeartbeat() {
|
||||
this.heartbeat = !this.heartbeat
|
||||
},
|
||||
},
|
||||
})
|
@@ -1,2 +1,2 @@
|
||||
export * from './app'
|
||||
export * from './history'
|
||||
export * from './chat'
|
||||
|
5
src/styles/global.less
Normal file
@@ -0,0 +1,5 @@
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
1102
src/styles/lib/github-markdown.less
Normal file
@@ -1,9 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
53
src/typings/chat.d.ts
vendored
@@ -1,20 +1,45 @@
|
||||
declare namespace Chat{
|
||||
interface ChatOptions {
|
||||
declare namespace Chat {
|
||||
|
||||
interface Chat {
|
||||
dateTime: string
|
||||
text: string
|
||||
inversion?: boolean
|
||||
error?: boolean
|
||||
loading?: boolean
|
||||
conversationOptions?: ConversationRequest | null
|
||||
requestOptions: { prompt: string; options?: ConversationRequest | null }
|
||||
}
|
||||
|
||||
interface History {
|
||||
title: string
|
||||
isEdit: boolean
|
||||
uuid: number
|
||||
}
|
||||
|
||||
interface ChatState {
|
||||
active: number | null
|
||||
history: History[]
|
||||
chat: { uuid: number; data: Chat[] }[]
|
||||
}
|
||||
|
||||
interface ConversationRequest {
|
||||
conversationId?: string
|
||||
parentMessageId?: string
|
||||
}
|
||||
|
||||
interface Chat {
|
||||
dateTime: string
|
||||
message: string
|
||||
reversal?: boolean
|
||||
error?: boolean
|
||||
options?: ChatOptions
|
||||
}
|
||||
|
||||
interface HistoryChat {
|
||||
title: string
|
||||
isEdit: boolean
|
||||
data: Chat[]
|
||||
interface ConversationResponse {
|
||||
conversationId: string
|
||||
detail: {
|
||||
choices: { finish_reason: string; index: number; logprobs: any; text: string }[]
|
||||
created: number
|
||||
id: string
|
||||
model: string
|
||||
object: string
|
||||
usage: { completion_tokens: number; prompt_tokens: number; total_tokens: number }
|
||||
}
|
||||
id: string
|
||||
parentMessageId: string
|
||||
role: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
1
src/typings/env.d.ts
vendored
@@ -2,5 +2,6 @@
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_GLOB_API_URL: string;
|
||||
readonly VITE_GLOB_API_TIMEOUT: string;
|
||||
readonly VITE_APP_API_BASE_URL: string;
|
||||
}
|
||||
|
15
src/utils/format/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// 转义 HTML 字符
|
||||
export function encodeHTML(source: string) {
|
||||
return source
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
// 判断是否为代码块
|
||||
export function includeCode(text: string | null | undefined) {
|
||||
const regexp = /^(?:\s{4}|\t).+/gm
|
||||
return !!(text?.includes(' = ') || text?.match(regexp))
|
||||
}
|
@@ -2,7 +2,7 @@ import axios, { type AxiosResponse } from 'axios'
|
||||
|
||||
const service = axios.create({
|
||||
baseURL: import.meta.env.VITE_GLOB_API_URL,
|
||||
timeout: 30 * 1000,
|
||||
timeout: !isNaN(+import.meta.env.VITE_GLOB_API_TIMEOUT) ? Number(import.meta.env.VITE_GLOB_API_TIMEOUT) : 60 * 1000,
|
||||
})
|
||||
|
||||
service.interceptors.request.use(
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { AxiosResponse, GenericAbortSignal } from 'axios'
|
||||
import type { AxiosProgressEvent, AxiosResponse, GenericAbortSignal } from 'axios'
|
||||
import request from './axios'
|
||||
|
||||
export interface HttpOption {
|
||||
@@ -6,6 +6,7 @@ export interface HttpOption {
|
||||
data?: any
|
||||
method?: string
|
||||
headers?: any
|
||||
onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void
|
||||
signal?: GenericAbortSignal
|
||||
beforeRequest?: () => void
|
||||
afterRequest?: () => void
|
||||
@@ -17,9 +18,11 @@ export interface Response<T = any> {
|
||||
status: string
|
||||
}
|
||||
|
||||
function http<T = any>({ url, data, method, headers, signal, beforeRequest, afterRequest }: HttpOption) {
|
||||
function http<T = any>(
|
||||
{ url, data, method, headers, onDownloadProgress, signal, beforeRequest, afterRequest }: HttpOption,
|
||||
) {
|
||||
const successHandler = (res: AxiosResponse<Response<T>>) => {
|
||||
if (res.data.status === 'Success')
|
||||
if (res.data.status === 'Success' || typeof res.data === 'string')
|
||||
return res.data
|
||||
|
||||
return Promise.reject(res.data)
|
||||
@@ -37,17 +40,18 @@ function http<T = any>({ url, data, method, headers, signal, beforeRequest, afte
|
||||
const params = Object.assign(typeof data === 'function' ? data() : data ?? {}, {})
|
||||
|
||||
return method === 'GET'
|
||||
? request.get(url, { params, signal }).then(successHandler, failHandler)
|
||||
: request.post(url, params, { headers, signal }).then(successHandler, failHandler)
|
||||
? request.get(url, { params, signal, onDownloadProgress }).then(successHandler, failHandler)
|
||||
: request.post(url, params, { headers, signal, onDownloadProgress }).then(successHandler, failHandler)
|
||||
}
|
||||
|
||||
export function get<T = any>(
|
||||
{ url, data, method = 'GET', signal, beforeRequest, afterRequest }: HttpOption,
|
||||
{ url, data, method = 'GET', onDownloadProgress, signal, beforeRequest, afterRequest }: HttpOption,
|
||||
): Promise<Response<T>> {
|
||||
return http<T>({
|
||||
url,
|
||||
method,
|
||||
data,
|
||||
onDownloadProgress,
|
||||
signal,
|
||||
beforeRequest,
|
||||
afterRequest,
|
||||
@@ -55,13 +59,14 @@ export function get<T = any>(
|
||||
}
|
||||
|
||||
export function post<T = any>(
|
||||
{ url, data, method = 'POST', headers, signal, beforeRequest, afterRequest }: HttpOption,
|
||||
{ url, data, method = 'POST', headers, onDownloadProgress, signal, beforeRequest, afterRequest }: HttpOption,
|
||||
): Promise<Response<T>> {
|
||||
return http<T>({
|
||||
url,
|
||||
method,
|
||||
data,
|
||||
headers,
|
||||
onDownloadProgress,
|
||||
signal,
|
||||
beforeRequest,
|
||||
afterRequest,
|
||||
|
@@ -8,9 +8,9 @@ defineProps<Props>()
|
||||
|
||||
<template>
|
||||
<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">
|
||||
<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>
|
||||
</span>
|
||||
</template>
|
69
src/views/chat/components/Message/Text.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { marked } from 'marked'
|
||||
import hljs from 'highlight.js'
|
||||
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
||||
import { encodeHTML } from '@/utils/format'
|
||||
|
||||
interface Props {
|
||||
inversion?: boolean
|
||||
error?: boolean
|
||||
text?: string
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { isMobile } = useBasicLayout()
|
||||
|
||||
const renderer = new marked.Renderer()
|
||||
|
||||
renderer.html = (html) => {
|
||||
return `<p>${encodeHTML(html)}</p>`
|
||||
}
|
||||
|
||||
renderer.code = (code, language) => {
|
||||
const validLang = !!(language && hljs.getLanguage(language))
|
||||
const highlighted = validLang ? hljs.highlight(language, code).value : code
|
||||
return `<pre><code class="hljs ${language}">${highlighted}</code></pre>`
|
||||
}
|
||||
|
||||
marked.setOptions({ renderer })
|
||||
|
||||
const wrapClass = computed(() => {
|
||||
return [
|
||||
'text-wrap',
|
||||
'min-w-[20px]',
|
||||
'rounded-md',
|
||||
isMobile.value ? 'p-2' : 'p-3',
|
||||
props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
|
||||
props.inversion ? 'dark:bg-[#a1dc95]' : 'dark:bg-[#1e1e20]',
|
||||
{ 'text-red-500': props.error },
|
||||
]
|
||||
})
|
||||
|
||||
const text = computed(() => {
|
||||
const value = props.text ?? ''
|
||||
if (!props.inversion)
|
||||
return marked(value)
|
||||
return value
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="text-black" :class="wrapClass">
|
||||
<template v-if="loading">
|
||||
<span class="dark:text-white w-[4px] h-[20px] block animate-blink" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="leading-relaxed break-all">
|
||||
<div v-if="!inversion" class="markdown-body" v-html="text" />
|
||||
<div v-else class="whitespace-pre-wrap" v-text="text" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
@import url(./style.less);
|
||||
</style>
|
72
src/views/chat/components/Message/index.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<script setup lang='ts'>
|
||||
import AvatarComponent from './Avatar.vue'
|
||||
import TextComponent from './Text.vue'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
|
||||
interface Props {
|
||||
dateTime?: string
|
||||
text?: string
|
||||
inversion?: boolean
|
||||
error?: boolean
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
interface Emit {
|
||||
(ev: 'regenerate'): void
|
||||
(ev: 'delete'): void
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
function handleDelete() {
|
||||
emit('delete')
|
||||
}
|
||||
|
||||
function handleRegenerate() {
|
||||
emit('regenerate')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full mb-6 overflow-hidden" :class="[{ 'flex-row-reverse': inversion }]">
|
||||
<div
|
||||
class="flex items-center justify-center flex-shrink-0 h-8 overflow-hidden rounded-full basis-8"
|
||||
:class="[inversion ? 'ml-2' : 'mr-2']"
|
||||
>
|
||||
<AvatarComponent :image="inversion" />
|
||||
</div>
|
||||
<div class="overflow-hidden text-sm " :class="[inversion ? 'items-end' : 'items-start']">
|
||||
<p class="text-xs text-[#b4bbc4]" :class="[inversion ? 'text-right' : 'text-left']">
|
||||
{{ dateTime }}
|
||||
</p>
|
||||
<div
|
||||
class="flex items-end gap-2 mt-2"
|
||||
:class="[inversion ? 'flex-row-reverse' : 'flex-row']"
|
||||
>
|
||||
<TextComponent
|
||||
:inversion="inversion"
|
||||
:error="error"
|
||||
:text="text"
|
||||
:loading="loading"
|
||||
/>
|
||||
<div class="flex flex-col">
|
||||
<button
|
||||
v-if="!inversion"
|
||||
class="mb-2 transition text-neutral-400 hover:text-neutral-800 dark:hover:text-neutral-200"
|
||||
@click="handleRegenerate"
|
||||
>
|
||||
<SvgIcon icon="ri:restart-line" />
|
||||
</button>
|
||||
<button
|
||||
class="mb-1 transition text-neutral-400 hover:text-neutral-800 dark:hover:text-neutral-200"
|
||||
@click="handleDelete"
|
||||
>
|
||||
<SvgIcon icon="ri:delete-bin-6-line" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
35
src/views/chat/components/Message/style.less
Normal file
@@ -0,0 +1,35 @@
|
||||
.markdown-body {
|
||||
background-color: transparent;
|
||||
font-size: 14px;
|
||||
|
||||
p{
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
pre code,
|
||||
pre tt {
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.highlight pre,
|
||||
pre {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.dark{
|
||||
.markdown-body{
|
||||
.highlight pre,
|
||||
pre {
|
||||
background-color: #18181c;
|
||||
}
|
||||
}
|
||||
}
|
18
src/views/chat/hooks/useChat.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { useChatStore } from '@/store'
|
||||
|
||||
export function useChat() {
|
||||
const chatStore = useChatStore()
|
||||
|
||||
const addChat = (uuid: number, chat: Chat.Chat) => {
|
||||
chatStore.addChatByUuid(uuid, chat)
|
||||
}
|
||||
|
||||
const updateChat = (uuid: number, index: number, chat: Chat.Chat) => {
|
||||
chatStore.updateChatByUuid(uuid, index, chat)
|
||||
}
|
||||
|
||||
return {
|
||||
addChat,
|
||||
updateChat,
|
||||
}
|
||||
}
|
32
src/views/chat/hooks/useScroll.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { Ref } from 'vue'
|
||||
import { nextTick, ref } from 'vue'
|
||||
|
||||
type ScrollElement = HTMLDivElement | null
|
||||
|
||||
interface ScrollReturn {
|
||||
scrollRef: Ref<ScrollElement>
|
||||
scrollToBottom: () => Promise<void>
|
||||
scrollToTop: () => Promise<void>
|
||||
}
|
||||
|
||||
export function useScroll(): ScrollReturn {
|
||||
const scrollRef = ref<ScrollElement>(null)
|
||||
|
||||
const scrollToBottom = async () => {
|
||||
await nextTick()
|
||||
if (scrollRef.value)
|
||||
scrollRef.value.scrollTop = scrollRef.value.scrollHeight
|
||||
}
|
||||
|
||||
const scrollToTop = async () => {
|
||||
await nextTick()
|
||||
if (scrollRef.value)
|
||||
scrollRef.value.scrollTop = 0
|
||||
}
|
||||
|
||||
return {
|
||||
scrollRef,
|
||||
scrollToBottom,
|
||||
scrollToTop,
|
||||
}
|
||||
}
|
382
src/views/chat/index.vue
Normal file
@@ -0,0 +1,382 @@
|
||||
<script setup lang='ts'>
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { NButton, NInput, useDialog, useMessage } from 'naive-ui'
|
||||
import { Message } from './components'
|
||||
import { useScroll } from './hooks/useScroll'
|
||||
import { useChat } from './hooks/useChat'
|
||||
import { HoverButton, SvgIcon } from '@/components/common'
|
||||
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
||||
import { useChatStore } from '@/store'
|
||||
import { fetchChatAPIProcess } from '@/api'
|
||||
|
||||
let controller = new AbortController()
|
||||
|
||||
const route = useRoute()
|
||||
const dialog = useDialog()
|
||||
const ms = useMessage()
|
||||
|
||||
const chatStore = useChatStore()
|
||||
|
||||
const { isMobile } = useBasicLayout()
|
||||
const { addChat, updateChat } = useChat()
|
||||
const { scrollRef, scrollToBottom } = useScroll()
|
||||
|
||||
const { uuid } = route.params as { uuid: string }
|
||||
|
||||
const dataSources = computed(() => chatStore.getChatByUuid(+uuid))
|
||||
const conversationList = computed(() => dataSources.value.filter(item => (!item.inversion && !item.error)))
|
||||
|
||||
const prompt = ref<string>('')
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
function handleSubmit() {
|
||||
onConversation()
|
||||
}
|
||||
|
||||
async function onConversation() {
|
||||
const message = prompt.value
|
||||
|
||||
if (loading.value)
|
||||
return
|
||||
|
||||
if (!message || message.trim() === '')
|
||||
return
|
||||
|
||||
controller = new AbortController()
|
||||
|
||||
addChat(
|
||||
+uuid,
|
||||
{
|
||||
dateTime: new Date().toLocaleString(),
|
||||
text: message,
|
||||
inversion: true,
|
||||
error: false,
|
||||
conversationOptions: null,
|
||||
requestOptions: { prompt: message, options: null },
|
||||
},
|
||||
)
|
||||
scrollToBottom()
|
||||
|
||||
loading.value = true
|
||||
prompt.value = ''
|
||||
|
||||
let options: Chat.ConversationRequest = {}
|
||||
const lastContext = conversationList.value[conversationList.value.length - 1]?.conversationOptions
|
||||
|
||||
if (lastContext)
|
||||
options = { ...lastContext }
|
||||
|
||||
addChat(
|
||||
+uuid,
|
||||
{
|
||||
dateTime: new Date().toLocaleString(),
|
||||
text: 'Aha, Thinking...',
|
||||
loading: true,
|
||||
inversion: false,
|
||||
error: false,
|
||||
conversationOptions: null,
|
||||
requestOptions: { prompt: message, options: { ...options } },
|
||||
},
|
||||
)
|
||||
scrollToBottom()
|
||||
|
||||
try {
|
||||
await fetchChatAPIProcess<Chat.ConversationResponse>({
|
||||
prompt: message,
|
||||
options,
|
||||
signal: controller.signal,
|
||||
onDownloadProgress: ({ event }) => {
|
||||
const xhr = event.target
|
||||
const { responseText } = xhr
|
||||
// Always process the final line
|
||||
const lastIndex = responseText.lastIndexOf('\n')
|
||||
let chunk = responseText
|
||||
if (lastIndex !== -1)
|
||||
chunk = responseText.substring(lastIndex)
|
||||
try {
|
||||
const data = JSON.parse(chunk)
|
||||
updateChat(
|
||||
+uuid,
|
||||
dataSources.value.length - 1,
|
||||
{
|
||||
dateTime: new Date().toLocaleString(),
|
||||
text: data.text ?? '',
|
||||
inversion: false,
|
||||
error: false,
|
||||
loading: false,
|
||||
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
|
||||
requestOptions: { prompt: message, options: { ...options } },
|
||||
},
|
||||
)
|
||||
scrollToBottom()
|
||||
}
|
||||
catch (error) {
|
||||
//
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
catch (error: any) {
|
||||
let errorMessage = error?.message ?? 'Something went wrong, please try again later.'
|
||||
|
||||
if (error.message === 'canceled')
|
||||
errorMessage = 'Request canceled. Please try again.'
|
||||
|
||||
updateChat(
|
||||
+uuid,
|
||||
dataSources.value.length - 1,
|
||||
{
|
||||
dateTime: new Date().toLocaleString(),
|
||||
text: errorMessage,
|
||||
inversion: false,
|
||||
error: true,
|
||||
loading: false,
|
||||
conversationOptions: null,
|
||||
requestOptions: { prompt: message, options: { ...options } },
|
||||
},
|
||||
)
|
||||
scrollToBottom()
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onRegenerate(index: number) {
|
||||
if (loading.value)
|
||||
return
|
||||
|
||||
controller = new AbortController()
|
||||
|
||||
const { requestOptions } = dataSources.value[index]
|
||||
|
||||
const message = requestOptions?.prompt ?? ''
|
||||
|
||||
let options: Chat.ConversationRequest = {}
|
||||
|
||||
if (requestOptions.options)
|
||||
options = { ...requestOptions.options }
|
||||
|
||||
loading.value = true
|
||||
|
||||
updateChat(
|
||||
+uuid,
|
||||
index,
|
||||
{
|
||||
dateTime: new Date().toLocaleString(),
|
||||
text: 'Aha, Let me think again...',
|
||||
inversion: false,
|
||||
error: false,
|
||||
loading: true,
|
||||
conversationOptions: null,
|
||||
requestOptions: { prompt: message, ...options },
|
||||
},
|
||||
)
|
||||
|
||||
try {
|
||||
await fetchChatAPIProcess<Chat.ConversationResponse>({
|
||||
prompt: message,
|
||||
options,
|
||||
signal: controller.signal,
|
||||
onDownloadProgress: ({ event }) => {
|
||||
const xhr = event.target
|
||||
const { responseText } = xhr
|
||||
// Always process the final line
|
||||
const lastIndex = responseText.lastIndexOf('\n')
|
||||
let chunk = responseText
|
||||
if (lastIndex !== -1)
|
||||
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) {
|
||||
let errorMessage = error?.message ?? 'Something went wrong, please try again later.'
|
||||
|
||||
if (error.message === 'canceled')
|
||||
errorMessage = 'Request canceled. Please try again.'
|
||||
|
||||
updateChat(
|
||||
+uuid,
|
||||
index,
|
||||
{
|
||||
dateTime: new Date().toLocaleString(),
|
||||
text: errorMessage,
|
||||
inversion: false,
|
||||
error: true,
|
||||
loading: false,
|
||||
conversationOptions: null,
|
||||
requestOptions: { prompt: message, ...options },
|
||||
},
|
||||
)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleDelete(index: number) {
|
||||
if (loading.value)
|
||||
return
|
||||
|
||||
dialog.warning({
|
||||
title: 'Delete Message',
|
||||
content: 'Are you sure to delete this message?',
|
||||
positiveText: 'Yes',
|
||||
negativeText: 'No',
|
||||
onPositiveClick: () => {
|
||||
chatStore.deleteChatByUuid(+uuid, index)
|
||||
ms.success('Message deleted successfully.')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function handleClear() {
|
||||
if (loading.value)
|
||||
return
|
||||
|
||||
dialog.warning({
|
||||
title: 'Clear Chat',
|
||||
content: 'Are you sure to clear this chat?',
|
||||
positiveText: 'Yes',
|
||||
negativeText: 'No',
|
||||
onPositiveClick: () => {
|
||||
chatStore.clearChatByUuid(+uuid)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function handleEnter(event: KeyboardEvent) {
|
||||
if (!isMobile.value) {
|
||||
if (event.key === 'Enter' && !event.shiftKey) {
|
||||
event.preventDefault()
|
||||
handleSubmit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleStop() {
|
||||
if (loading.value) {
|
||||
controller.abort()
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const placeholder = computed(() => {
|
||||
if (isMobile.value)
|
||||
return 'Ask me anything...'
|
||||
return 'Ask me anything... (Shift + Enter = line break)'
|
||||
})
|
||||
|
||||
const buttonDisabled = computed(() => {
|
||||
return loading.value || !prompt.value || prompt.value.trim() === ''
|
||||
})
|
||||
|
||||
const wrapClass = computed(() => {
|
||||
if (isMobile.value)
|
||||
return ['pt-14', 'pb-16']
|
||||
|
||||
return []
|
||||
})
|
||||
|
||||
const footerClass = computed(() => {
|
||||
let classes = ['p-4']
|
||||
if (isMobile.value)
|
||||
classes = ['p-2', 'pr-4', 'fixed', 'bottom-4', 'left-0', 'right-0', 'z-30', 'h-14', 'overflow-hidden']
|
||||
return classes
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
scrollToBottom()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (loading.value)
|
||||
controller.abort()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col h-full" :class="wrapClass">
|
||||
<main class="flex-1 overflow-hidden">
|
||||
<div
|
||||
id="scrollRef"
|
||||
ref="scrollRef"
|
||||
class="h-full overflow-hidden overflow-y-auto"
|
||||
:class="[isMobile ? 'p-2' : 'p-4']"
|
||||
>
|
||||
<template v-if="!dataSources.length">
|
||||
<div class="flex items-center justify-center mt-4 text-center text-neutral-300">
|
||||
<SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
|
||||
<span>Aha~</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div>
|
||||
<Message
|
||||
v-for="(item, index) of dataSources"
|
||||
:key="index"
|
||||
:date-time="item.dateTime"
|
||||
:text="item.text"
|
||||
:inversion="item.inversion"
|
||||
:error="item.error"
|
||||
:loading="item.loading"
|
||||
@regenerate="onRegenerate(index)"
|
||||
@delete="handleDelete(index)"
|
||||
/>
|
||||
<div class="flex justify-center">
|
||||
<NButton v-if="loading" ghost @click="handleStop">
|
||||
<template #icon>
|
||||
<SvgIcon icon="ri:stop-circle-line" />
|
||||
</template>
|
||||
Stop Responding
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</main>
|
||||
<footer :class="footerClass">
|
||||
<div class="flex items-center justify-between space-x-2">
|
||||
<HoverButton @click="handleClear">
|
||||
<span class="text-xl text-[#4f555e] dark:text-white">
|
||||
<SvgIcon icon="ri:delete-bin-line" />
|
||||
</span>
|
||||
</HoverButton>
|
||||
<NInput
|
||||
v-model:value="prompt"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 1, maxRows: 2 }"
|
||||
:placeholder="placeholder"
|
||||
@keypress="handleEnter"
|
||||
/>
|
||||
<NButton type="primary" :disabled="buttonDisabled" @click="handleSubmit">
|
||||
<template #icon>
|
||||
<span class="dark:text-black">
|
||||
<SvgIcon icon="ri:send-plane-fill" />
|
||||
</span>
|
||||
</template>
|
||||
</NButton>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
@@ -1,12 +1,17 @@
|
||||
<script setup lang='ts'>
|
||||
import { computed } from 'vue'
|
||||
import { NLayout, NLayoutContent } from 'naive-ui'
|
||||
import { useRouter } from 'vue-router'
|
||||
import Sider from './sider/index.vue'
|
||||
import Header from './header/index.vue'
|
||||
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
||||
import { useAppStore } from '@/store'
|
||||
import { useAppStore, useChatStore } from '@/store'
|
||||
|
||||
const router = useRouter()
|
||||
const appStore = useAppStore()
|
||||
const chatStore = useChatStore()
|
||||
|
||||
router.replace({ name: 'Chat', params: { uuid: chatStore.active } })
|
||||
|
||||
const { isMobile } = useBasicLayout()
|
||||
|
||||
@@ -15,26 +20,27 @@ const collapsed = computed(() => appStore.siderCollapsed)
|
||||
const getMobileClass = computed(() => {
|
||||
if (isMobile.value)
|
||||
return ['rounded-none', 'shadow-none']
|
||||
return ['border', 'rounded-md', 'shadow-md']
|
||||
return ['border', 'rounded-md', 'shadow-md', 'dark:border-neutral-800']
|
||||
})
|
||||
|
||||
const getContainerClass = computed(() => {
|
||||
return [
|
||||
'h-full',
|
||||
{ 'pt-14': isMobile.value },
|
||||
{ 'pl-[260px]': !isMobile.value && !collapsed.value },
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-screen p-4" :class="[{ 'p-0': isMobile }]">
|
||||
<div class="h-full dark:bg-[#24272e] transition-all" :class="[isMobile ? 'p-0' : 'p-4']">
|
||||
<div class="h-full overflow-hidden" :class="getMobileClass">
|
||||
<NLayout class="z-40 transition" :class="getContainerClass" has-sider>
|
||||
<Sider />
|
||||
<Header v-if="isMobile" />
|
||||
<NLayoutContent class="h-full">
|
||||
<slot />
|
||||
<RouterView v-slot="{ Component, route }">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</RouterView>
|
||||
</NLayoutContent>
|
||||
</NLayout>
|
||||
</div>
|
55
src/views/chat/layout/header/index.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick } from 'vue'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
import { useAppStore, useChatStore } from '@/store'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const chatStore = useChatStore()
|
||||
|
||||
const collapsed = computed(() => appStore.siderCollapsed)
|
||||
const currentChatHistory = computed(() => chatStore.getChatHistoryByCurrentActive)
|
||||
|
||||
function handleUpdateCollapsed() {
|
||||
appStore.setSiderCollapsed(!collapsed.value)
|
||||
}
|
||||
|
||||
function onScrollToTop() {
|
||||
const scrollRef = document.querySelector('#scrollRef')
|
||||
if (scrollRef)
|
||||
nextTick(() => scrollRef.scrollTop = 0)
|
||||
}
|
||||
|
||||
function onScrollToBottom() {
|
||||
const scrollRef = document.querySelector('#scrollRef')
|
||||
if (scrollRef)
|
||||
nextTick(() => scrollRef.scrollTop = scrollRef.scrollHeight)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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">
|
||||
<button
|
||||
class="flex items-center justify-center w-11 h-11"
|
||||
@click="handleUpdateCollapsed"
|
||||
>
|
||||
<SvgIcon v-if="collapsed" class="text-2xl" icon="ri:align-justify" />
|
||||
<SvgIcon v-else class="text-2xl" icon="ri:align-right" />
|
||||
</button>
|
||||
<h1
|
||||
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>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
3
src/views/chat/layout/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import ChatLayout from './Layout.vue'
|
||||
|
||||
export { ChatLayout }
|
64
src/views/chat/layout/sider/Footer.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<script setup lang='ts'>
|
||||
import { computed, h, ref } from 'vue'
|
||||
import { NDropdown } from 'naive-ui'
|
||||
import { HoverButton, Setting, SvgIcon, UserAvatar } from '@/components/common'
|
||||
import { useAppStore } from '@/store'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const show = ref(false)
|
||||
|
||||
const theme = computed(() => appStore.theme)
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: 'Dark',
|
||||
key: 'dark',
|
||||
icon: renderIcon('ri:moon-foggy-line'),
|
||||
},
|
||||
{
|
||||
label: 'Light',
|
||||
key: 'light',
|
||||
icon: renderIcon('ri:sun-foggy-line'),
|
||||
},
|
||||
{
|
||||
label: 'Auto',
|
||||
key: 'auto',
|
||||
icon: renderIcon('ri:contrast-line'),
|
||||
},
|
||||
]
|
||||
|
||||
function renderIcon(icon: string) {
|
||||
return () => {
|
||||
return h(SvgIcon, { icon })
|
||||
}
|
||||
}
|
||||
|
||||
function handleThemeChange(key: 'light' | 'dark' | 'auto') {
|
||||
appStore.setTheme(key)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer class="flex items-center justify-between min-w-0 p-4 overflow-hidden border-t dark:border-neutral-800">
|
||||
<UserAvatar />
|
||||
|
||||
<NDropdown :options="options" placement="top" trigger="click" @select="handleThemeChange">
|
||||
<HoverButton>
|
||||
<span class="text-xl text-[#4f555e] dark:text-white">
|
||||
<SvgIcon v-if="theme === 'dark'" icon="ri:moon-foggy-line" />
|
||||
<SvgIcon v-if="theme === 'light'" icon="ri:sun-foggy-line" />
|
||||
<SvgIcon v-if="theme === 'auto'" icon="ri:contrast-line" />
|
||||
</span>
|
||||
</HoverButton>
|
||||
</NDropdown>
|
||||
|
||||
<HoverButton tooltip="Setting" @click="show = true">
|
||||
<span class="text-xl text-[#4f555e] dark:text-white">
|
||||
<SvgIcon icon="ri:settings-4-line" />
|
||||
</span>
|
||||
</HoverButton>
|
||||
|
||||
<Setting v-model:visible="show" />
|
||||
</footer>
|
||||
</template>
|
99
src/views/chat/layout/sider/List.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<script setup lang='ts'>
|
||||
import { computed } from 'vue'
|
||||
import { NInput, NPopconfirm, NScrollbar } from 'naive-ui'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
import { useAppStore, useChatStore } from '@/store'
|
||||
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
||||
|
||||
const { isMobile } = useBasicLayout()
|
||||
|
||||
const appStore = useAppStore()
|
||||
const chatStore = useChatStore()
|
||||
|
||||
const dataSources = computed(() => chatStore.history)
|
||||
|
||||
async function handleSelect({ uuid }: Chat.History) {
|
||||
if (isActive(uuid))
|
||||
return
|
||||
|
||||
await chatStore.setActive(uuid)
|
||||
|
||||
if (isMobile.value)
|
||||
appStore.setSiderCollapsed(true)
|
||||
}
|
||||
|
||||
function handleEdit({ uuid }: Chat.History, isEdit: boolean, event?: MouseEvent) {
|
||||
event?.stopPropagation()
|
||||
chatStore.updateHistory(uuid, { isEdit })
|
||||
}
|
||||
|
||||
function handleDelete(index: number, event?: MouseEvent | TouchEvent) {
|
||||
event?.stopPropagation()
|
||||
chatStore.deleteHistory(index)
|
||||
}
|
||||
|
||||
function handleEnter({ uuid }: Chat.History, isEdit: boolean, event: KeyboardEvent) {
|
||||
event?.stopPropagation()
|
||||
if (event.key === 'Enter')
|
||||
chatStore.updateHistory(uuid, { isEdit })
|
||||
}
|
||||
|
||||
function isActive(uuid: number) {
|
||||
return chatStore.active === uuid
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NScrollbar class="px-4">
|
||||
<div class="flex flex-col gap-2 text-sm">
|
||||
<template v-if="!dataSources.length">
|
||||
<div class="flex flex-col items-center mt-4 text-center text-neutral-300">
|
||||
<SvgIcon icon="ri:inbox-line" class="mb-2 text-3xl" />
|
||||
<span>No history</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-for="(item, index) of dataSources" :key="index">
|
||||
<a
|
||||
class="relative flex items-center gap-3 px-3 py-3 break-all border rounded-md cursor-pointer hover:bg-neutral-100 group dark:border-neutral-800 dark:hover:bg-[#24272e]"
|
||||
:class="isActive(item.uuid) && ['border-[#4b9e5f]', 'bg-neutral-100', 'text-[#4b9e5f]', 'dark:bg-[#24272e]', 'dark:border-[#4b9e5f]', 'pr-14']"
|
||||
@click="handleSelect(item)"
|
||||
>
|
||||
<span>
|
||||
<SvgIcon icon="ri:message-3-line" />
|
||||
</span>
|
||||
<div class="relative flex-1 overflow-hidden break-all text-ellipsis whitespace-nowrap">
|
||||
<NInput
|
||||
v-if="item.isEdit"
|
||||
v-model:value="item.title"
|
||||
size="tiny"
|
||||
@keypress="handleEnter(item, false, $event)"
|
||||
/>
|
||||
<span v-else>{{ item.title }}</span>
|
||||
</div>
|
||||
<div v-if="isActive(item.uuid)" class="absolute z-10 flex visible right-1">
|
||||
<template v-if="item.isEdit">
|
||||
<button class="p-1" @click="handleEdit(item, false, $event)">
|
||||
<SvgIcon icon="ri:save-line" />
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button class="p-1">
|
||||
<SvgIcon icon="ri:edit-line" @click="handleEdit(item, true, $event)" />
|
||||
</button>
|
||||
<NPopconfirm placement="bottom" @positive-click="handleDelete(index, $event)">
|
||||
<template #trigger>
|
||||
<button class="p-1">
|
||||
<SvgIcon icon="ri:delete-bin-line" />
|
||||
</button>
|
||||
</template>
|
||||
Are you sure to clear this history?
|
||||
</NPopconfirm>
|
||||
</template>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</NScrollbar>
|
||||
</template>
|
@@ -1,35 +1,46 @@
|
||||
<script setup lang='ts'>
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { computed, watch } from 'vue'
|
||||
import { NButton, NLayoutSider } from 'naive-ui'
|
||||
import List from './List.vue'
|
||||
import { HoverButton, SvgIcon, UserAvatar } from '@/components/common'
|
||||
import { useAppStore, useHistoryStore } from '@/store'
|
||||
import Footer from './Footer.vue'
|
||||
import { useAppStore, useChatStore } from '@/store'
|
||||
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const historyStore = useHistoryStore()
|
||||
const chatStore = useChatStore()
|
||||
|
||||
const { isMobile } = useBasicLayout()
|
||||
|
||||
const collapsed = computed(() => appStore.siderCollapsed)
|
||||
|
||||
function handleAdd() {
|
||||
historyStore.addHistory({
|
||||
title: 'New Chat',
|
||||
isEdit: false,
|
||||
data: [],
|
||||
})
|
||||
chatStore.addHistory({ title: 'New Chat', uuid: Date.now(), isEdit: false })
|
||||
}
|
||||
|
||||
function handleUpdateCollapsed() {
|
||||
appStore.setSiderCollapsed(!collapsed.value)
|
||||
}
|
||||
|
||||
const getMobileClass = computed<CSSProperties>(() => {
|
||||
if (isMobile.value) {
|
||||
return {
|
||||
position: 'fixed',
|
||||
zIndex: 50,
|
||||
}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
watch(
|
||||
isMobile,
|
||||
(val) => {
|
||||
appStore.setSiderCollapsed(val)
|
||||
},
|
||||
{ flush: 'post' },
|
||||
{
|
||||
immediate: true,
|
||||
flush: 'post',
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -42,25 +53,24 @@ watch(
|
||||
collapse-mode="transform"
|
||||
position="absolute"
|
||||
bordered
|
||||
:style="getMobileClass"
|
||||
@update-collapsed="handleUpdateCollapsed"
|
||||
>
|
||||
<div class="flex flex-col h-full" :class="[{ 'pt-14': isMobile }]">
|
||||
<main class="flex-1 min-h-0 overflow-hidden">
|
||||
<div class="flex flex-col h-full">
|
||||
<main class="flex flex-col flex-1 min-h-0">
|
||||
<div class="p-4">
|
||||
<NButton dashed block @click="handleAdd">
|
||||
New chat
|
||||
</NButton>
|
||||
</div>
|
||||
<List />
|
||||
<div class="flex-1 min-h-0 pb-4 overflow-hidden">
|
||||
<List />
|
||||
</div>
|
||||
</main>
|
||||
<footer class="flex items-center justify-between min-w-0 p-4 overflow-hidden border-t h-[70px]">
|
||||
<UserAvatar />
|
||||
<HoverButton tooltip="Setting">
|
||||
<span class="text-xl text-[#4f555e]">
|
||||
<SvgIcon icon="ri:settings-4-line" />
|
||||
</span>
|
||||
</HoverButton>
|
||||
</footer>
|
||||
<Footer />
|
||||
</div>
|
||||
</NLayoutSider>
|
||||
<template v-if="isMobile">
|
||||
<div v-show="!collapsed" class="fixed inset-0 z-40 bg-black/40" @click="handleUpdateCollapsed" />
|
||||
</template>
|
||||
</template>
|
@@ -1,7 +0,0 @@
|
||||
<script setup lang='ts'>
|
||||
import { Chat } from '@/components/business'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Chat />
|
||||
</template>
|
@@ -1,11 +1,22 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
'./index.html',
|
||||
'./src/**/*.{vue,js,ts,jsx,tsx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
animation: {
|
||||
blink: 'blink 1.2s infinite steps(1, start)',
|
||||
},
|
||||
keyframes: {
|
||||
blink: {
|
||||
'0%, 100%': { 'background-color': 'currentColor' },
|
||||
'50%': { 'background-color': 'transparent' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
@@ -13,8 +13,8 @@ export default defineConfig((env) => {
|
||||
},
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
port: 1002,
|
||||
host: '0.0.0.0',
|
||||
port: 1002,
|
||||
open: false,
|
||||
proxy: {
|
||||
'/api': {
|
||||
|