chokiproai commited on
Commit
3424203
1 Parent(s): a81cbf3

Upload 135 files

Browse files
Files changed (46) hide show
  1. .env +2 -2
  2. .eslintignore +1 -1
  3. docker-compose/docker-compose.yml +1 -1
  4. docker-compose/nginx/nginx.conf +1 -1
  5. kubernetes/README.md +1 -1
  6. kubernetes/deploy.yaml +1 -1
  7. kubernetes/ingress.yaml +1 -1
  8. package-lock.json +0 -0
  9. package.json +1 -1
  10. pnpm-lock.yaml +1 -1
  11. public/favicon.ico +0 -0
  12. public/favicon.svg +1 -24
  13. public/pwa-192x192.png +0 -0
  14. public/pwa-512x512.png +0 -0
  15. service/.env.example +1 -3
  16. service/package.json +1 -1
  17. service/pnpm-lock.yaml +0 -0
  18. src/api/index.ts +1 -1
  19. src/components/common/PromptStore/index.vue +1 -1
  20. src/components/common/Setting/Advanced.vue +2 -2
  21. src/components/common/Setting/General.vue +5 -2
  22. src/components/common/Setting/index.vue +1 -1
  23. src/router/permission.ts +1 -1
  24. src/store/helper.ts +3 -0
  25. src/store/index.ts +1 -3
  26. src/store/modules/app/helper.ts +2 -2
  27. src/store/modules/app/index.ts +1 -1
  28. src/store/modules/auth/index.ts +1 -1
  29. src/store/modules/chat/index.ts +6 -1
  30. src/store/modules/settings/helper.ts +1 -1
  31. src/store/modules/settings/index.ts +3 -3
  32. src/store/modules/user/helper.ts +3 -3
  33. src/styles/lib/highlight.less +1 -1
  34. src/typings/env.d.ts +1 -1
  35. src/utils/functions/debounce.ts +1 -1
  36. src/utils/request/index.ts +1 -1
  37. src/utils/storage/index.ts +57 -1
  38. src/views/chat/components/Header/index.vue +8 -8
  39. src/views/chat/components/Message/Text.vue +2 -5
  40. src/views/chat/components/Message/index.vue +1 -1
  41. src/views/chat/components/Message/style.less +61 -1
  42. src/views/chat/index.vue +5 -5
  43. src/views/chat/layout/sider/List.vue +1 -1
  44. src/views/chat/layout/sider/index.vue +28 -6
  45. start.cmd +1 -1
  46. 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://localhost: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
 
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.0",
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="1" :min="0" :step="0.1" />
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: 'Tiếng Việt', key: 'vi-VN', value: 'vi-VN' },
 
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 { createPinia } from 'pinia'
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 = 'vi-VN' | 'en-US'
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: 'en-US' }
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
- this.recordState()
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://pnghive.com/core/images/full/chat-gpt-logo-png-1680406057.png',
19
- name: 'ChatGPT',
20
- description: 'AI assistant',
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
- export * from './local'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: 'toggleUsingContext'): void
13
  }
14
 
15
  defineProps<Props>()
@@ -36,8 +36,8 @@ function handleExport() {
36
  emit('export')
37
  }
38
 
39
- function toggleUsingContext() {
40
- emit('toggleUsingContext')
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
- @toggle-using-context="toggleUsingContext"
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 v-if="!isMobile" @click="toggleUsingContext">
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
- <NButton block @click="show = true">
84
- {{ $t('store.siderButton') }}
 
 
 
 
 
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
+ })