Spaces:
Sleeping
Sleeping
chokiproai
commited on
Commit
•
3424203
1
Parent(s):
a81cbf3
Upload 135 files
Browse files- .env +2 -2
- .eslintignore +1 -1
- docker-compose/docker-compose.yml +1 -1
- docker-compose/nginx/nginx.conf +1 -1
- kubernetes/README.md +1 -1
- kubernetes/deploy.yaml +1 -1
- kubernetes/ingress.yaml +1 -1
- package-lock.json +0 -0
- package.json +1 -1
- pnpm-lock.yaml +1 -1
- public/favicon.ico +0 -0
- public/favicon.svg +1 -24
- public/pwa-192x192.png +0 -0
- public/pwa-512x512.png +0 -0
- service/.env.example +1 -3
- service/package.json +1 -1
- service/pnpm-lock.yaml +0 -0
- src/api/index.ts +1 -1
- src/components/common/PromptStore/index.vue +1 -1
- src/components/common/Setting/Advanced.vue +2 -2
- src/components/common/Setting/General.vue +5 -2
- src/components/common/Setting/index.vue +1 -1
- src/router/permission.ts +1 -1
- src/store/helper.ts +3 -0
- src/store/index.ts +1 -3
- src/store/modules/app/helper.ts +2 -2
- src/store/modules/app/index.ts +1 -1
- src/store/modules/auth/index.ts +1 -1
- src/store/modules/chat/index.ts +6 -1
- src/store/modules/settings/helper.ts +1 -1
- src/store/modules/settings/index.ts +3 -3
- src/store/modules/user/helper.ts +3 -3
- src/styles/lib/highlight.less +1 -1
- src/typings/env.d.ts +1 -1
- src/utils/functions/debounce.ts +1 -1
- src/utils/request/index.ts +1 -1
- src/utils/storage/index.ts +57 -1
- src/views/chat/components/Header/index.vue +8 -8
- src/views/chat/components/Message/Text.vue +2 -5
- src/views/chat/components/Message/index.vue +1 -1
- src/views/chat/components/Message/style.less +61 -1
- src/views/chat/index.vue +5 -5
- src/views/chat/layout/sider/List.vue +1 -1
- src/views/chat/layout/sider/index.vue +28 -6
- start.cmd +1 -1
- vite.config.ts +1 -1
.env
CHANGED
@@ -1,10 +1,10 @@
|
|
1 |
# Glob API URL
|
2 |
VITE_GLOB_API_URL=/api
|
3 |
|
4 |
-
VITE_APP_API_BASE_URL=http://
|
5 |
|
6 |
# Whether long replies are supported, which may result in higher API fees
|
7 |
VITE_GLOB_OPEN_LONG_REPLY=false
|
8 |
|
9 |
# When you want to use PWA
|
10 |
-
VITE_GLOB_APP_PWA=false
|
|
|
1 |
# Glob API URL
|
2 |
VITE_GLOB_API_URL=/api
|
3 |
|
4 |
+
VITE_APP_API_BASE_URL=http://127.0.0.1:3002/
|
5 |
|
6 |
# Whether long replies are supported, which may result in higher API fees
|
7 |
VITE_GLOB_OPEN_LONG_REPLY=false
|
8 |
|
9 |
# When you want to use PWA
|
10 |
+
VITE_GLOB_APP_PWA=false
|
.eslintignore
CHANGED
@@ -1,2 +1,2 @@
|
|
1 |
docker-compose
|
2 |
-
kubernetes
|
|
|
1 |
docker-compose
|
2 |
+
kubernetes
|
docker-compose/docker-compose.yml
CHANGED
@@ -44,4 +44,4 @@ services:
|
|
44 |
- ./nginx/html:/usr/share/nginx/html
|
45 |
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
|
46 |
links:
|
47 |
-
- app
|
|
|
44 |
- ./nginx/html:/usr/share/nginx/html
|
45 |
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
|
46 |
links:
|
47 |
+
- app
|
docker-compose/nginx/nginx.conf
CHANGED
@@ -24,4 +24,4 @@ server {
|
|
24 |
proxy_set_header X-Real-IP $remote_addr;
|
25 |
proxy_set_header REMOTE-HOST $remote_addr;
|
26 |
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
27 |
-
}
|
|
|
24 |
proxy_set_header X-Real-IP $remote_addr;
|
25 |
proxy_set_header REMOTE-HOST $remote_addr;
|
26 |
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
27 |
+
}
|
kubernetes/README.md
CHANGED
@@ -6,4 +6,4 @@ kubectl apply -f deploy.yaml
|
|
6 |
### 如果需要Ingress域名接入
|
7 |
```
|
8 |
kubectl apply -f ingress.yaml
|
9 |
-
```
|
|
|
6 |
### 如果需要Ingress域名接入
|
7 |
```
|
8 |
kubectl apply -f ingress.yaml
|
9 |
+
```
|
kubernetes/deploy.yaml
CHANGED
@@ -63,4 +63,4 @@ spec:
|
|
63 |
targetPort: 3002
|
64 |
selector:
|
65 |
app: chatgpt-web
|
66 |
-
type: ClusterIP
|
|
|
63 |
targetPort: 3002
|
64 |
selector:
|
65 |
app: chatgpt-web
|
66 |
+
type: ClusterIP
|
kubernetes/ingress.yaml
CHANGED
@@ -18,4 +18,4 @@ spec:
|
|
18 |
path: /
|
19 |
pathType: ImplementationSpecific
|
20 |
tls:
|
21 |
-
- secretName: chatgpt-web-tls
|
|
|
18 |
path: /
|
19 |
pathType: ImplementationSpecific
|
20 |
tls:
|
21 |
+
- secretName: chatgpt-web-tls
|
package-lock.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
{
|
2 |
"name": "chatgpt-web",
|
3 |
-
"version": "2.11.
|
4 |
"private": false,
|
5 |
"description": "ChatGPT Web",
|
6 |
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
|
|
|
1 |
{
|
2 |
"name": "chatgpt-web",
|
3 |
+
"version": "2.11.1",
|
4 |
"private": false,
|
5 |
"description": "ChatGPT Web",
|
6 |
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
|
pnpm-lock.yaml
CHANGED
@@ -6900,4 +6900,4 @@ packages:
|
|
6900 |
/yocto-queue@0.1.0:
|
6901 |
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
6902 |
engines: {node: '>=10'}
|
6903 |
-
dev: true
|
|
|
6900 |
/yocto-queue@0.1.0:
|
6901 |
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
6902 |
engines: {node: '>=10'}
|
6903 |
+
dev: true
|
public/favicon.ico
CHANGED
|
|
public/favicon.svg
CHANGED
|
|
public/pwa-192x192.png
CHANGED
![]() |
![]() |
public/pwa-512x512.png
CHANGED
![]() |
![]() |
service/.env.example
CHANGED
@@ -1,9 +1,6 @@
|
|
1 |
# OpenAI API Key - https://platform.openai.com/overview
|
2 |
OPENAI_API_KEY=
|
3 |
|
4 |
-
# OpenAI API Key ARR - https://platform.openai.com/overview
|
5 |
-
OPENAI_API_KEY_ARR=
|
6 |
-
|
7 |
# change this to an `accessToken` extracted from the ChatGPT site's `https://chat.openai.com/api/auth/session` response
|
8 |
OPENAI_ACCESS_TOKEN=
|
9 |
|
@@ -44,3 +41,4 @@ SOCKS_PROXY_PASSWORD=
|
|
44 |
|
45 |
# HTTPS PROXY
|
46 |
HTTPS_PROXY=
|
|
|
|
1 |
# OpenAI API Key - https://platform.openai.com/overview
|
2 |
OPENAI_API_KEY=
|
3 |
|
|
|
|
|
|
|
4 |
# change this to an `accessToken` extracted from the ChatGPT site's `https://chat.openai.com/api/auth/session` response
|
5 |
OPENAI_ACCESS_TOKEN=
|
6 |
|
|
|
41 |
|
42 |
# HTTPS PROXY
|
43 |
HTTPS_PROXY=
|
44 |
+
|
service/package.json
CHANGED
@@ -44,4 +44,4 @@
|
|
44 |
"tsup": "^6.6.3",
|
45 |
"typescript": "^4.9.5"
|
46 |
}
|
47 |
-
}
|
|
|
44 |
"tsup": "^6.6.3",
|
45 |
"typescript": "^4.9.5"
|
46 |
}
|
47 |
+
}
|
service/pnpm-lock.yaml
CHANGED
The diff for this file is too large to render.
See raw diff
|
|
src/api/index.ts
CHANGED
@@ -63,4 +63,4 @@ export function fetchVerify<T>(token: string) {
|
|
63 |
url: '/verify',
|
64 |
data: { token },
|
65 |
})
|
66 |
-
}
|
|
|
63 |
url: '/verify',
|
64 |
data: { token },
|
65 |
})
|
66 |
+
}
|
src/components/common/PromptStore/index.vue
CHANGED
@@ -477,4 +477,4 @@ const dataSource = computed(() => {
|
|
477 |
</NButton>
|
478 |
</NSpace>
|
479 |
</NModal>
|
480 |
-
</template>
|
|
|
477 |
</NButton>
|
478 |
</NSpace>
|
479 |
</NModal>
|
480 |
+
</template>
|
src/components/common/Setting/Advanced.vue
CHANGED
@@ -42,7 +42,7 @@ function handleReset() {
|
|
42 |
<div class="flex items-center space-x-4">
|
43 |
<span class="flex-shrink-0 w-[120px]">{{ $t('setting.temperature') }} </span>
|
44 |
<div class="flex-1">
|
45 |
-
<NSlider v-model:value="temperature" :max="
|
46 |
</div>
|
47 |
<span>{{ temperature }}</span>
|
48 |
<NButton size="tiny" text type="primary" @click="updateSettings({ temperature })">
|
@@ -67,4 +67,4 @@ function handleReset() {
|
|
67 |
</div>
|
68 |
</div>
|
69 |
</div>
|
70 |
-
</template>
|
|
|
42 |
<div class="flex items-center space-x-4">
|
43 |
<span class="flex-shrink-0 w-[120px]">{{ $t('setting.temperature') }} </span>
|
44 |
<div class="flex-1">
|
45 |
+
<NSlider v-model:value="temperature" :max="2" :min="0" :step="0.1" />
|
46 |
</div>
|
47 |
<span>{{ temperature }}</span>
|
48 |
<NButton size="tiny" text type="primary" @click="updateSettings({ temperature })">
|
|
|
67 |
</div>
|
68 |
</div>
|
69 |
</div>
|
70 |
+
</template>
|
src/components/common/Setting/General.vue
CHANGED
@@ -54,8 +54,11 @@ const themeOptions: { label: string; key: Theme; icon: string }[] = [
|
|
54 |
]
|
55 |
|
56 |
const languageOptions: { label: string; key: Language; value: Language }[] = [
|
57 |
-
{ label: '
|
|
|
58 |
{ label: 'English', key: 'en-US', value: 'en-US' },
|
|
|
|
|
59 |
]
|
60 |
|
61 |
function updateUserInfo(options: Partial<UserInfo>) {
|
@@ -219,4 +222,4 @@ function handleImportButtonClick(): void {
|
|
219 |
</div>
|
220 |
</div>
|
221 |
</div>
|
222 |
-
</template>
|
|
|
54 |
]
|
55 |
|
56 |
const languageOptions: { label: string; key: Language; value: Language }[] = [
|
57 |
+
{ label: '简体中文', key: 'zh-CN', value: 'zh-CN' },
|
58 |
+
{ label: '繁體中文', key: 'zh-TW', value: 'zh-TW' },
|
59 |
{ label: 'English', key: 'en-US', value: 'en-US' },
|
60 |
+
{ label: '한국어', key: 'ko-KR', value: 'ko-KR' },
|
61 |
+
{ label: 'Русский язык', key: 'ru-RU', value: 'ru-RU' },
|
62 |
]
|
63 |
|
64 |
function updateUserInfo(options: Partial<UserInfo>) {
|
|
|
222 |
</div>
|
223 |
</div>
|
224 |
</div>
|
225 |
+
</template>
|
src/components/common/Setting/index.vue
CHANGED
@@ -67,4 +67,4 @@ const show = computed({
|
|
67 |
</NTabs>
|
68 |
</div>
|
69 |
</NModal>
|
70 |
-
</template>
|
|
|
67 |
</NTabs>
|
68 |
</div>
|
69 |
</NModal>
|
70 |
+
</template>
|
src/router/permission.ts
CHANGED
@@ -25,4 +25,4 @@ export function setupPageGuard(router: Router) {
|
|
25 |
next()
|
26 |
}
|
27 |
})
|
28 |
-
}
|
|
|
25 |
next()
|
26 |
}
|
27 |
})
|
28 |
+
}
|
src/store/helper.ts
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
import { createPinia } from 'pinia'
|
2 |
+
|
3 |
+
export const store = createPinia()
|
src/store/index.ts
CHANGED
@@ -1,7 +1,5 @@
|
|
1 |
import type { App } from 'vue'
|
2 |
-
import {
|
3 |
-
|
4 |
-
export const store = createPinia()
|
5 |
|
6 |
export function setupStore(app: App) {
|
7 |
app.use(store)
|
|
|
1 |
import type { App } from 'vue'
|
2 |
+
import { store } from './helper'
|
|
|
|
|
3 |
|
4 |
export function setupStore(app: App) {
|
5 |
app.use(store)
|
src/store/modules/app/helper.ts
CHANGED
@@ -4,7 +4,7 @@ const LOCAL_NAME = 'appSetting'
|
|
4 |
|
5 |
export type Theme = 'light' | 'dark' | 'auto'
|
6 |
|
7 |
-
export type Language = '
|
8 |
|
9 |
export interface AppState {
|
10 |
siderCollapsed: boolean
|
@@ -13,7 +13,7 @@ export interface AppState {
|
|
13 |
}
|
14 |
|
15 |
export function defaultSetting(): AppState {
|
16 |
-
return { siderCollapsed: false, theme: 'light', language: '
|
17 |
}
|
18 |
|
19 |
export function getLocalSetting(): AppState {
|
|
|
4 |
|
5 |
export type Theme = 'light' | 'dark' | 'auto'
|
6 |
|
7 |
+
export type Language = 'zh-CN' | 'zh-TW' | 'en-US' | 'ko-KR' | 'ru-RU'
|
8 |
|
9 |
export interface AppState {
|
10 |
siderCollapsed: boolean
|
|
|
13 |
}
|
14 |
|
15 |
export function defaultSetting(): AppState {
|
16 |
+
return { siderCollapsed: false, theme: 'light', language: 'zh-CN' }
|
17 |
}
|
18 |
|
19 |
export function getLocalSetting(): AppState {
|
src/store/modules/app/index.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
import { defineStore } from 'pinia'
|
2 |
import type { AppState, Language, Theme } from './helper'
|
3 |
import { getLocalSetting, setLocalSetting } from './helper'
|
4 |
-
import { store } from '@/store'
|
5 |
|
6 |
export const useAppStore = defineStore('app-store', {
|
7 |
state: (): AppState => getLocalSetting(),
|
|
|
1 |
import { defineStore } from 'pinia'
|
2 |
import type { AppState, Language, Theme } from './helper'
|
3 |
import { getLocalSetting, setLocalSetting } from './helper'
|
4 |
+
import { store } from '@/store/helper'
|
5 |
|
6 |
export const useAppStore = defineStore('app-store', {
|
7 |
state: (): AppState => getLocalSetting(),
|
src/store/modules/auth/index.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
import { defineStore } from 'pinia'
|
2 |
import { getToken, removeToken, setToken } from './helper'
|
3 |
-
import { store } from '@/store'
|
4 |
import { fetchSession } from '@/api'
|
5 |
|
6 |
interface SessionResponse {
|
|
|
1 |
import { defineStore } from 'pinia'
|
2 |
import { getToken, removeToken, setToken } from './helper'
|
3 |
+
import { store } from '@/store/helper'
|
4 |
import { fetchSession } from '@/api'
|
5 |
|
6 |
interface SessionResponse {
|
src/store/modules/chat/index.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
import { defineStore } from 'pinia'
|
2 |
-
import { getLocalState, setLocalState } from './helper'
|
3 |
import { router } from '@/router'
|
4 |
|
5 |
export const useChatStore = defineStore('chat-store', {
|
@@ -182,6 +182,11 @@ export const useChatStore = defineStore('chat-store', {
|
|
182 |
}
|
183 |
},
|
184 |
|
|
|
|
|
|
|
|
|
|
|
185 |
async reloadRoute(uuid?: number) {
|
186 |
this.recordState()
|
187 |
await router.push({ name: 'Chat', params: { uuid } })
|
|
|
1 |
import { defineStore } from 'pinia'
|
2 |
+
import { defaultState, getLocalState, setLocalState } from './helper'
|
3 |
import { router } from '@/router'
|
4 |
|
5 |
export const useChatStore = defineStore('chat-store', {
|
|
|
182 |
}
|
183 |
},
|
184 |
|
185 |
+
clearHistory() {
|
186 |
+
this.$state = { ...defaultState() }
|
187 |
+
this.recordState()
|
188 |
+
},
|
189 |
+
|
190 |
async reloadRoute(uuid?: number) {
|
191 |
this.recordState()
|
192 |
await router.push({ name: 'Chat', params: { uuid } })
|
src/store/modules/settings/helper.ts
CHANGED
@@ -27,4 +27,4 @@ export function setLocalState(setting: SettingsState): void {
|
|
27 |
|
28 |
export function removeLocalState() {
|
29 |
ss.remove(LOCAL_NAME)
|
30 |
-
}
|
|
|
27 |
|
28 |
export function removeLocalState() {
|
29 |
ss.remove(LOCAL_NAME)
|
30 |
+
}
|
src/store/modules/settings/index.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
import { defineStore } from 'pinia'
|
2 |
import type { SettingsState } from './helper'
|
3 |
-
import { defaultSetting, getLocalState, setLocalState } from './helper'
|
4 |
|
5 |
export const useSettingStore = defineStore('setting-store', {
|
6 |
state: (): SettingsState => getLocalState(),
|
@@ -12,11 +12,11 @@ export const useSettingStore = defineStore('setting-store', {
|
|
12 |
|
13 |
resetSetting() {
|
14 |
this.$state = defaultSetting()
|
15 |
-
|
16 |
},
|
17 |
|
18 |
recordState() {
|
19 |
setLocalState(this.$state)
|
20 |
},
|
21 |
},
|
22 |
-
})
|
|
|
1 |
import { defineStore } from 'pinia'
|
2 |
import type { SettingsState } from './helper'
|
3 |
+
import { defaultSetting, getLocalState, removeLocalState, setLocalState } from './helper'
|
4 |
|
5 |
export const useSettingStore = defineStore('setting-store', {
|
6 |
state: (): SettingsState => getLocalState(),
|
|
|
12 |
|
13 |
resetSetting() {
|
14 |
this.$state = defaultSetting()
|
15 |
+
removeLocalState()
|
16 |
},
|
17 |
|
18 |
recordState() {
|
19 |
setLocalState(this.$state)
|
20 |
},
|
21 |
},
|
22 |
+
})
|
src/store/modules/user/helper.ts
CHANGED
@@ -15,9 +15,9 @@ export interface UserState {
|
|
15 |
export function defaultSetting(): UserState {
|
16 |
return {
|
17 |
userInfo: {
|
18 |
-
avatar: 'https://
|
19 |
-
name: '
|
20 |
-
description: '
|
21 |
},
|
22 |
}
|
23 |
}
|
|
|
15 |
export function defaultSetting(): UserState {
|
16 |
return {
|
17 |
userInfo: {
|
18 |
+
avatar: 'https://raw.githubusercontent.com/Chanzhaoyu/chatgpt-web/main/src/assets/avatar.jpg',
|
19 |
+
name: 'ChenZhaoYu',
|
20 |
+
description: 'Star on <a href="https://github.com/Chanzhaoyu/chatgpt-bot" class="text-blue-500" target="_blank" >GitHub</a>',
|
21 |
},
|
22 |
}
|
23 |
}
|
src/styles/lib/highlight.less
CHANGED
@@ -203,4 +203,4 @@ html {
|
|
203 |
.hljs-link {
|
204 |
text-decoration: underline
|
205 |
}
|
206 |
-
}
|
|
|
203 |
.hljs-link {
|
204 |
text-decoration: underline
|
205 |
}
|
206 |
+
}
|
src/typings/env.d.ts
CHANGED
@@ -5,4 +5,4 @@ interface ImportMetaEnv {
|
|
5 |
readonly VITE_APP_API_BASE_URL: string;
|
6 |
readonly VITE_GLOB_OPEN_LONG_REPLY: string;
|
7 |
readonly VITE_GLOB_APP_PWA: string;
|
8 |
-
}
|
|
|
5 |
readonly VITE_APP_API_BASE_URL: string;
|
6 |
readonly VITE_GLOB_OPEN_LONG_REPLY: string;
|
7 |
readonly VITE_GLOB_APP_PWA: string;
|
8 |
+
}
|
src/utils/functions/debounce.ts
CHANGED
@@ -15,4 +15,4 @@ export function debounce<T extends unknown[]>(
|
|
15 |
clearTimeout(timeoutId)
|
16 |
timeoutId = setTimeout(later, wait)
|
17 |
}
|
18 |
-
}
|
|
|
15 |
clearTimeout(timeoutId)
|
16 |
timeoutId = setTimeout(later, wait)
|
17 |
}
|
18 |
+
}
|
src/utils/request/index.ts
CHANGED
@@ -81,4 +81,4 @@ export function post<T = any>(
|
|
81 |
})
|
82 |
}
|
83 |
|
84 |
-
export default post
|
|
|
81 |
})
|
82 |
}
|
83 |
|
84 |
+
export default post
|
src/utils/storage/index.ts
CHANGED
@@ -1 +1,57 @@
|
|
1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
interface StorageData<T = any> {
|
2 |
+
data: T
|
3 |
+
expire: number | null
|
4 |
+
}
|
5 |
+
|
6 |
+
export function createLocalStorage(options?: { expire?: number | null }) {
|
7 |
+
const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7
|
8 |
+
|
9 |
+
const { expire } = Object.assign({ expire: DEFAULT_CACHE_TIME }, options)
|
10 |
+
|
11 |
+
function set<T = any>(key: string, data: T) {
|
12 |
+
const storageData: StorageData<T> = {
|
13 |
+
data,
|
14 |
+
expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
|
15 |
+
}
|
16 |
+
|
17 |
+
const json = JSON.stringify(storageData)
|
18 |
+
window.localStorage.setItem(key, json)
|
19 |
+
}
|
20 |
+
|
21 |
+
function get(key: string) {
|
22 |
+
const json = window.localStorage.getItem(key)
|
23 |
+
if (json) {
|
24 |
+
let storageData: StorageData | null = null
|
25 |
+
|
26 |
+
try {
|
27 |
+
storageData = JSON.parse(json)
|
28 |
+
}
|
29 |
+
catch {
|
30 |
+
// Prevent failure
|
31 |
+
}
|
32 |
+
|
33 |
+
if (storageData) {
|
34 |
+
const { data, expire } = storageData
|
35 |
+
if (expire === null || expire >= Date.now())
|
36 |
+
return data
|
37 |
+
}
|
38 |
+
|
39 |
+
remove(key)
|
40 |
+
return null
|
41 |
+
}
|
42 |
+
}
|
43 |
+
|
44 |
+
function remove(key: string) {
|
45 |
+
window.localStorage.removeItem(key)
|
46 |
+
}
|
47 |
+
|
48 |
+
function clear() {
|
49 |
+
window.localStorage.clear()
|
50 |
+
}
|
51 |
+
|
52 |
+
return { set, get, remove, clear }
|
53 |
+
}
|
54 |
+
|
55 |
+
export const ls = createLocalStorage()
|
56 |
+
|
57 |
+
export const ss = createLocalStorage({ expire: null })
|
src/views/chat/components/Header/index.vue
CHANGED
@@ -9,7 +9,7 @@ interface Props {
|
|
9 |
|
10 |
interface Emit {
|
11 |
(ev: 'export'): void
|
12 |
-
(ev: '
|
13 |
}
|
14 |
|
15 |
defineProps<Props>()
|
@@ -36,8 +36,8 @@ function handleExport() {
|
|
36 |
emit('export')
|
37 |
}
|
38 |
|
39 |
-
function
|
40 |
-
emit('
|
41 |
}
|
42 |
</script>
|
43 |
|
@@ -62,16 +62,16 @@ function toggleUsingContext() {
|
|
62 |
{{ currentChatHistory?.title ?? '' }}
|
63 |
</h1>
|
64 |
<div class="flex items-center space-x-2">
|
65 |
-
<HoverButton @click="toggleUsingContext">
|
66 |
-
<span class="text-xl" :class="{ 'text-[#4b9e5f]': usingContext, 'text-[#a8071a]': !usingContext }">
|
67 |
-
<SvgIcon icon="ri:chat-history-line" />
|
68 |
-
</span>
|
69 |
-
</HoverButton>
|
70 |
<HoverButton @click="handleExport">
|
71 |
<span class="text-xl text-[#4f555e] dark:text-white">
|
72 |
<SvgIcon icon="ri:download-2-line" />
|
73 |
</span>
|
74 |
</HoverButton>
|
|
|
|
|
|
|
|
|
|
|
75 |
</div>
|
76 |
</div>
|
77 |
</header>
|
|
|
9 |
|
10 |
interface Emit {
|
11 |
(ev: 'export'): void
|
12 |
+
(ev: 'handleClear'): void
|
13 |
}
|
14 |
|
15 |
defineProps<Props>()
|
|
|
36 |
emit('export')
|
37 |
}
|
38 |
|
39 |
+
function handleClear() {
|
40 |
+
emit('handleClear')
|
41 |
}
|
42 |
</script>
|
43 |
|
|
|
62 |
{{ currentChatHistory?.title ?? '' }}
|
63 |
</h1>
|
64 |
<div class="flex items-center space-x-2">
|
|
|
|
|
|
|
|
|
|
|
65 |
<HoverButton @click="handleExport">
|
66 |
<span class="text-xl text-[#4f555e] dark:text-white">
|
67 |
<SvgIcon icon="ri:download-2-line" />
|
68 |
</span>
|
69 |
</HoverButton>
|
70 |
+
<HoverButton @click="handleClear">
|
71 |
+
<span class="text-xl text-[#4f555e] dark:text-white">
|
72 |
+
<SvgIcon icon="ri:delete-bin-line" />
|
73 |
+
</span>
|
74 |
+
</HoverButton>
|
75 |
</div>
|
76 |
</div>
|
77 |
</header>
|
src/views/chat/components/Message/Text.vue
CHANGED
@@ -107,17 +107,14 @@ onUnmounted(() => {
|
|
107 |
<div class="text-black" :class="wrapClass">
|
108 |
<div ref="textRef" class="leading-relaxed break-words">
|
109 |
<div v-if="!inversion">
|
110 |
-
<div v-if="!asRawText" class="markdown-body" v-html="text" />
|
111 |
<div v-else class="whitespace-pre-wrap" v-text="text" />
|
112 |
</div>
|
113 |
<div v-else class="whitespace-pre-wrap" v-text="text" />
|
114 |
-
<template v-if="loading">
|
115 |
-
<span class="dark:text-white w-[4px] h-[20px] block animate-blink" />
|
116 |
-
</template>
|
117 |
</div>
|
118 |
</div>
|
119 |
</template>
|
120 |
|
121 |
<style lang="less">
|
122 |
@import url(./style.less);
|
123 |
-
</style>
|
|
|
107 |
<div class="text-black" :class="wrapClass">
|
108 |
<div ref="textRef" class="leading-relaxed break-words">
|
109 |
<div v-if="!inversion">
|
110 |
+
<div v-if="!asRawText" class="markdown-body" :class="{ 'markdown-body-generate': loading }" v-html="text" />
|
111 |
<div v-else class="whitespace-pre-wrap" v-text="text" />
|
112 |
</div>
|
113 |
<div v-else class="whitespace-pre-wrap" v-text="text" />
|
|
|
|
|
|
|
114 |
</div>
|
115 |
</div>
|
116 |
</template>
|
117 |
|
118 |
<style lang="less">
|
119 |
@import url(./style.less);
|
120 |
+
</style>
|
src/views/chat/components/Message/index.vue
CHANGED
@@ -142,4 +142,4 @@ async function handleCopy() {
|
|
142 |
</div>
|
143 |
</div>
|
144 |
</div>
|
145 |
-
</template>
|
|
|
142 |
</div>
|
143 |
</div>
|
144 |
</div>
|
145 |
+
</template>
|
src/views/chat/components/Message/style.less
CHANGED
@@ -57,10 +57,60 @@
|
|
57 |
}
|
58 |
}
|
59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
}
|
61 |
|
62 |
html.dark {
|
63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
.message-reply {
|
65 |
.whitespace-pre-wrap {
|
66 |
white-space: pre-wrap;
|
@@ -72,4 +122,14 @@ html.dark {
|
|
72 |
pre {
|
73 |
background-color: #282c34;
|
74 |
}
|
75 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
}
|
58 |
}
|
59 |
|
60 |
+
|
61 |
+
&.markdown-body-generate>dd:last-child:after,
|
62 |
+
&.markdown-body-generate>dl:last-child:after,
|
63 |
+
&.markdown-body-generate>dt:last-child:after,
|
64 |
+
&.markdown-body-generate>h1:last-child:after,
|
65 |
+
&.markdown-body-generate>h2:last-child:after,
|
66 |
+
&.markdown-body-generate>h3:last-child:after,
|
67 |
+
&.markdown-body-generate>h4:last-child:after,
|
68 |
+
&.markdown-body-generate>h5:last-child:after,
|
69 |
+
&.markdown-body-generate>h6:last-child:after,
|
70 |
+
&.markdown-body-generate>li:last-child:after,
|
71 |
+
&.markdown-body-generate>ol:last-child li:last-child:after,
|
72 |
+
&.markdown-body-generate>p:last-child:after,
|
73 |
+
&.markdown-body-generate>pre:last-child code:after,
|
74 |
+
&.markdown-body-generate>td:last-child:after,
|
75 |
+
&.markdown-body-generate>ul:last-child li:last-child:after {
|
76 |
+
animation: blink 1s steps(5, start) infinite;
|
77 |
+
color: #000;
|
78 |
+
content: '_';
|
79 |
+
font-weight: 700;
|
80 |
+
margin-left: 3px;
|
81 |
+
vertical-align: baseline;
|
82 |
+
}
|
83 |
+
|
84 |
+
@keyframes blink {
|
85 |
+
to {
|
86 |
+
visibility: hidden;
|
87 |
+
}
|
88 |
+
}
|
89 |
}
|
90 |
|
91 |
html.dark {
|
92 |
|
93 |
+
.markdown-body {
|
94 |
+
|
95 |
+
&.markdown-body-generate>dd:last-child:after,
|
96 |
+
&.markdown-body-generate>dl:last-child:after,
|
97 |
+
&.markdown-body-generate>dt:last-child:after,
|
98 |
+
&.markdown-body-generate>h1:last-child:after,
|
99 |
+
&.markdown-body-generate>h2:last-child:after,
|
100 |
+
&.markdown-body-generate>h3:last-child:after,
|
101 |
+
&.markdown-body-generate>h4:last-child:after,
|
102 |
+
&.markdown-body-generate>h5:last-child:after,
|
103 |
+
&.markdown-body-generate>h6:last-child:after,
|
104 |
+
&.markdown-body-generate>li:last-child:after,
|
105 |
+
&.markdown-body-generate>ol:last-child li:last-child:after,
|
106 |
+
&.markdown-body-generate>p:last-child:after,
|
107 |
+
&.markdown-body-generate>pre:last-child code:after,
|
108 |
+
&.markdown-body-generate>td:last-child:after,
|
109 |
+
&.markdown-body-generate>ul:last-child li:last-child:after {
|
110 |
+
color: #65a665;
|
111 |
+
}
|
112 |
+
}
|
113 |
+
|
114 |
.message-reply {
|
115 |
.whitespace-pre-wrap {
|
116 |
white-space: pre-wrap;
|
|
|
122 |
pre {
|
123 |
background-color: #282c34;
|
124 |
}
|
125 |
+
}
|
126 |
+
|
127 |
+
@media screen and (max-width: 533px) {
|
128 |
+
.markdown-body .code-block-wrapper {
|
129 |
+
padding: unset;
|
130 |
+
|
131 |
+
code {
|
132 |
+
padding: 24px 16px 16px 16px;
|
133 |
+
}
|
134 |
+
}
|
135 |
+
}
|
src/views/chat/index.vue
CHANGED
@@ -93,7 +93,7 @@ async function onConversation() {
|
|
93 |
+uuid,
|
94 |
{
|
95 |
dateTime: new Date().toLocaleString(),
|
96 |
-
text: '',
|
97 |
loading: true,
|
98 |
inversion: false,
|
99 |
error: false,
|
@@ -469,7 +469,7 @@ onUnmounted(() => {
|
|
469 |
v-if="isMobile"
|
470 |
:using-context="usingContext"
|
471 |
@export="handleExport"
|
472 |
-
@
|
473 |
/>
|
474 |
<main class="flex-1 overflow-hidden">
|
475 |
<div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
|
@@ -513,7 +513,7 @@ onUnmounted(() => {
|
|
513 |
<footer :class="footerClass">
|
514 |
<div class="w-full max-w-screen-xl m-auto">
|
515 |
<div class="flex items-center justify-between space-x-2">
|
516 |
-
<HoverButton @click="handleClear">
|
517 |
<span class="text-xl text-[#4f555e] dark:text-white">
|
518 |
<SvgIcon icon="ri:delete-bin-line" />
|
519 |
</span>
|
@@ -523,7 +523,7 @@ onUnmounted(() => {
|
|
523 |
<SvgIcon icon="ri:download-2-line" />
|
524 |
</span>
|
525 |
</HoverButton>
|
526 |
-
<HoverButton
|
527 |
<span class="text-xl" :class="{ 'text-[#4b9e5f]': usingContext, 'text-[#a8071a]': !usingContext }">
|
528 |
<SvgIcon icon="ri:chat-history-line" />
|
529 |
</span>
|
@@ -554,4 +554,4 @@ onUnmounted(() => {
|
|
554 |
</div>
|
555 |
</footer>
|
556 |
</div>
|
557 |
-
</template>
|
|
|
93 |
+uuid,
|
94 |
{
|
95 |
dateTime: new Date().toLocaleString(),
|
96 |
+
text: '思考中',
|
97 |
loading: true,
|
98 |
inversion: false,
|
99 |
error: false,
|
|
|
469 |
v-if="isMobile"
|
470 |
:using-context="usingContext"
|
471 |
@export="handleExport"
|
472 |
+
@handle-clear="handleClear"
|
473 |
/>
|
474 |
<main class="flex-1 overflow-hidden">
|
475 |
<div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
|
|
|
513 |
<footer :class="footerClass">
|
514 |
<div class="w-full max-w-screen-xl m-auto">
|
515 |
<div class="flex items-center justify-between space-x-2">
|
516 |
+
<HoverButton v-if="!isMobile" @click="handleClear">
|
517 |
<span class="text-xl text-[#4f555e] dark:text-white">
|
518 |
<SvgIcon icon="ri:delete-bin-line" />
|
519 |
</span>
|
|
|
523 |
<SvgIcon icon="ri:download-2-line" />
|
524 |
</span>
|
525 |
</HoverButton>
|
526 |
+
<HoverButton @click="toggleUsingContext">
|
527 |
<span class="text-xl" :class="{ 'text-[#4b9e5f]': usingContext, 'text-[#a8071a]': !usingContext }">
|
528 |
<SvgIcon icon="ri:chat-history-line" />
|
529 |
</span>
|
|
|
554 |
</div>
|
555 |
</footer>
|
556 |
</div>
|
557 |
+
</template>
|
src/views/chat/layout/sider/List.vue
CHANGED
@@ -102,4 +102,4 @@ function isActive(uuid: number) {
|
|
102 |
</template>
|
103 |
</div>
|
104 |
</NScrollbar>
|
105 |
-
</template>
|
|
|
102 |
</template>
|
103 |
</div>
|
104 |
</NScrollbar>
|
105 |
+
</template>
|
src/views/chat/layout/sider/index.vue
CHANGED
@@ -1,16 +1,19 @@
|
|
1 |
<script setup lang='ts'>
|
2 |
import type { CSSProperties } from 'vue'
|
3 |
import { computed, ref, watch } from 'vue'
|
4 |
-
import { NButton, NLayoutSider } from 'naive-ui'
|
5 |
import List from './List.vue'
|
6 |
import Footer from './Footer.vue'
|
7 |
import { useAppStore, useChatStore } from '@/store'
|
8 |
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
9 |
-
import { PromptStore } from '@/components/common'
|
|
|
10 |
|
11 |
const appStore = useAppStore()
|
12 |
const chatStore = useChatStore()
|
13 |
|
|
|
|
|
14 |
const { isMobile } = useBasicLayout()
|
15 |
const show = ref(false)
|
16 |
|
@@ -26,6 +29,20 @@ function handleUpdateCollapsed() {
|
|
26 |
appStore.setSiderCollapsed(!collapsed.value)
|
27 |
}
|
28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
const getMobileClass = computed<CSSProperties>(() => {
|
30 |
if (isMobile.value) {
|
31 |
return {
|
@@ -79,9 +96,14 @@ watch(
|
|
79 |
<div class="flex-1 min-h-0 pb-4 overflow-hidden">
|
80 |
<List />
|
81 |
</div>
|
82 |
-
<div class="p-4">
|
83 |
-
<
|
84 |
-
|
|
|
|
|
|
|
|
|
|
|
85 |
</NButton>
|
86 |
</div>
|
87 |
</main>
|
@@ -92,4 +114,4 @@ watch(
|
|
92 |
<div v-show="!collapsed" class="fixed inset-0 z-40 w-full h-full bg-black/40" @click="handleUpdateCollapsed" />
|
93 |
</template>
|
94 |
<PromptStore v-model:visible="show" />
|
95 |
-
</template>
|
|
|
1 |
<script setup lang='ts'>
|
2 |
import type { CSSProperties } from 'vue'
|
3 |
import { computed, ref, watch } from 'vue'
|
4 |
+
import { NButton, NLayoutSider, useDialog } from 'naive-ui'
|
5 |
import List from './List.vue'
|
6 |
import Footer from './Footer.vue'
|
7 |
import { useAppStore, useChatStore } from '@/store'
|
8 |
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
9 |
+
import { PromptStore, SvgIcon } from '@/components/common'
|
10 |
+
import { t } from '@/locales'
|
11 |
|
12 |
const appStore = useAppStore()
|
13 |
const chatStore = useChatStore()
|
14 |
|
15 |
+
const dialog = useDialog()
|
16 |
+
|
17 |
const { isMobile } = useBasicLayout()
|
18 |
const show = ref(false)
|
19 |
|
|
|
29 |
appStore.setSiderCollapsed(!collapsed.value)
|
30 |
}
|
31 |
|
32 |
+
function handleClearAll() {
|
33 |
+
dialog.warning({
|
34 |
+
title: t('chat.deleteMessage'),
|
35 |
+
content: t('chat.clearHistoryConfirm'),
|
36 |
+
positiveText: t('common.yes'),
|
37 |
+
negativeText: t('common.no'),
|
38 |
+
onPositiveClick: () => {
|
39 |
+
chatStore.clearHistory()
|
40 |
+
if (isMobile.value)
|
41 |
+
appStore.setSiderCollapsed(true)
|
42 |
+
},
|
43 |
+
})
|
44 |
+
}
|
45 |
+
|
46 |
const getMobileClass = computed<CSSProperties>(() => {
|
47 |
if (isMobile.value) {
|
48 |
return {
|
|
|
96 |
<div class="flex-1 min-h-0 pb-4 overflow-hidden">
|
97 |
<List />
|
98 |
</div>
|
99 |
+
<div class="flex items-center p-4 space-x-4">
|
100 |
+
<div class="flex-1">
|
101 |
+
<NButton block @click="show = true">
|
102 |
+
{{ $t('store.siderButton') }}
|
103 |
+
</NButton>
|
104 |
+
</div>
|
105 |
+
<NButton @click="handleClearAll">
|
106 |
+
<SvgIcon icon="ri:close-circle-line" />
|
107 |
</NButton>
|
108 |
</div>
|
109 |
</main>
|
|
|
114 |
<div v-show="!collapsed" class="fixed inset-0 z-40 w-full h-full bg-black/40" @click="handleUpdateCollapsed" />
|
115 |
</template>
|
116 |
<PromptStore v-model:visible="show" />
|
117 |
+
</template>
|
start.cmd
CHANGED
@@ -6,4 +6,4 @@ echo "Start service complete!"
|
|
6 |
cd ..
|
7 |
echo "" > front.log
|
8 |
start pnpm dev > front.log &
|
9 |
-
echo "Start front complete!"
|
|
|
6 |
cd ..
|
7 |
echo "" > front.log
|
8 |
start pnpm dev > front.log &
|
9 |
+
echo "Start front complete!"
|
vite.config.ts
CHANGED
@@ -51,4 +51,4 @@ export default defineConfig((env) => {
|
|
51 |
},
|
52 |
},
|
53 |
}
|
54 |
-
})
|
|
|
51 |
},
|
52 |
},
|
53 |
}
|
54 |
+
})
|