Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac9536ab87 | ||
|
|
938c91f635 | ||
|
|
4ab9f709de | ||
|
|
cb90d81c69 |
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,3 +1,21 @@
|
|||||||
|
## 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
|
## v2.5.0
|
||||||
|
|
||||||
`2023-02-20`
|
`2023-02-20`
|
||||||
|
|||||||
29
README.md
29
README.md
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
使用 express 和 vue3 搭建的 ChartGPT 演示网页
|
使用 express 和 vue3 搭建的 ChartGPT 演示网页
|
||||||
|
|
||||||

|

|
||||||
|

|
||||||
|
|
||||||
> 提示:目前 `OpenAI` 开放的模型最高只有 `GPT-3`,和现在网页所使用的 `GPT-3.5` 或 `GPT-4` 有很大差距,需要等官方开放最新的模型接口。
|
> 提示:目前 `OpenAI` 开放的模型最高只有 `GPT-3`,和现在网页所使用的 `GPT-3.5` 或 `GPT-4` 有很大差距,需要等官方开放最新的模型接口。
|
||||||
|
|
||||||
@@ -11,8 +12,6 @@
|
|||||||
|
|
||||||
[✓] 对代码等消息类型的格式化美化处理
|
[✓] 对代码等消息类型的格式化美化处理
|
||||||
|
|
||||||
[✗] 用户模块(注册、登录、个人中心)
|
|
||||||
|
|
||||||
[✗] 界面多语言
|
[✗] 界面多语言
|
||||||
|
|
||||||
[✗] 界面主题
|
[✗] 界面主题
|
||||||
@@ -61,7 +60,6 @@ pnpm install
|
|||||||
pnpm bootstrap
|
pnpm bootstrap
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## 运行
|
## 运行
|
||||||
### 后端服务
|
### 后端服务
|
||||||
|
|
||||||
@@ -78,15 +76,24 @@ pnpm dev
|
|||||||
```
|
```
|
||||||
|
|
||||||
## 打包
|
## 打包
|
||||||
## Docker build
|
|
||||||
|
|
||||||
[参考信息](https://github.com/Chanzhaoyu/chatgpt-web/pull/42)
|
### 使用 Docker
|
||||||
|
### Docker build & Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t chatgpt-web .
|
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
|
### Docker compose
|
||||||
|
|
||||||
[Hub 地址](https://hub.docker.com/repository/docker/chenzhaoyu94/chatgpt-web/general)
|
[Hub 地址](https://hub.docker.com/repository/docker/chenzhaoyu94/chatgpt-web/general)
|
||||||
|
|
||||||
@@ -102,10 +109,12 @@ services:
|
|||||||
OPENAI_API_KEY: xxxxxx
|
OPENAI_API_KEY: xxxxxx
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 手动打包
|
||||||
### 后端服务
|
### 后端服务
|
||||||
> 如果你不需要本项目的 `node` 接口,可以省略如下操作
|
> 如果你不需要本项目的 `node` 接口,可以省略如下操作
|
||||||
|
|
||||||
复制 `service` 文件夹到你有 `node` 服务环境的服务器上。(搜索关键字:`express部署`)
|
复制 `service` 文件夹到你有 `node` 服务环境的服务器上。
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# 安装
|
# 安装
|
||||||
@@ -122,7 +131,9 @@ PS: 不进行打包,直接在服务器上运行 `pnpm start` 也可
|
|||||||
|
|
||||||
### 前端打包
|
### 前端打包
|
||||||
|
|
||||||
根目录下运行以下命令,然后将 `dist` 文件夹复制到你的托管服务器上
|
1、修改根目录下 `.env` 内 `VITE_APP_API_BASE_URL` 为你的实际后端接口地址
|
||||||
|
|
||||||
|
2、根目录下运行以下命令,然后将 `dist` 文件夹内的文件复制到你网站服务的根目录下
|
||||||
|
|
||||||
[参考信息](https://cn.vitejs.dev/guide/static-deploy.html#building-the-app)
|
[参考信息](https://cn.vitejs.dev/guide/static-deploy.html#building-the-app)
|
||||||
|
|
||||||
|
|||||||
BIN
docs/cover.png
BIN
docs/cover.png
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 96 KiB |
BIN
docs/cover2.png
Normal file
BIN
docs/cover2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 518 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "chatgpt-web",
|
"name": "chatgpt-web",
|
||||||
"version": "2.5.0",
|
"version": "2.5.2",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "ChatGPT Web",
|
"description": "ChatGPT Web",
|
||||||
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
|
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vueuse/core": "^9.13.0",
|
"@vueuse/core": "^9.13.0",
|
||||||
"highlight.js": "^11.7.0",
|
"highlight.js": "^11.7.0",
|
||||||
|
"marked": "^4.2.12",
|
||||||
"naive-ui": "^2.34.3",
|
"naive-ui": "^2.34.3",
|
||||||
"pinia": "^2.0.30",
|
"pinia": "^2.0.30",
|
||||||
"vue": "^3.2.47",
|
"vue": "^3.2.47",
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
"@commitlint/config-conventional": "^17.4.4",
|
"@commitlint/config-conventional": "^17.4.4",
|
||||||
"@iconify/vue": "^4.1.0",
|
"@iconify/vue": "^4.1.0",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
|
"@types/marked": "^4.0.8",
|
||||||
"@types/node": "^18.14.0",
|
"@types/node": "^18.14.0",
|
||||||
"@types/web-bluetooth": "^0.0.16",
|
"@types/web-bluetooth": "^0.0.16",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
|
|||||||
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@@ -6,6 +6,7 @@ specifiers:
|
|||||||
'@commitlint/config-conventional': ^17.4.4
|
'@commitlint/config-conventional': ^17.4.4
|
||||||
'@iconify/vue': ^4.1.0
|
'@iconify/vue': ^4.1.0
|
||||||
'@types/crypto-js': ^4.1.1
|
'@types/crypto-js': ^4.1.1
|
||||||
|
'@types/marked': ^4.0.8
|
||||||
'@types/node': ^18.14.0
|
'@types/node': ^18.14.0
|
||||||
'@types/web-bluetooth': ^0.0.16
|
'@types/web-bluetooth': ^0.0.16
|
||||||
'@vitejs/plugin-vue': ^4.0.0
|
'@vitejs/plugin-vue': ^4.0.0
|
||||||
@@ -18,6 +19,7 @@ specifiers:
|
|||||||
husky: ^8.0.3
|
husky: ^8.0.3
|
||||||
less: ^4.1.3
|
less: ^4.1.3
|
||||||
lint-staged: ^13.1.2
|
lint-staged: ^13.1.2
|
||||||
|
marked: ^4.2.12
|
||||||
naive-ui: ^2.34.3
|
naive-ui: ^2.34.3
|
||||||
npm-run-all: ^4.1.5
|
npm-run-all: ^4.1.5
|
||||||
pinia: ^2.0.30
|
pinia: ^2.0.30
|
||||||
@@ -33,6 +35,7 @@ specifiers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@vueuse/core': 9.13.0_vue@3.2.47
|
'@vueuse/core': 9.13.0_vue@3.2.47
|
||||||
highlight.js: 11.7.0
|
highlight.js: 11.7.0
|
||||||
|
marked: 4.2.12
|
||||||
naive-ui: 2.34.3_vue@3.2.47
|
naive-ui: 2.34.3_vue@3.2.47
|
||||||
pinia: 2.0.30_hmuptsblhheur2tugfgucj7gc4
|
pinia: 2.0.30_hmuptsblhheur2tugfgucj7gc4
|
||||||
vue: 3.2.47
|
vue: 3.2.47
|
||||||
@@ -44,6 +47,7 @@ devDependencies:
|
|||||||
'@commitlint/config-conventional': 17.4.4
|
'@commitlint/config-conventional': 17.4.4
|
||||||
'@iconify/vue': 4.1.0_vue@3.2.47
|
'@iconify/vue': 4.1.0_vue@3.2.47
|
||||||
'@types/crypto-js': 4.1.1
|
'@types/crypto-js': 4.1.1
|
||||||
|
'@types/marked': 4.0.8
|
||||||
'@types/node': 18.14.0
|
'@types/node': 18.14.0
|
||||||
'@types/web-bluetooth': 0.0.16
|
'@types/web-bluetooth': 0.0.16
|
||||||
'@vitejs/plugin-vue': 4.0.0_vite@4.1.2+vue@3.2.47
|
'@vitejs/plugin-vue': 4.0.0_vite@4.1.2+vue@3.2.47
|
||||||
@@ -735,6 +739,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
|
resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/marked/4.0.8:
|
||||||
|
resolution: {integrity: sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/mdast/3.0.10:
|
/@types/mdast/3.0.10:
|
||||||
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
|
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3229,6 +3237,12 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/marked/4.2.12:
|
||||||
|
resolution: {integrity: sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==}
|
||||||
|
engines: {node: '>= 12'}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/mdast-util-from-markdown/0.8.5:
|
/mdast-util-from-markdown/0.8.5:
|
||||||
resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==}
|
resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { App, Directive } from 'vue'
|
import type { App, Directive } from 'vue'
|
||||||
import hljs from 'highlight.js'
|
import hljs from 'highlight.js'
|
||||||
|
import includeCode from '@/utils/functions/includeCode'
|
||||||
|
|
||||||
function highlightCode(el: HTMLElement) {
|
function highlightCode(el: HTMLElement) {
|
||||||
const regexp = /^(?:\s{4}|\t).+/gm
|
if (includeCode(el.textContent))
|
||||||
if (el.textContent?.indexOf(' = ') !== -1 || el.textContent.match(regexp))
|
|
||||||
hljs.highlightBlock(el)
|
hljs.highlightBlock(el)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
import type { RouteRecordRaw } from 'vue-router'
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
import { ChatLayout } from '@/views/chat/layout'
|
import { ChatLayout } from '@/views/chat/layout'
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
@@ -20,7 +20,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export const router = createRouter({
|
export const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHashHistory(),
|
||||||
routes,
|
routes,
|
||||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export const useChatStore = defineStore('chat-store', {
|
|||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
addHistory(history: Chat.History, chatData: Chat.Chat[] = []) {
|
addHistory(history: Chat.History, chatData: Chat.Chat[] = []) {
|
||||||
this.history.push(history)
|
this.history.unshift(history)
|
||||||
this.chat.push({ uuid: history.uuid, data: chatData })
|
this.chat.unshift({ uuid: history.uuid, data: chatData })
|
||||||
this.active = history.uuid
|
this.active = history.uuid
|
||||||
this.reloadRoute(history.uuid)
|
this.reloadRoute(history.uuid)
|
||||||
},
|
},
|
||||||
@@ -63,9 +63,9 @@ export const useChatStore = defineStore('chat-store', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setActive(uuid: number) {
|
async setActive(uuid: number) {
|
||||||
this.active = uuid
|
this.active = uuid
|
||||||
this.reloadRoute(uuid)
|
return await this.reloadRoute(uuid)
|
||||||
},
|
},
|
||||||
|
|
||||||
addChatByUuid(uuid: number, chat: Chat.Chat) {
|
addChatByUuid(uuid: number, chat: Chat.Chat) {
|
||||||
|
|||||||
8
src/utils/functions/includeCode.ts
Normal file
8
src/utils/functions/includeCode.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
function includeCode(text: string | null | undefined) {
|
||||||
|
const regexp = /^(?:\s{4}|\t).+/gm
|
||||||
|
if (text?.includes(' = ') || text?.match(regexp))
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export default includeCode
|
||||||
@@ -1,28 +1,60 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { marked } from 'marked'
|
||||||
|
import includeCode from '@/utils/functions/includeCode'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
inversion?: boolean
|
inversion?: boolean
|
||||||
error?: boolean
|
error?: boolean
|
||||||
|
text?: string
|
||||||
|
loading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const wrapClass = computed(() => {
|
||||||
|
return [
|
||||||
|
'text-wrap',
|
||||||
|
'p-2',
|
||||||
|
'min-w-[20px]',
|
||||||
|
'rounded-md',
|
||||||
|
props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
|
||||||
|
{ 'text-red-500': props.error },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const text = computed(() => {
|
||||||
|
if (props.text) {
|
||||||
|
if (!includeCode(props.text))
|
||||||
|
return marked.parse(props.text)
|
||||||
|
return props.text
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div :class="wrapClass">
|
||||||
class="min-w-[20px] p-2 rounded-md"
|
<template v-if="loading">
|
||||||
:class="[inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]', { 'text-red-500': error }]"
|
<span class="w-[3px] h-[20px] block animate-blink" />
|
||||||
>
|
</template>
|
||||||
<span
|
<template v-else>
|
||||||
v-highlight
|
<code v-if="includeCode(text)" v-highlight class="leading-relaxed" v-text="text" />
|
||||||
class="leading-relaxed whitespace-pre-wrap"
|
<div v-else class="leading-relaxed break-all" v-html="text" />
|
||||||
>
|
</template>
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style lang="less">
|
||||||
|
.text-wrap{
|
||||||
|
img{
|
||||||
|
max-width: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.hljs {
|
.hljs {
|
||||||
background-color: #fff0 !important;
|
background-color: #fff0 !important;
|
||||||
|
white-space: break-spaces;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -37,10 +37,7 @@ function handleRegenerate() {
|
|||||||
{{ dateTime }}
|
{{ dateTime }}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex items-end mt-2">
|
<div class="flex items-end mt-2">
|
||||||
<Text :inversion="inversion" :error="error">
|
<Text :inversion="inversion" :error="error" :text="text" :loading="loading" />
|
||||||
<span v-if="loading" class="w-[3px] h-[20px] block animate-blink" />
|
|
||||||
<span v-else>{{ text }}</span>
|
|
||||||
</Text>
|
|
||||||
<button
|
<button
|
||||||
v-if="!inversion && !loading"
|
v-if="!inversion && !loading"
|
||||||
class="mb-2 ml-2 transition text-neutral-400 hover:text-neutral-800"
|
class="mb-2 ml-2 transition text-neutral-400 hover:text-neutral-800"
|
||||||
|
|||||||
@@ -153,7 +153,6 @@ async function onRegenerate(index: number) {
|
|||||||
requestOptions: { prompt: message, ...options },
|
requestOptions: { prompt: message, ...options },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
scrollToBottom()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await fetchChatAPI<Chat.ConversationResponse>(message, options, controller.signal)
|
const { data } = await fetchChatAPI<Chat.ConversationResponse>(message, options, controller.signal)
|
||||||
@@ -170,7 +169,6 @@ async function onRegenerate(index: number) {
|
|||||||
requestOptions: { prompt: message, ...options },
|
requestOptions: { prompt: message, ...options },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
scrollToBottom()
|
|
||||||
}
|
}
|
||||||
catch (error: any) {
|
catch (error: any) {
|
||||||
let errorMessage = 'Something went wrong, please try again later.'
|
let errorMessage = 'Something went wrong, please try again later.'
|
||||||
@@ -191,7 +189,6 @@ async function onRegenerate(index: number) {
|
|||||||
requestOptions: { prompt: message, ...options },
|
requestOptions: { prompt: message, ...options },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
scrollToBottom()
|
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -224,10 +221,17 @@ const buttonDisabled = computed(() => {
|
|||||||
return loading.value || !prompt.value || prompt.value.trim() === ''
|
return loading.value || !prompt.value || prompt.value.trim() === ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const wrapClass = computed(() => {
|
||||||
|
if (isMobile.value)
|
||||||
|
return ['pt-14', 'pb-14']
|
||||||
|
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
const footerClass = computed(() => {
|
const footerClass = computed(() => {
|
||||||
let classes = ['p-4']
|
let classes = ['p-4']
|
||||||
if (isMobile.value)
|
if (isMobile.value)
|
||||||
classes = [...classes, 'pl-2', 'pt-2', 'pb-2', 'fixed', 'bottom-0', 'left-0', 'right-0', 'z-30']
|
classes = ['p-2', 'pr-4', 'fixed', 'bottom-4', 'left-0', 'right-0', 'z-30', 'h-14', 'overflow-hidden']
|
||||||
return classes
|
return classes
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -242,7 +246,7 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col h-full">
|
<div class="flex flex-col h-full" :class="wrapClass">
|
||||||
<main class="flex-1 overflow-hidden">
|
<main class="flex-1 overflow-hidden">
|
||||||
<div ref="scrollRef" class="h-full p-4 overflow-hidden overflow-y-auto" :class="[{ 'p-2': isMobile }]">
|
<div ref="scrollRef" class="h-full p-4 overflow-hidden overflow-y-auto" :class="[{ 'p-2': isMobile }]">
|
||||||
<template v-if="!dataSources.length">
|
<template v-if="!dataSources.length">
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
<script setup lang='ts'>
|
<script setup lang='ts'>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { NLayout, NLayoutContent } from 'naive-ui'
|
import { NLayout, NLayoutContent } from 'naive-ui'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import Sider from './sider/index.vue'
|
import Sider from './sider/index.vue'
|
||||||
import Header from './header/index.vue'
|
import Header from './header/index.vue'
|
||||||
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
||||||
import { useAppStore } from '@/store'
|
import { useAppStore, useChatStore } from '@/store'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
const chatStore = useChatStore()
|
||||||
|
|
||||||
|
router.replace({ name: 'Chat', params: { uuid: chatStore.active } })
|
||||||
|
|
||||||
const { isMobile } = useBasicLayout()
|
const { isMobile } = useBasicLayout()
|
||||||
|
|
||||||
@@ -21,14 +26,13 @@ const getMobileClass = computed(() => {
|
|||||||
const getContainerClass = computed(() => {
|
const getContainerClass = computed(() => {
|
||||||
return [
|
return [
|
||||||
'h-full',
|
'h-full',
|
||||||
{ 'pt-14': isMobile.value },
|
|
||||||
{ 'pl-[260px]': !isMobile.value && !collapsed.value },
|
{ 'pl-[260px]': !isMobile.value && !collapsed.value },
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-screen" :class="[isMobile ? 'p-0' : 'p-4']">
|
<div class="h-full" :class="[isMobile ? 'p-0' : 'p-4']">
|
||||||
<div class="h-full overflow-hidden" :class="getMobileClass">
|
<div class="h-full overflow-hidden" :class="getMobileClass">
|
||||||
<NLayout class="z-40 transition" :class="getContainerClass" has-sider>
|
<NLayout class="z-40 transition" :class="getContainerClass" has-sider>
|
||||||
<Sider />
|
<Sider />
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function handleUpdateCollapsed() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header class="fixed top-0 left-0 right-0 z-50 border-b bg-white/80 backdrop-blur">
|
<header class="fixed top-0 left-0 right-0 z-30 border-b bg-white/80 backdrop-blur">
|
||||||
<div class="relative flex items-center justify-between h-14">
|
<div class="relative flex items-center justify-between h-14">
|
||||||
<button class="flex items-center justify-center w-11 h-11" @click="handleUpdateCollapsed">
|
<button class="flex items-center justify-center w-11 h-11" @click="handleUpdateCollapsed">
|
||||||
<SvgIcon v-if="collapsed" class="text-2xl" icon="ri:align-justify" />
|
<SvgIcon v-if="collapsed" class="text-2xl" icon="ri:align-justify" />
|
||||||
|
|||||||
@@ -2,8 +2,12 @@
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { NInput, NPopconfirm, NScrollbar } from 'naive-ui'
|
import { NInput, NPopconfirm, NScrollbar } from 'naive-ui'
|
||||||
import { SvgIcon } from '@/components/common'
|
import { SvgIcon } from '@/components/common'
|
||||||
import { useChatStore } from '@/store'
|
import { useAppStore, useChatStore } from '@/store'
|
||||||
|
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
||||||
|
|
||||||
|
const { isMobile } = useBasicLayout()
|
||||||
|
|
||||||
|
const appStore = useAppStore()
|
||||||
const chatStore = useChatStore()
|
const chatStore = useChatStore()
|
||||||
|
|
||||||
const dataSources = computed(() => chatStore.history)
|
const dataSources = computed(() => chatStore.history)
|
||||||
@@ -12,7 +16,10 @@ async function handleSelect({ uuid }: Chat.History) {
|
|||||||
if (isActive(uuid))
|
if (isActive(uuid))
|
||||||
return
|
return
|
||||||
|
|
||||||
chatStore.setActive(uuid)
|
await chatStore.setActive(uuid)
|
||||||
|
|
||||||
|
if (isMobile.value)
|
||||||
|
appStore.setSiderCollapsed(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEdit({ uuid }: Chat.History, isEdit: boolean, event?: MouseEvent) {
|
function handleEdit({ uuid }: Chat.History, isEdit: boolean, event?: MouseEvent) {
|
||||||
|
|||||||
@@ -56,14 +56,16 @@ watch(
|
|||||||
:style="getMobileClass"
|
:style="getMobileClass"
|
||||||
@update-collapsed="handleUpdateCollapsed"
|
@update-collapsed="handleUpdateCollapsed"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col h-full" :class="[{ 'pt-14': isMobile }]">
|
<div class="flex flex-col h-full">
|
||||||
<main class="flex-1 min-h-0 overflow-hidden">
|
<main class="flex flex-col flex-1 min-h-0">
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<NButton dashed block @click="handleAdd">
|
<NButton dashed block @click="handleAdd">
|
||||||
New chat
|
New chat
|
||||||
</NButton>
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
<List />
|
<div class="flex-1 min-h-0 pb-4 overflow-hidden">
|
||||||
|
<List />
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<footer class="flex items-center justify-between min-w-0 p-4 overflow-hidden border-t">
|
<footer class="flex items-center justify-between min-w-0 p-4 overflow-hidden border-t">
|
||||||
<UserAvatar />
|
<UserAvatar />
|
||||||
|
|||||||
Reference in New Issue
Block a user