Compare commits

22 Commits

Author SHA1 Message Date
ChenZhaoYu
6216d84ecd chore: v2.4.1 2023-02-18 09:59:39 +08:00
Void
ea887ad755 输入框修改为富文本,支持换行 (#53)
* add Dockerfile

* update Readme.md

* Fixed : Docker log file

* Update vite.config.ts

* chore: code merge, update README.md

* chore: update GitHub workflow

* fix: 输入框修改为富文本,支持换行

* chore: 更新.gitignore

* Update index.vue

---------

Co-authored-by: Redon <790348264@qq.com>
2023-02-18 09:56:13 +08:00
Redon
c98a2a3cb8 feat: 移动端样式修复 (#56)
* feat: 移动端优化

* feat: 部份移动端问题修复
2023-02-18 09:25:57 +08:00
ChenZhaoYu
06f41c3758 chore: 更新封面 2023-02-17 11:10:17 +08:00
Redon
3f4cb5c900 feat: 响应式支持移动端 (#49)
* feat: 响应式兼容 h5

* feat: 补充空状态

* feat: thinking

* chore: @vueuse/core 导致的类型检查错误

* chore: version 2.4.0
2023-02-17 10:57:06 +08:00
ChenZhaoYu
405aeaa2a5 chore: version 2.3.3 2023-02-16 21:03:12 +08:00
ChenZhaoYu
fd28468778 chore: build_docker repositories 2023-02-16 20:30:49 +08:00
Stephen Ni
4128d319ed docs: polish docker run (#44)
* docs: polish docker run

* Update README.md

---------

Co-authored-by: Redon <790348264@qq.com>
2023-02-16 20:29:46 +08:00
Jerry Wang
4a06b3943c 优化 docker 构建的镜像文件大小,添加自动化构建 (#42)
* perf: optimize docker image

* perf: shrink image size

* fix: build docker image

* fix: docker image namespace

* docs: add docker compose example

* docs: fix docker compose example

* docs: fix again :)

* docs: and again.....
2023-02-16 20:25:29 +08:00
ChenZhaoYu
77cdcb7798 chore: 添加贡献列表 2023-02-16 14:17:03 +08:00
ChenZhaoYu
ee542a01f7 chore: eslint 依赖报错 2023-02-16 14:03:40 +08:00
ChenZhaoYu
30372c2e70 chore: update README 2023-02-16 13:42:19 +08:00
Void
6646695849 Add Dockerfile (#33)
* add Dockerfile

* update Readme.md

* Fixed : Docker log file

* Update vite.config.ts
2023-02-16 13:39:07 +08:00
ChenZhaoYu
e9db25a970 chore: version 2.3.2 2023-02-16 12:31:08 +08:00
ChenZhaoYu
63f4d7d77c chore: update deps 2023-02-16 12:29:47 +08:00
ChenZhaoYu
20255ddce4 chore: gitignore file 2023-02-16 12:00:03 +08:00
wang ha ha
bbd8ff773b 添加 docker 镜像打包部署脚本 (#27)
* 删除env

* 修改生产依赖安装缺失。

* 添加docker镜像构建

* no message

* 修改超时时间。

* 修复镜像构建错误。

* 修改环境变量获取baseapi

* 修改前端链接获取位置。

* 删除默认env

* 添加会 .env 文件,但是注释掉。

* 使用默认 api。

* 添加前端调试和部署路由兼容。

* 删除key

* Update .env

* Delete build_docker.yml

暂时不做 `git hooks` 处理

* Delete .gitignore

* Delete Dockerfile

工作路径好像有点错误,先不合并

* Delete build.sh

工作路径好像有点错误,先不合并

---------

Co-authored-by: 王金海(haha.wang) <wanghaha@huolala.cn>
Co-authored-by: haha.wang <haha.wang@huolala.cn>
Co-authored-by: Redon <790348264@qq.com>
2023-02-16 11:58:29 +08:00
Redon
c8518b7789 chore: 删除未使用代码 (#37) 2023-02-16 11:38:18 +08:00
ChenZhaoYu
bf015d35f7 chore: 添加 node>=14 说明 2023-02-16 11:15:00 +08:00
ChenZhaoYu
eab39b7f88 chore: update doc 2023-02-16 08:24:42 +08:00
ChenZhaoYu
981c036542 fix: type error 2023-02-16 08:23:31 +08:00
Redon
91f51b6338 pull (#34) (#35)
* fix: 修复部分多会话逻辑

* chore: version 2.3.1
2023-02-15 22:44:32 +08:00
29 changed files with 599 additions and 175 deletions

6
.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
node_modules
Dockerfile
.git
.husky
.github
.vscode

37
.github/workflows/build_docker.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: build_docker
on:
push:
branches: [main]
jobs:
build_docker:
name: Build docker
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: chenzhaoyu94/chatgpt-web
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64

View File

@@ -23,7 +23,9 @@
"axios",
"bumpp",
"chatgpt",
"chenzhaoyu",
"commitlint",
"dockerhub",
"esno",
"GPTAPI",
"iconify",

View File

@@ -1,3 +1,40 @@
## v2.4.1
`2023-02-18`
### Enhancement
- 调整部份移动端上的样式
- 输入框支持换行
## v2.4.0
`2023-02-17`
### Feature
- 响应式支持移动端
### Enhancement
- 修改部份描述错误
## v2.3.3
`2023-02-16`
### Feature
- 添加 `README` 部份说明和贡献列表
- 添加 `docker` 镜像
- 添加 `GitHub Action` 自动化构建
### BugFix
- 回退依赖更新导致的 [Eslint 报错](https://github.com/eslint/eslint/issues/16896)
## v2.3.2
`2023-02-16`
### Enhancement
- 更新依赖至最新
- 优化部份内容
## v2.3.1
`2023-02-15`

20
Dockerfile Normal file
View File

@@ -0,0 +1,20 @@
# build front-end
FROM node:lts-alpine AS builder
COPY ./ /app
WORKDIR /app
RUN npm install pnpm -g && pnpm install && pnpm run build
# service
FROM node:lts-alpine
COPY /service /app
COPY --from=builder /app/dist /app/public
WORKDIR /app
RUN npm install pnpm -g && pnpm install
EXPOSE 3002
CMD ["pnpm", "run", "start"]

View File

@@ -2,7 +2,7 @@
使用 express 和 vue3 搭建的 ChartGPT 演示网页
![cover](./docs/cover-2.png)
![PC](./docs/cover.png)
> 提示:目前 `OpenAI` 开放的模型最高只有 `GPT-3`,和现在网页所使用的 `GPT-3.5` 或 `GPT-4` 有很大差距,需要等官方开放最新的模型接口。
@@ -23,7 +23,7 @@
### Node
`node` 版本需要 >= 18,使用 [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
@@ -36,7 +36,7 @@ npm install pnpm -g
```
### OpenAI API Key
获取 [OpenAI API key](https://platform.openai.com/overview) 并填写到本地环境变量
注册并获取 [OpenAI API key](https://platform.openai.com/overview) 并填写到本地环境变量
```
# service/.env 文件
@@ -78,6 +78,29 @@ pnpm dev
```
## 打包
## Docker build
[参考信息](https://github.com/Chanzhaoyu/chatgpt-web/pull/42)
```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
```
### 后端服务
> 如果你不需要本项目的 `node` 接口,可以省略如下操作
@@ -97,9 +120,12 @@ pnpm prod
PS: 不进行打包,直接在服务器上运行 `pnpm start` 也可
### 网页
### 前端打包
根目录下运行以下命令,然后将 `dist` 文件夹复制到你的托管服务器上
[参考信息](https://cn.vitejs.dev/guide/static-deploy.html#building-the-app)
```shell
pnpm build
```
@@ -117,5 +143,15 @@ Q: 文件保存时全部爆红?
A: `vscode` 请安装项目推荐插件,或手动安装 `Eslint` 插件。
## 参与贡献
贡献之前请先阅读 [贡献指南](./CONTRIBUTING.md)
感谢所有做过贡献的人!
<a href="https://github.com/Chanzhaoyu/chatgpt-web/graphs/contributors">
<img src="https://contrib.rocks/image?repo=Chanzhaoyu/chatgpt-web" />
</a>
## License
MIT © [ChenZhaoYu](./license)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

BIN
docs/cover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "chatgpt-web",
"version": "2.3.1",
"version": "2.4.1",
"private": false,
"description": "ChatGPT Web",
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
@@ -16,12 +16,14 @@
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"bootstrap": "pnpm install && pnpm run common:prepare",
"common:cleanup": "rimraf node_modules && rimraf pnpm-lock.yaml",
"common:prepare": "husky install"
},
"dependencies": {
"@vueuse/core": "^9.12.0",
"highlight.js": "^11.7.0",
"naive-ui": "^2.34.3",
"pinia": "^2.0.30",
@@ -35,20 +37,22 @@
"@iconify/vue": "^4.1.0",
"@types/crypto-js": "^4.1.1",
"@types/node": "^18.13.0",
"@types/web-bluetooth": "^0.0.16",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.4.13",
"axios": "^1.3.2",
"axios": "^1.3.3",
"crypto-js": "^4.1.1",
"eslint": "^8.34.0",
"husky": "^8.0.3",
"lint-staged": "^13.1.1",
"less": "^4.1.3",
"lint-staged": "^13.1.2",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.21",
"rimraf": "^4.1.2",
"tailwindcss": "^3.2.6",
"typescript": "~4.9.5",
"vite": "^4.1.1",
"vue-tsc": "^1.0.24"
"vue-tsc": "^1.1.0"
},
"lint-staged": {
"*.{ts,tsx,vue}": [

216
pnpm-lock.yaml generated
View File

@@ -7,14 +7,17 @@ specifiers:
'@iconify/vue': ^4.1.0
'@types/crypto-js': ^4.1.1
'@types/node': ^18.13.0
'@types/web-bluetooth': ^0.0.16
'@vitejs/plugin-vue': ^4.0.0
'@vueuse/core': ^9.12.0
autoprefixer: ^10.4.13
axios: ^1.3.2
axios: ^1.3.3
crypto-js: ^4.1.1
eslint: ^8.34.0
highlight.js: ^11.7.0
husky: ^8.0.3
lint-staged: ^13.1.1
less: ^4.1.3
lint-staged: ^13.1.2
naive-ui: ^2.34.3
npm-run-all: ^4.1.5
pinia: ^2.0.30
@@ -25,9 +28,10 @@ specifiers:
vite: ^4.1.1
vue: ^3.2.47
vue-router: ^4.1.6
vue-tsc: ^1.0.24
vue-tsc: ^1.1.0
dependencies:
'@vueuse/core': 9.12.0_vue@3.2.47
highlight.js: 11.7.0
naive-ui: 2.34.3_vue@3.2.47
pinia: 2.0.30_hmuptsblhheur2tugfgucj7gc4
@@ -41,20 +45,22 @@ devDependencies:
'@iconify/vue': 4.1.0_vue@3.2.47
'@types/crypto-js': 4.1.1
'@types/node': 18.13.0
'@types/web-bluetooth': 0.0.16
'@vitejs/plugin-vue': 4.0.0_vite@4.1.1+vue@3.2.47
autoprefixer: 10.4.13_postcss@8.4.21
axios: 1.3.3
crypto-js: 4.1.1
eslint: 8.34.0
husky: 8.0.3
less: 4.1.3
lint-staged: 13.1.2
npm-run-all: 4.1.5
postcss: 8.4.21
rimraf: 4.1.2
tailwindcss: 3.2.6_postcss@8.4.21
typescript: 4.9.5
vite: 4.1.1_@types+node@18.13.0
vue-tsc: 1.0.24_typescript@4.9.5
vite: 4.1.1_edl3ajnhen4c5iqs57t4gqrzly
vue-tsc: 1.1.0_typescript@4.9.5
packages:
@@ -755,6 +761,9 @@ packages:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: true
/@types/web-bluetooth/0.0.16:
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
/@typescript-eslint/eslint-plugin/5.52.0_6cfvjsbua5ptj65675bqcn6oza:
resolution: {integrity: sha512-lHazYdvYVsBokwCdKOppvYJKaJ4S41CgKBcPvyd0xjZNbvQdhn/pnJlGtQksQ/NhInzdaeaSarlBjDXHuclEbg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -892,47 +901,47 @@ packages:
vite: ^4.0.0
vue: ^3.2.25
dependencies:
vite: 4.1.1_@types+node@18.13.0
vite: 4.1.1_edl3ajnhen4c5iqs57t4gqrzly
vue: 3.2.47
dev: true
/@volar/language-core/1.0.24:
resolution: {integrity: sha512-vTN+alJiWwK0Pax6POqrmevbtFW2dXhjwWiW/MW4f48eDYPLdyURWcr8TixO7EN/nHsUBj2udT7igFKPtjyAKg==}
/@volar/language-core/1.2.0-alpha.11:
resolution: {integrity: sha512-OfbPmmFa4LUA8kJCg77V9ud4NASjJ3VKJ79QCQSfHa5SwXeZ5w7lvQe2yILFBjZ3JDB5EfFnHZUSct6ziK3x5Q==}
dependencies:
'@volar/source-map': 1.0.24
muggle-string: 0.1.0
'@volar/source-map': 1.2.0-alpha.11
dev: true
/@volar/source-map/1.0.24:
resolution: {integrity: sha512-Qsv/tkplx18pgBr8lKAbM1vcDqgkGKQzbChg6NW+v0CZc3G7FLmK+WrqEPzKlN7Cwdc6XVL559Nod8WKAfKr4A==}
/@volar/source-map/1.2.0-alpha.11:
resolution: {integrity: sha512-GCRqcq2bn8Gf9N/qbdl8GgfGbmYuuSIB8arhl+gRZfCIWvT5NhIRVlG5GX0lkgpp02lA8ZYWZ0GLGOkwz7+DMQ==}
dependencies:
muggle-string: 0.1.0
muggle-string: 0.2.2
dev: true
/@volar/typescript/1.0.24:
resolution: {integrity: sha512-f8hCSk+PfKR1/RQHxZ79V1NpDImHoivqoizK+mstphm25tn/YJ/JnKNjZHB+o21fuW0yKlI26NV3jkVb2Cc/7A==}
/@volar/typescript/1.2.0-alpha.11:
resolution: {integrity: sha512-tJ20326E/Xi1lvvuWX57boVJtzhStNF3HjBu4orjl9PqCXUbhqWwP+jRYzyb+nLbHqGPmEBvHKYjAO3GsJ/YXg==}
dependencies:
'@volar/language-core': 1.0.24
'@volar/language-core': 1.2.0-alpha.11
dev: true
/@volar/vue-language-core/1.0.24:
resolution: {integrity: sha512-2NTJzSgrwKu6uYwPqLiTMuAzi7fAY3yFy5PJ255bGJc82If0Xr+cW8pC80vpjG0D/aVLmlwAdO4+Ya2BI8GdDg==}
/@volar/vue-language-core/1.1.0:
resolution: {integrity: sha512-1zTAyeGiyNKYE9s+i3dUpmuvY/Cz1U7LjIh9d5FX3p0NWpaBrzYvSh0gQY+nRaz67or7Y9qYSUCaHLKOmeolzg==}
dependencies:
'@volar/language-core': 1.0.24
'@volar/source-map': 1.0.24
'@volar/language-core': 1.2.0-alpha.11
'@volar/source-map': 1.2.0-alpha.11
'@vue/compiler-dom': 3.2.47
'@vue/compiler-sfc': 3.2.47
'@vue/reactivity': 3.2.47
'@vue/shared': 3.2.47
minimatch: 5.1.6
minimatch: 6.2.0
muggle-string: 0.2.2
vue-template-compiler: 2.7.14
dev: true
/@volar/vue-typescript/1.0.24:
resolution: {integrity: sha512-9a25oHDvGaNC0okRS47uqJI6FxY4hUQZUsxeOUFHcqVxZEv8s17LPuP/pMMXyz7jPygrZubB/qXqHY5jEu/akA==}
/@volar/vue-typescript/1.1.0:
resolution: {integrity: sha512-smtfaePuNpVzXEypJayORtl8muvBdtV1FDWjces1WLYbbtcnmfWtdACW9xY0dkVk0LoE/LZTEmLBCQrRJ6hS1w==}
dependencies:
'@volar/typescript': 1.0.24
'@volar/vue-language-core': 1.0.24
'@volar/typescript': 1.2.0-alpha.11
'@volar/vue-language-core': 1.1.0
dev: true
/@vue/compiler-core/3.2.47:
@@ -1012,6 +1021,31 @@ 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==}
dependencies:
'@types/web-bluetooth': 0.0.16
'@vueuse/metadata': 9.12.0
'@vueuse/shared': 9.12.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==}
dev: false
/@vueuse/shared/9.12.0_vue@3.2.47:
resolution: {integrity: sha512-TWuJLACQ0BVithVTRbex4Wf1a1VaRuSpVeyEd4vMUWl54PzlE0ciFUshKCXnlLuD0lxIaLK4Ypj3NXYzZh4+SQ==}
dependencies:
vue-demi: 0.13.11_vue@3.2.47
transitivePeerDependencies:
- '@vue/composition-api'
- vue
dev: false
/JSONStream/1.3.5:
resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
hasBin: true
@@ -1490,6 +1524,12 @@ packages:
through2: 4.0.2
dev: true
/copy-anything/2.0.6:
resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
dependencies:
is-what: 3.14.1
dev: true
/cosmiconfig-typescript-loader/4.3.0_p7cp6dsfhdrlk7mvuxd3wodbsu:
resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==}
engines: {node: '>=12', npm: '>=6'}
@@ -1741,6 +1781,15 @@ packages:
engines: {node: '>=0.12'}
dev: true
/errno/0.1.8:
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
hasBin: true
requiresBuild: true
dependencies:
prr: 1.0.1
dev: true
optional: true
/error-ex/1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
dependencies:
@@ -2634,11 +2683,27 @@ packages:
hasBin: true
dev: true
/iconv-lite/0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
dependencies:
safer-buffer: 2.1.2
dev: true
optional: true
/ignore/5.2.4:
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
engines: {node: '>= 4'}
dev: true
/image-size/0.5.5:
resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==}
engines: {node: '>=0.10.0'}
hasBin: true
requiresBuild: true
dev: true
optional: true
/import-fresh/3.3.0:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'}
@@ -2874,6 +2939,10 @@ packages:
call-bind: 1.0.2
dev: true
/is-what/3.14.1:
resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
dev: true
/isexe/2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
@@ -2959,6 +3028,26 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/less/4.1.3:
resolution: {integrity: sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==}
engines: {node: '>=6'}
hasBin: true
dependencies:
copy-anything: 2.0.6
parse-node-version: 1.0.1
tslib: 2.5.0
optionalDependencies:
errno: 0.1.8
graceful-fs: 4.2.10
image-size: 0.5.5
make-dir: 2.1.0
mime: 1.6.0
needle: 3.2.0
source-map: 0.6.1
transitivePeerDependencies:
- supports-color
dev: true
/levn/0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
@@ -3116,6 +3205,16 @@ packages:
dependencies:
sourcemap-codec: 1.4.8
/make-dir/2.1.0:
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
engines: {node: '>=6'}
requiresBuild: true
dependencies:
pify: 4.0.1
semver: 5.7.1
dev: true
optional: true
/make-error/1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
@@ -3206,6 +3305,14 @@ packages:
mime-db: 1.52.0
dev: true
/mime/1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'}
hasBin: true
requiresBuild: true
dev: true
optional: true
/mimic-fn/2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
@@ -3227,8 +3334,8 @@ packages:
brace-expansion: 1.1.11
dev: true
/minimatch/5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
/minimatch/6.2.0:
resolution: {integrity: sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==}
engines: {node: '>=10'}
dependencies:
brace-expansion: 2.0.1
@@ -3255,8 +3362,8 @@ packages:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
dev: true
/muggle-string/0.1.0:
resolution: {integrity: sha512-Tr1knR3d2mKvvWthlk7202rywKbiOm4rVFLsfAaSIhJ6dt9o47W4S+JMtWhd/PW9Wrdew2/S2fSvhz3E2gkfEg==}
/muggle-string/0.2.2:
resolution: {integrity: sha512-YVE1mIJ4VpUMqZObFndk9CJu6DBJR/GB13p3tXuNbwD4XExaI5EOuRl6BHeIDxIqXZVxSfAC+y6U1Z/IxCfKUg==}
dev: true
/naive-ui/2.34.3_vue@3.2.47:
@@ -3298,6 +3405,20 @@ packages:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: true
/needle/3.2.0:
resolution: {integrity: sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==}
engines: {node: '>= 4.4.x'}
hasBin: true
requiresBuild: true
dependencies:
debug: 3.2.7
iconv-lite: 0.6.3
sax: 1.2.4
transitivePeerDependencies:
- supports-color
dev: true
optional: true
/nice-try/1.0.5:
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
dev: true
@@ -3512,6 +3633,11 @@ packages:
lines-and-columns: 1.2.4
dev: true
/parse-node-version/1.0.1:
resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
engines: {node: '>= 0.10'}
dev: true
/path-exists/4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -3583,6 +3709,12 @@ packages:
engines: {node: '>=4'}
dev: true
/pify/4.0.1:
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
engines: {node: '>=6'}
dev: true
optional: true
/pinia/2.0.30_hmuptsblhheur2tugfgucj7gc4:
resolution: {integrity: sha512-q6DUmxWwe/mQgg+55QQjykpKC+aGeGdaJV3niminl19V08dE+LRTvSEuqi6/NLSGCKHI49KGL6tMNEOssFiMyA==}
peerDependencies:
@@ -3684,6 +3816,11 @@ packages:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
dev: true
/prr/1.0.1:
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
dev: true
optional: true
/punycode/2.3.0:
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
engines: {node: '>=6'}
@@ -3896,6 +4033,16 @@ packages:
regexp-tree: 0.1.24
dev: true
/safer-buffer/2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: true
optional: true
/sax/1.2.4:
resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
dev: true
optional: true
/seemly/0.3.6:
resolution: {integrity: sha512-lEV5VB8BUKTo/AfktXJcy+JeXns26ylbMkIUco8CYREsQijuz4mrXres2Q+vMLdwkuLxJdIPQ8IlCIxLYm71Yw==}
dev: false
@@ -4380,7 +4527,7 @@ packages:
vue: 3.2.47
dev: false
/vite/4.1.1_@types+node@18.13.0:
/vite/4.1.1_edl3ajnhen4c5iqs57t4gqrzly:
resolution: {integrity: sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
@@ -4407,6 +4554,7 @@ packages:
dependencies:
'@types/node': 18.13.0
esbuild: 0.16.17
less: 4.1.3
postcss: 8.4.21
resolve: 1.22.1
rollup: 3.15.0
@@ -4472,14 +4620,14 @@ packages:
he: 1.2.0
dev: true
/vue-tsc/1.0.24_typescript@4.9.5:
resolution: {integrity: sha512-mmU1s5SAqE1nByQAiQnao9oU4vX+mSdsgI8H57SfKH6UVzq/jP9+Dbi2GaV+0b4Cn361d2ln8m6xeU60ApiEXg==}
/vue-tsc/1.1.0_typescript@4.9.5:
resolution: {integrity: sha512-+JqTcuScA6OfyaVH3ezeCh2i2wRgzUScZ6EdTZ3AW69Nb+rmRyOAxmAjL6MPam8YCdwmmdfAUhmN/BNGVp5vQg==}
hasBin: true
peerDependencies:
typescript: '*'
dependencies:
'@volar/vue-language-core': 1.0.24
'@volar/vue-typescript': 1.0.24
'@volar/vue-language-core': 1.1.0
'@volar/vue-typescript': 1.1.0
typescript: 4.9.5
dev: true

View File

@@ -24,6 +24,8 @@
},
"dependencies": {
"chatgpt": "^4.3.2",
"dotenv": "^16.0.3",
"esno": "^0.16.3",
"express": "^4.18.2",
"isomorphic-fetch": "^3.0.0"
},
@@ -31,9 +33,7 @@
"@antfu/eslint-config": "^0.35.2",
"@types/express": "^4.17.17",
"@types/node": "^18.13.0",
"dotenv": "^16.0.3",
"eslint": "^8.34.0",
"esno": "^0.16.3",
"rimraf": "^4.1.2",
"tsup": "^6.6.2",
"typescript": "^4.9.5"

48
service/pnpm-lock.yaml generated
View File

@@ -16,6 +16,8 @@ specifiers:
dependencies:
chatgpt: 4.4.0
dotenv: 16.0.3
esno: 0.16.3
express: 4.18.2
isomorphic-fetch: 3.0.0
@@ -23,9 +25,7 @@ devDependencies:
'@antfu/eslint-config': 0.35.2_7kw3g6rralp5ps6mg3uyzz6azm
'@types/express': 4.17.17
'@types/node': 18.13.0
dotenv: 16.0.3
eslint: 8.34.0
esno: 0.16.3
rimraf: 4.1.2
tsup: 6.6.3_typescript@4.9.5
typescript: 4.9.5
@@ -154,21 +154,21 @@ packages:
dependencies:
'@esbuild-kit/core-utils': 3.1.0
get-tsconfig: 4.4.0
dev: true
dev: false
/@esbuild-kit/core-utils/3.1.0:
resolution: {integrity: sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw==}
dependencies:
esbuild: 0.17.8
source-map-support: 0.5.21
dev: true
dev: false
/@esbuild-kit/esm-loader/2.5.5:
resolution: {integrity: sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==}
dependencies:
'@esbuild-kit/core-utils': 3.1.0
get-tsconfig: 4.4.0
dev: true
dev: false
/@esbuild/android-arm/0.17.8:
resolution: {integrity: sha512-0/rb91GYKhrtbeglJXOhAv9RuYimgI8h623TplY2X+vA4EXnk3Zj1fXZreJ0J3OJJu1bwmb0W7g+2cT/d8/l/w==}
@@ -176,7 +176,6 @@ packages:
cpu: [arm]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-arm64/0.17.8:
@@ -185,7 +184,6 @@ packages:
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-x64/0.17.8:
@@ -194,7 +192,6 @@ packages:
cpu: [x64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-arm64/0.17.8:
@@ -203,7 +200,6 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-x64/0.17.8:
@@ -212,7 +208,6 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-arm64/0.17.8:
@@ -221,7 +216,6 @@ packages:
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-x64/0.17.8:
@@ -230,7 +224,6 @@ packages:
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm/0.17.8:
@@ -239,7 +232,6 @@ packages:
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm64/0.17.8:
@@ -248,7 +240,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ia32/0.17.8:
@@ -257,7 +248,6 @@ packages:
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-loong64/0.17.8:
@@ -266,7 +256,6 @@ packages:
cpu: [loong64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-mips64el/0.17.8:
@@ -275,7 +264,6 @@ packages:
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ppc64/0.17.8:
@@ -284,7 +272,6 @@ packages:
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-riscv64/0.17.8:
@@ -293,7 +280,6 @@ packages:
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-s390x/0.17.8:
@@ -302,7 +288,6 @@ packages:
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-x64/0.17.8:
@@ -311,7 +296,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/netbsd-x64/0.17.8:
@@ -320,7 +304,6 @@ packages:
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/openbsd-x64/0.17.8:
@@ -329,7 +312,6 @@ packages:
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/sunos-x64/0.17.8:
@@ -338,7 +320,6 @@ packages:
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-arm64/0.17.8:
@@ -347,7 +328,6 @@ packages:
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-ia32/0.17.8:
@@ -356,7 +336,6 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-x64/0.17.8:
@@ -365,7 +344,6 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@eslint-community/eslint-utils/4.1.2_eslint@8.34.0:
@@ -805,7 +783,7 @@ packages:
/buffer-from/1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
dev: true
dev: false
/builtin-modules/3.3.0:
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
@@ -1090,7 +1068,7 @@ packages:
/dotenv/16.0.3:
resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
engines: {node: '>=12'}
dev: true
dev: false
/ee-first/1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
@@ -1203,7 +1181,6 @@ packages:
'@esbuild/win32-arm64': 0.17.8
'@esbuild/win32-ia32': 0.17.8
'@esbuild/win32-x64': 0.17.8
dev: true
/escape-html/1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
@@ -1584,7 +1561,7 @@ packages:
hasBin: true
dependencies:
tsx: 3.12.3
dev: true
dev: false
/espree/9.4.1:
resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==}
@@ -1799,7 +1776,6 @@ packages:
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/function-bind/1.1.1:
@@ -1841,7 +1817,7 @@ packages:
/get-tsconfig/4.4.0:
resolution: {integrity: sha512-0Gdjo/9+FzsYhXCEFueo2aY1z1tpXrxWZzP7k8ul9qt1U5o8rYJwTJYmaeHdrVosYIVYkOy2iwCJ9FdpocJhPQ==}
dev: true
dev: false
/glob-parent/5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
@@ -2977,12 +2953,12 @@ packages:
dependencies:
buffer-from: 1.1.2
source-map: 0.6.1
dev: true
dev: false
/source-map/0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
dev: true
dev: false
/source-map/0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
@@ -3211,7 +3187,7 @@ packages:
'@esbuild-kit/esm-loader': 2.5.5
optionalDependencies:
fsevents: 2.3.2
dev: true
dev: false
/type-check/0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}

View File

@@ -3,6 +3,11 @@ import 'isomorphic-fetch'
import type { ChatGPTAPI, SendMessageOptions } from 'chatgpt'
import { sendResponse } from './utils'
export interface ChatContext {
conversationId?: string
parentMessageId?: string
}
dotenv.config()
const apiKey = process.env.OPENAI_API_KEY

View File

@@ -3,7 +3,9 @@ import type { ChatContext } from './chatgpt'
import { chatReply } from './chatgpt'
const app = express()
const router = express.Router()
app.use(express.static('public'))
app.use(express.json())
app.all('*', (_, res, next) => {
@@ -13,7 +15,7 @@ app.all('*', (_, res, next) => {
next()
})
app.post('/chat', async (req, res) => {
router.post('/chat', async (req, res) => {
try {
const { prompt, options = {} } = req.body as { prompt: string; options?: ChatContext }
const response = await chatReply(prompt, options)
@@ -24,4 +26,7 @@ app.post('/chat', async (req, res) => {
}
})
app.use('', router)
app.use('/api', router)
app.listen(3002, () => globalThis.console.log('Server is running on port 3002'))

View File

@@ -1,8 +1,6 @@
import type { GenericAbortSignal } from 'axios'
import { post } from '@/utils/request'
export const controller = new AbortController()
export function fetchChatAPI<T = any>(
prompt: string,
options?: { conversationId?: string; parentMessageId?: string },

View File

@@ -1,5 +1,6 @@
<script setup lang='ts'>
import { computed, nextTick, onMounted, ref, watch } from 'vue'
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'
@@ -7,6 +8,7 @@ import { useChat } from './hooks/useChat'
import { fetchChatAPI } from '@/api'
import { HoverButton, SvgIcon } from '@/components/common'
import { useHistoryStore } from '@/store'
import { useBasicLayout } from '@/hooks/useBasicLayout'
let controller = new AbortController()
@@ -14,6 +16,10 @@ const ms = useMessage()
const historyStore = useHistoryStore()
const { isMobile } = useBasicLayout()
let messageReactive: MessageReactive | null = null
const scrollRef = ref<HTMLDivElement>()
const { addChat, clearChat } = useChat()
@@ -27,6 +33,12 @@ 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)))
const footerMobileStyle = computed(() => {
if (isMobile.value)
return ['pl-2', 'pt-2', 'pb-6', 'fixed', 'bottom-0', 'left-0', 'right-0', 'z-30']
return []
})
async function handleSubmit() {
if (loading.value)
return
@@ -51,6 +63,7 @@ async function handleSubmit() {
try {
loading.value = true
createMessage()
const { data } = await fetchChatAPI(message, options, controller.signal)
addMessage(data?.text ?? '', { options: { conversationId: data.conversationId, parentMessageId: data.id } })
}
@@ -60,12 +73,15 @@ async function handleSubmit() {
}
finally {
loading.value = false
removeMessage()
}
}
function handleEnter(event: KeyboardEvent) {
if (event.key === 'Enter')
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault()
handleSubmit()
}
}
function addMessage(
@@ -89,12 +105,32 @@ 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,
() => {
@@ -118,24 +154,49 @@ watch(
<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">
<div>
<Message
v-for="(item, index) of list" :key="index" :date-time="item.dateTime" :message="item.message"
:reversal="item.reversal" :error="item.error"
/>
</div>
<div
ref="scrollRef"
class="h-full p-4 overflow-hidden overflow-y-auto"
:class="[{ 'p-2': isMobile }]"
>
<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">
<footer
class="p-4"
:class="footerMobileStyle"
>
<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" :loading="loading" @click="handleSubmit">
<NInput
v-model:value="prompt"
type="textarea"
:autosize="{ minRows: 1, maxRows: 2 }"
placeholder="Ask me anything..."
@keypress="handleEnter"
/>
<NButton type="primary" :disabled="loading" @click="handleSubmit">
<template #icon>
<SvgIcon icon="ri:send-plane-fill" />
</template>

View File

@@ -1,15 +1,43 @@
<script setup lang='ts'>
import { computed } from 'vue'
import { NLayout, NLayoutContent } from 'naive-ui'
import Sider from './sider/index.vue'
import Header from './header/index.vue'
import { useBasicLayout } from '@/hooks/useBasicLayout'
import { useAppStore } from '@/store'
const appStore = useAppStore()
const { isMobile } = useBasicLayout()
const collapsed = computed(() => appStore.siderCollapsed)
const getMobileClass = computed(() => {
if (isMobile.value)
return ['rounded-none', 'shadow-none']
return ['border', 'rounded-md', 'shadow-md']
})
const getContainerClass = computed(() => {
return [
'h-full',
{ 'pt-14': isMobile.value },
{ 'pb-[70px]': isMobile.value },
{ 'pl-[260px]': !isMobile.value && !collapsed.value },
]
})
</script>
<template>
<div class="h-full overflow-hidden border rounded-md shadow-md">
<NLayout class="h-full" has-sider>
<Sider />
<NLayoutContent class="h-full">
<slot />
</NLayoutContent>
</NLayout>
<div class="h-screen" :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 />
</NLayoutContent>
</NLayout>
</div>
</div>
</template>

View File

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

View File

@@ -1,14 +0,0 @@
<script setup lang='ts'>
import { HoverButton, SvgIcon, UserAvatar } from '@/components/common'
</script>
<template>
<footer class="flex items-center justify-between min-w-0 p-4 overflow-hidden border-t h-[70px]">
<UserAvatar class="flex-1" />
<HoverButton tooltip="Setting">
<span class="text-xl text-[#4f555e]">
<SvgIcon icon="ri:settings-4-line" />
</span>
</HoverButton>
</footer>
</template>

View File

@@ -33,39 +33,47 @@ function handleEnter(index: number, isEdit: boolean, event: KeyboardEvent) {
<template>
<NScrollbar class="px-4">
<div class="flex flex-col gap-2 text-sm">
<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 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>

View File

@@ -1,14 +1,16 @@
<script setup lang='ts'>
import { ref } from 'vue'
import { computed, watch } from 'vue'
import { NButton, NLayoutSider } from 'naive-ui'
import List from './List.vue'
import Footer from './Footer.vue'
import { HoverButton, SvgIcon, UserAvatar } from '@/components/common'
import { useAppStore, useHistoryStore } from '@/store'
import { useBasicLayout } from '@/hooks/useBasicLayout'
const appStore = useAppStore()
const historyStore = useHistoryStore()
const { isMobile } = useBasicLayout()
const collapsed = ref(appStore.siderCollapsed ?? false)
const collapsed = computed(() => appStore.siderCollapsed)
function handleAdd() {
historyStore.addHistory({
@@ -18,10 +20,20 @@ function handleAdd() {
})
}
function handleCollapsed() {
collapsed.value = !collapsed.value
appStore.setSiderCollapsed(collapsed.value)
function handleUpdateCollapsed() {
appStore.setSiderCollapsed(!collapsed.value)
}
watch(
isMobile,
(val) => {
appStore.setSiderCollapsed(val)
},
{
immediate: true,
flush: 'post',
},
)
</script>
<template>
@@ -29,12 +41,14 @@ function handleCollapsed() {
:collapsed="collapsed"
:collapsed-width="0"
:width="260"
:show-trigger="isMobile ? false : 'arrow-circle'"
collapse-mode="transform"
show-trigger="arrow-circle"
position="absolute"
bordered
@update:collapsed="handleCollapsed"
style="z-index: 50;"
@update-collapsed="handleUpdateCollapsed"
>
<div class="flex flex-col h-full">
<div class="flex flex-col h-full" :class="[{ 'pt-14': isMobile }]">
<main class="flex-1 min-h-0 overflow-hidden">
<div class="p-4">
<NButton dashed block @click="handleAdd">
@@ -43,7 +57,17 @@ function handleCollapsed() {
</div>
<List />
</main>
<Footer />
<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>
</div>
</NLayoutSider>
<template v-if="isMobile">
<div v-show="!collapsed" class="absolute inset-0 z-40 bg-black/40" @click="handleUpdateCollapsed" />
</template>
</template>

View File

@@ -17,7 +17,3 @@ import { GithubSite } from '@/components/custom'
</div>
</div>
</template>
<style>
</style>

View File

@@ -0,0 +1,8 @@
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
export function useBasicLayout() {
const breakpoints = useBreakpoints(breakpointsTailwind)
const isMobile = breakpoints.smaller('sm')
return { isMobile }
}

View File

@@ -9,8 +9,5 @@ export const useAppStore = defineStore('app-store', {
this.siderCollapsed = collapsed
setLocalSetting(this.$state)
},
toggleSiderCollapse() {
this.setSiderCollapsed(!this.siderCollapsed)
},
},
})

View File

@@ -14,7 +14,7 @@ export function isNull<T extends null>(value: T | unknown): value is null {
return Object.prototype.toString.call(value) === '[object Null]'
}
export function isUndefine<T extends undefined>(value: T | unknown): value is undefined {
export function isUndefined<T extends undefined>(value: T | unknown): value is undefined {
return Object.prototype.toString.call(value) === '[object Undefined]'
}

View File

@@ -11,10 +11,6 @@ export interface HttpOption {
afterRequest?: () => void
}
export interface ExtraOption {
notification?: boolean
}
export interface Response<T = any> {
data: T
message: string | null

View File

@@ -3,7 +3,5 @@ import { Chat } from '@/components/business'
</script>
<template>
<div class="h-full p-4 overflow-hidden">
<Chat />
</div>
<Chat />
</template>

11
start.sh Normal file
View File

@@ -0,0 +1,11 @@
cd ./service
nohup pnpm start > service.log &
echo "Start service complete!"
cd ..
echo "" > front.log
nohup pnpm dev > front.log &
echo "Start front complete!"
tail -f front.log

View File

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