feat: 响应式支持移动端 (#49)
* feat: 响应式兼容 h5 * feat: 补充空状态 * feat: thinking * chore: @vueuse/core 导致的类型检查错误 * chore: version 2.4.0
This commit is contained in:
parent
405aeaa2a5
commit
3f4cb5c900
|
@ -1,3 +1,11 @@
|
|||
## v2.4.0
|
||||
|
||||
`2023-02-17`
|
||||
|
||||
### Feature
|
||||
- 响应式支持移动端
|
||||
### Enhancement
|
||||
- 修改部份描述错误
|
||||
## v2.3.3
|
||||
|
||||
`2023-02-16`
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
使用 express 和 vue3 搭建的 ChartGPT 演示网页
|
||||
|
||||
![cover](./docs/cover-2.png)
|
||||
![PC](./docs/c1.png)
|
||||
|
||||
![Mobile](./docs/c2.png)
|
||||
|
||||
> 提示:目前 `OpenAI` 开放的模型最高只有 `GPT-3`,和现在网页所使用的 `GPT-3.5` 或 `GPT-4` 有很大差距,需要等官方开放最新的模型接口。
|
||||
|
||||
|
@ -112,9 +114,9 @@ docker build -t chatgpt-web .
|
|||
```yml
|
||||
version: '3'
|
||||
|
||||
service:
|
||||
services:
|
||||
app:
|
||||
image: chenzhaoyu94/chatgpt-web
|
||||
image: chenzhaoyu94/chatgpt-web:main
|
||||
ports:
|
||||
- 3002:3002
|
||||
environment:
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 188 KiB |
Binary file not shown.
After Width: | Height: | Size: 136 KiB |
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "chatgpt-web",
|
||||
"version": "2.3.3",
|
||||
"version": "2.4.0",
|
||||
"private": false,
|
||||
"description": "ChatGPT Web",
|
||||
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
|
||||
|
@ -23,6 +23,7 @@
|
|||
"common:prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^9.12.0",
|
||||
"highlight.js": "^11.7.0",
|
||||
"naive-ui": "^2.34.3",
|
||||
"pinia": "^2.0.30",
|
||||
|
@ -36,12 +37,14 @@
|
|||
"@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.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",
|
||||
|
|
154
pnpm-lock.yaml
154
pnpm-lock.yaml
|
@ -7,13 +7,16 @@ 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.3
|
||||
crypto-js: ^4.1.1
|
||||
eslint: ^8.34.0
|
||||
highlight.js: ^11.7.0
|
||||
husky: ^8.0.3
|
||||
less: ^4.1.3
|
||||
lint-staged: ^13.1.2
|
||||
naive-ui: ^2.34.3
|
||||
npm-run-all: ^4.1.5
|
||||
|
@ -28,6 +31,7 @@ specifiers:
|
|||
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,19 +45,21 @@ 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
|
||||
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,7 +901,7 @@ 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
|
||||
|
||||
|
@ -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'}
|
||||
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
@ -14,6 +15,8 @@ const ms = useMessage()
|
|||
|
||||
const historyStore = useHistoryStore()
|
||||
|
||||
let messageReactive: MessageReactive | null = null
|
||||
|
||||
const scrollRef = ref<HTMLDivElement>()
|
||||
|
||||
const { addChat, clearChat } = useChat()
|
||||
|
@ -51,6 +54,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,6 +64,7 @@ async function handleSubmit() {
|
|||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
removeMessage()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,12 +94,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,
|
||||
() => {
|
||||
|
@ -119,12 +144,20 @@ watch(
|
|||
<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>
|
||||
<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">
|
||||
|
@ -135,7 +168,7 @@ watch(
|
|||
</span>
|
||||
</HoverButton>
|
||||
<NInput v-model:value="prompt" placeholder="Type a message..." @keypress="handleEnter" />
|
||||
<NButton type="primary" :loading="loading" @click="handleSubmit">
|
||||
<NButton type="primary" :disabled="loading" @click="handleSubmit">
|
||||
<template #icon>
|
||||
<SvgIcon icon="ri:send-plane-fill" />
|
||||
</template>
|
||||
|
|
|
@ -1,15 +1,42 @@
|
|||
<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 },
|
||||
{ '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 p-4" :class="[{ 'p-0': isMobile }]">
|
||||
<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>
|
||||
|
|
|
@ -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 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,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>
|
|
@ -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>
|
||||
|
|
|
@ -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,17 @@ function handleAdd() {
|
|||
})
|
||||
}
|
||||
|
||||
function handleCollapsed() {
|
||||
collapsed.value = !collapsed.value
|
||||
appStore.setSiderCollapsed(collapsed.value)
|
||||
function handleUpdateCollapsed() {
|
||||
appStore.setSiderCollapsed(!collapsed.value)
|
||||
}
|
||||
|
||||
watch(
|
||||
isMobile,
|
||||
(val) => {
|
||||
appStore.setSiderCollapsed(val)
|
||||
},
|
||||
{ flush: 'post' },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -29,12 +38,13 @@ 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"
|
||||
@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 +53,14 @@ 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>
|
||||
|
|
|
@ -17,7 +17,3 @@ import { GithubSite } from '@/components/custom'
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
|
||||
|
||||
export function useBasicLayout() {
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||
const isMobile = breakpoints.smaller('sm')
|
||||
|
||||
return { isMobile }
|
||||
}
|
|
@ -3,7 +3,5 @@ import { Chat } from '@/components/business'
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full p-4 overflow-hidden">
|
||||
<Chat />
|
||||
</div>
|
||||
<Chat />
|
||||
</template>
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue