mirror of
https://github.com/Chanzhaoyu/chatgpt-web.git
synced 2025-08-02 12:08:06 +00:00
Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
42e320fe35 | ||
![]() |
94e23bb916 |
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -29,6 +29,7 @@
|
|||||||
"dockerhub",
|
"dockerhub",
|
||||||
"esno",
|
"esno",
|
||||||
"GPTAPI",
|
"GPTAPI",
|
||||||
|
"hljs",
|
||||||
"iconify",
|
"iconify",
|
||||||
"logprobs",
|
"logprobs",
|
||||||
"nodata",
|
"nodata",
|
||||||
|
19
CHANGELOG.md
19
CHANGELOG.md
@@ -1,3 +1,22 @@
|
|||||||
|
## v2.8.3
|
||||||
|
|
||||||
|
`2023-03-01`
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
- 消息已输出内容不会因为中断而消失[#167]
|
||||||
|
- 添加复制消息按钮[#133]
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- `README` 添加声明内容
|
||||||
|
|
||||||
|
## v2.8.2
|
||||||
|
|
||||||
|
`2023-02-28`
|
||||||
|
### Enhancement
|
||||||
|
- 代码主题调整为 `One Dark - light|dark` 适配深色模式
|
||||||
|
### BugFix
|
||||||
|
- 修复普通文本代码渲染和深色模式下的问题[#139][#154]
|
||||||
|
|
||||||
## v2.8.1
|
## v2.8.1
|
||||||
|
|
||||||
`2023-02-27`
|
`2023-02-27`
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# ChatGPT Web
|
# ChatGPT Web
|
||||||
|
|
||||||
> 使用 `express` 和 `vue3` 搭建的支持 `ChatGPT` 双模型演示网页
|
> 声明:此项目只发布于 Github,基于 MIT 协议,免费且作为开源学习使用。并且不会有任何形式的卖号、付费服务、讨论群、讨论组等行为。谨防受骗。
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
@@ -174,7 +174,7 @@ version: '3'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: chenzhaoyu94/chatgpt-web # 总是使用latest,更新时重新pull该tag镜像即可
|
image: chenzhaoyu94/chatgpt-web # 总是使用latest,更新时重新pull该tag镜像即可
|
||||||
ports:
|
ports:
|
||||||
- 3002:3002
|
- 3002:3002
|
||||||
environment:
|
environment:
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "chatgpt-web",
|
"name": "chatgpt-web",
|
||||||
"version": "2.8.1",
|
"version": "2.8.3",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "ChatGPT Web",
|
"description": "ChatGPT Web",
|
||||||
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
|
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
|
||||||
|
@@ -8,4 +8,4 @@ OPENAI_ACCESS_TOKEN=
|
|||||||
API_REVERSE_PROXY=
|
API_REVERSE_PROXY=
|
||||||
|
|
||||||
# timeout
|
# timeout
|
||||||
TIMEOUT_MS=60000
|
TIMEOUT_MS=100000
|
||||||
|
36
src/hooks/useIconRender.ts
Normal file
36
src/hooks/useIconRender.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { h } from 'vue'
|
||||||
|
import { SvgIcon } from '@/components/common'
|
||||||
|
|
||||||
|
export const useIconRender = () => {
|
||||||
|
interface IconConfig {
|
||||||
|
icon?: string
|
||||||
|
color?: string
|
||||||
|
fontSize?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IconStyle {
|
||||||
|
color?: string
|
||||||
|
fontSize?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconRender = (config: IconConfig) => {
|
||||||
|
const { color, fontSize, icon } = config
|
||||||
|
|
||||||
|
const style: IconStyle = {}
|
||||||
|
|
||||||
|
if (color)
|
||||||
|
style.color = color
|
||||||
|
|
||||||
|
if (fontSize)
|
||||||
|
style.fontSize = `${fontSize}px`
|
||||||
|
|
||||||
|
if (!icon)
|
||||||
|
window.console.warn('iconRender: icon is required')
|
||||||
|
|
||||||
|
return () => h(SvgIcon, { icon, style })
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
iconRender,
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
import 'highlight.js/styles/xcode.css'
|
|
||||||
import '@/styles/lib/tailwind.css'
|
import '@/styles/lib/tailwind.css'
|
||||||
|
import '@/styles/lib/highlight.less'
|
||||||
import '@/styles/lib/github-markdown.less'
|
import '@/styles/lib/github-markdown.less'
|
||||||
import '@/styles/global.less'
|
import '@/styles/global.less'
|
||||||
|
|
||||||
|
@@ -129,6 +129,22 @@ export const useChatStore = defineStore('chat-store', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateChatSomeByUuid(uuid: number, index: number, chat: Partial<Chat.Chat>) {
|
||||||
|
if (!uuid || uuid === 0) {
|
||||||
|
if (this.chat.length) {
|
||||||
|
this.chat[0].data[index] = { ...this.chat[0].data[index], ...chat }
|
||||||
|
this.recordState()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatIndex = this.chat.findIndex(item => item.uuid === uuid)
|
||||||
|
if (chatIndex !== -1) {
|
||||||
|
this.chat[chatIndex].data[index] = { ...this.chat[chatIndex].data[index], ...chat }
|
||||||
|
this.recordState()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
deleteChatByUuid(uuid: number, index: number) {
|
deleteChatByUuid(uuid: number, index: number) {
|
||||||
if (!uuid || uuid === 0) {
|
if (!uuid || uuid === 0) {
|
||||||
if (this.chat.length) {
|
if (this.chat.length) {
|
||||||
|
203
src/styles/lib/highlight.less
Normal file
203
src/styles/lib/highlight.less
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
html.dark {
|
||||||
|
pre code.hljs {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 1em
|
||||||
|
}
|
||||||
|
|
||||||
|
code.hljs {
|
||||||
|
padding: 3px 5px
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs {
|
||||||
|
color: #abb2bf;
|
||||||
|
background: #282c34
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-operator,
|
||||||
|
.hljs-pattern-match {
|
||||||
|
color: #f92672
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-function,
|
||||||
|
.hljs-pattern-match .hljs-constructor {
|
||||||
|
color: #61aeee
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-function .hljs-params {
|
||||||
|
color: #a6e22e
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-function .hljs-params .hljs-typing {
|
||||||
|
color: #fd971f
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-module-access .hljs-module {
|
||||||
|
color: #7e57c2
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-constructor {
|
||||||
|
color: #e2b93d
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-constructor .hljs-string {
|
||||||
|
color: #9ccc65
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-quote {
|
||||||
|
color: #b18eb1;
|
||||||
|
font-style: italic
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-doctag,
|
||||||
|
.hljs-formula {
|
||||||
|
color: #c678dd
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-deletion,
|
||||||
|
.hljs-name,
|
||||||
|
.hljs-section,
|
||||||
|
.hljs-selector-tag,
|
||||||
|
.hljs-subst {
|
||||||
|
color: #e06c75
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-literal {
|
||||||
|
color: #56b6c2
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-addition,
|
||||||
|
.hljs-attribute,
|
||||||
|
.hljs-meta .hljs-string,
|
||||||
|
.hljs-regexp,
|
||||||
|
.hljs-string {
|
||||||
|
color: #98c379
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-class .hljs-title,
|
||||||
|
.hljs-title.class_ {
|
||||||
|
color: #e6c07b
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-attr,
|
||||||
|
.hljs-number,
|
||||||
|
.hljs-selector-attr,
|
||||||
|
.hljs-selector-class,
|
||||||
|
.hljs-selector-pseudo,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-type,
|
||||||
|
.hljs-variable {
|
||||||
|
color: #d19a66
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-bullet,
|
||||||
|
.hljs-link,
|
||||||
|
.hljs-meta,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-title {
|
||||||
|
color: #61aeee
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-emphasis {
|
||||||
|
font-style: italic
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-strong {
|
||||||
|
font-weight: 700
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-link {
|
||||||
|
text-decoration: underline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
pre code.hljs {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 1em
|
||||||
|
}
|
||||||
|
|
||||||
|
code.hljs {
|
||||||
|
padding: 3px 5px
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs {
|
||||||
|
color: #383a42;
|
||||||
|
background: #fafafa
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-quote {
|
||||||
|
color: #a0a1a7;
|
||||||
|
font-style: italic
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-doctag,
|
||||||
|
.hljs-formula,
|
||||||
|
.hljs-keyword {
|
||||||
|
color: #a626a4
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-deletion,
|
||||||
|
.hljs-name,
|
||||||
|
.hljs-section,
|
||||||
|
.hljs-selector-tag,
|
||||||
|
.hljs-subst {
|
||||||
|
color: #e45649
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-literal {
|
||||||
|
color: #0184bb
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-addition,
|
||||||
|
.hljs-attribute,
|
||||||
|
.hljs-meta .hljs-string,
|
||||||
|
.hljs-regexp,
|
||||||
|
.hljs-string {
|
||||||
|
color: #50a14f
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-attr,
|
||||||
|
.hljs-number,
|
||||||
|
.hljs-selector-attr,
|
||||||
|
.hljs-selector-class,
|
||||||
|
.hljs-selector-pseudo,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-type,
|
||||||
|
.hljs-variable {
|
||||||
|
color: #986801
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-bullet,
|
||||||
|
.hljs-link,
|
||||||
|
.hljs-meta,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-title {
|
||||||
|
color: #4078f2
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-class .hljs-title,
|
||||||
|
.hljs-title.class_ {
|
||||||
|
color: #c18401
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-emphasis {
|
||||||
|
font-style: italic
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-strong {
|
||||||
|
font-weight: 700
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-link {
|
||||||
|
text-decoration: underline
|
||||||
|
}
|
||||||
|
}
|
@@ -13,3 +13,15 @@ export function includeCode(text: string | null | undefined) {
|
|||||||
const regexp = /^(?:\s{4}|\t).+/gm
|
const regexp = /^(?:\s{4}|\t).+/gm
|
||||||
return !!(text?.includes(' = ') || text?.match(regexp))
|
return !!(text?.includes(' = ') || text?.match(regexp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 复制文本
|
||||||
|
export function copyText(text: string) {
|
||||||
|
const input = document.createElement('input')
|
||||||
|
input.setAttribute('readonly', 'readonly')
|
||||||
|
input.setAttribute('value', text)
|
||||||
|
document.body.appendChild(input)
|
||||||
|
input.select()
|
||||||
|
if (document.execCommand('copy'))
|
||||||
|
document.execCommand('copy')
|
||||||
|
document.body.removeChild(input)
|
||||||
|
}
|
||||||
|
@@ -24,11 +24,17 @@ renderer.html = (html) => {
|
|||||||
|
|
||||||
renderer.code = (code, language) => {
|
renderer.code = (code, language) => {
|
||||||
const validLang = !!(language && hljs.getLanguage(language))
|
const validLang = !!(language && hljs.getLanguage(language))
|
||||||
const highlighted = validLang ? hljs.highlight(language, code).value : code
|
if (validLang)
|
||||||
return `<pre><code class="hljs ${language}">${highlighted}</code></pre>`
|
return `<pre><code class="hljs ${language}">${hljs.highlight(language, code).value}</code></pre>`
|
||||||
|
return `<pre style="background: none">${hljs.highlightAuto(code).value}</pre>`
|
||||||
}
|
}
|
||||||
|
|
||||||
marked.setOptions({ renderer })
|
marked.setOptions({
|
||||||
|
renderer,
|
||||||
|
highlight(code) {
|
||||||
|
return hljs.highlightAuto(code).value
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const wrapClass = computed(() => {
|
const wrapClass = computed(() => {
|
||||||
return [
|
return [
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
<script setup lang='ts'>
|
<script setup lang='ts'>
|
||||||
|
import { NDropdown, useMessage } from 'naive-ui'
|
||||||
import AvatarComponent from './Avatar.vue'
|
import AvatarComponent from './Avatar.vue'
|
||||||
import TextComponent from './Text.vue'
|
import TextComponent from './Text.vue'
|
||||||
import { SvgIcon } from '@/components/common'
|
import { SvgIcon } from '@/components/common'
|
||||||
|
import { copyText } from '@/utils/format'
|
||||||
|
import { useIconRender } from '@/hooks/useIconRender'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
dateTime?: string
|
dateTime?: string
|
||||||
@@ -16,14 +19,42 @@ interface Emit {
|
|||||||
(ev: 'delete'): void
|
(ev: 'delete'): void
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
const emit = defineEmits<Emit>()
|
const emit = defineEmits<Emit>()
|
||||||
|
|
||||||
|
const ms = useMessage()
|
||||||
|
|
||||||
|
const { iconRender } = useIconRender()
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
label: 'Copy',
|
||||||
|
key: 'copy',
|
||||||
|
icon: iconRender({ icon: 'ri:file-copy-2-line' }),
|
||||||
|
}, {
|
||||||
|
label: 'Delete',
|
||||||
|
key: 'delete',
|
||||||
|
icon: iconRender({ icon: 'ri:delete-bin-line' }),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function handleSelect(key: 'copy' | 'delete') {
|
||||||
|
if (key === 'copy')
|
||||||
|
handleCopy()
|
||||||
|
else
|
||||||
|
handleDelete()
|
||||||
|
}
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
emit('delete')
|
emit('delete')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleCopy() {
|
||||||
|
copyText(props.text ?? '')
|
||||||
|
ms.success('Copied')
|
||||||
|
}
|
||||||
|
|
||||||
function handleRegenerate() {
|
function handleRegenerate() {
|
||||||
emit('regenerate')
|
emit('regenerate')
|
||||||
}
|
}
|
||||||
@@ -59,12 +90,11 @@ function handleRegenerate() {
|
|||||||
>
|
>
|
||||||
<SvgIcon icon="ri:restart-line" />
|
<SvgIcon icon="ri:restart-line" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<NDropdown :options="options" @select="handleSelect">
|
||||||
class="mb-1 transition text-neutral-400 hover:text-neutral-800 dark:hover:text-neutral-200"
|
<button class="transition text-neutral-300 hover:text-neutral-800 dark:hover:text-neutral-200">
|
||||||
@click="handleDelete"
|
<SvgIcon icon="ri:function-line" />
|
||||||
>
|
</button>
|
||||||
<SvgIcon icon="ri:delete-bin-6-line" />
|
</NDropdown>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
||||||
p{
|
p {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,13 +23,16 @@
|
|||||||
pre {
|
pre {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.dark{
|
code.hljs{
|
||||||
.markdown-body{
|
padding: 0;
|
||||||
.highlight pre,
|
}
|
||||||
pre {
|
}
|
||||||
background-color: #18181c;
|
|
||||||
}
|
html.dark {
|
||||||
|
|
||||||
|
.highlight pre,
|
||||||
|
pre {
|
||||||
|
background-color: #282c34;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,10 @@ import { useChatStore } from '@/store'
|
|||||||
export function useChat() {
|
export function useChat() {
|
||||||
const chatStore = useChatStore()
|
const chatStore = useChatStore()
|
||||||
|
|
||||||
|
const getChatByUuidAndIndex = (uuid: number, index: number) => {
|
||||||
|
return chatStore.getChatByUuidAndIndex(uuid, index)
|
||||||
|
}
|
||||||
|
|
||||||
const addChat = (uuid: number, chat: Chat.Chat) => {
|
const addChat = (uuid: number, chat: Chat.Chat) => {
|
||||||
chatStore.addChatByUuid(uuid, chat)
|
chatStore.addChatByUuid(uuid, chat)
|
||||||
}
|
}
|
||||||
@@ -11,8 +15,14 @@ export function useChat() {
|
|||||||
chatStore.updateChatByUuid(uuid, index, chat)
|
chatStore.updateChatByUuid(uuid, index, chat)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateChatSome = (uuid: number, index: number, chat: Partial<Chat.Chat>) => {
|
||||||
|
chatStore.updateChatSomeByUuid(uuid, index, chat)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
addChat,
|
addChat,
|
||||||
updateChat,
|
updateChat,
|
||||||
|
updateChatSome,
|
||||||
|
getChatByUuidAndIndex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ const ms = useMessage()
|
|||||||
const chatStore = useChatStore()
|
const chatStore = useChatStore()
|
||||||
|
|
||||||
const { isMobile } = useBasicLayout()
|
const { isMobile } = useBasicLayout()
|
||||||
const { addChat, updateChat } = useChat()
|
const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex } = useChat()
|
||||||
const { scrollRef, scrollToBottom } = useScroll()
|
const { scrollRef, scrollToBottom } = useScroll()
|
||||||
|
|
||||||
const { uuid } = route.params as { uuid: string }
|
const { uuid } = route.params as { uuid: string }
|
||||||
@@ -71,7 +71,7 @@ async function onConversation() {
|
|||||||
+uuid,
|
+uuid,
|
||||||
{
|
{
|
||||||
dateTime: new Date().toLocaleString(),
|
dateTime: new Date().toLocaleString(),
|
||||||
text: 'Aha, Thinking...',
|
text: '',
|
||||||
loading: true,
|
loading: true,
|
||||||
inversion: false,
|
inversion: false,
|
||||||
error: false,
|
error: false,
|
||||||
@@ -118,10 +118,34 @@ async function onConversation() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
catch (error: any) {
|
catch (error: any) {
|
||||||
let errorMessage = error?.message ?? 'Something went wrong, please try again later.'
|
const errorMessage = error?.message ?? 'Something went wrong, please try again later.'
|
||||||
|
|
||||||
if (error.message === 'canceled')
|
if (error.message === 'canceled') {
|
||||||
errorMessage = 'Request canceled. Please try again.'
|
updateChatSome(
|
||||||
|
+uuid,
|
||||||
|
dataSources.value.length - 1,
|
||||||
|
{
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
scrollToBottom()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentChat = getChatByUuidAndIndex(+uuid, dataSources.value.length - 1)
|
||||||
|
|
||||||
|
if (currentChat?.text && currentChat.text !== '') {
|
||||||
|
updateChatSome(
|
||||||
|
+uuid,
|
||||||
|
dataSources.value.length - 1,
|
||||||
|
{
|
||||||
|
text: `${currentChat.text}\n[${errorMessage}]`,
|
||||||
|
error: false,
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
updateChat(
|
updateChat(
|
||||||
+uuid,
|
+uuid,
|
||||||
@@ -165,7 +189,7 @@ async function onRegenerate(index: number) {
|
|||||||
index,
|
index,
|
||||||
{
|
{
|
||||||
dateTime: new Date().toLocaleString(),
|
dateTime: new Date().toLocaleString(),
|
||||||
text: 'Aha, Let me think again...',
|
text: '',
|
||||||
inversion: false,
|
inversion: false,
|
||||||
error: false,
|
error: false,
|
||||||
loading: true,
|
loading: true,
|
||||||
@@ -210,10 +234,18 @@ async function onRegenerate(index: number) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
catch (error: any) {
|
catch (error: any) {
|
||||||
let errorMessage = error?.message ?? 'Something went wrong, please try again later.'
|
if (error.message === 'canceled') {
|
||||||
|
updateChatSome(
|
||||||
|
+uuid,
|
||||||
|
index,
|
||||||
|
{
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (error.message === 'canceled')
|
const errorMessage = error?.message ?? 'Something went wrong, please try again later.'
|
||||||
errorMessage = 'Request canceled. Please try again.'
|
|
||||||
|
|
||||||
updateChat(
|
updateChat(
|
||||||
+uuid,
|
+uuid,
|
||||||
@@ -343,8 +375,8 @@ onUnmounted(() => {
|
|||||||
@regenerate="onRegenerate(index)"
|
@regenerate="onRegenerate(index)"
|
||||||
@delete="handleDelete(index)"
|
@delete="handleDelete(index)"
|
||||||
/>
|
/>
|
||||||
<div class="flex justify-center">
|
<div class="sticky bottom-0 left-0 flex justify-center">
|
||||||
<NButton v-if="loading" ghost @click="handleStop">
|
<NButton v-if="loading" type="warning" @click="handleStop">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<SvgIcon icon="ri:stop-circle-line" />
|
<SvgIcon icon="ri:stop-circle-line" />
|
||||||
</template>
|
</template>
|
||||||
|
@@ -1,11 +1,14 @@
|
|||||||
<script setup lang='ts'>
|
<script setup lang='ts'>
|
||||||
import { computed, h, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { NDropdown } from 'naive-ui'
|
import { NDropdown } from 'naive-ui'
|
||||||
import { HoverButton, Setting, SvgIcon, UserAvatar } from '@/components/common'
|
import { HoverButton, Setting, SvgIcon, UserAvatar } from '@/components/common'
|
||||||
import { useAppStore } from '@/store'
|
import { useAppStore } from '@/store'
|
||||||
|
import { useIconRender } from '@/hooks/useIconRender'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
|
const { iconRender } = useIconRender()
|
||||||
|
|
||||||
const show = ref(false)
|
const show = ref(false)
|
||||||
|
|
||||||
const theme = computed(() => appStore.theme)
|
const theme = computed(() => appStore.theme)
|
||||||
@@ -14,26 +17,20 @@ const options = [
|
|||||||
{
|
{
|
||||||
label: 'Dark',
|
label: 'Dark',
|
||||||
key: 'dark',
|
key: 'dark',
|
||||||
icon: renderIcon('ri:moon-foggy-line'),
|
icon: iconRender({ icon: 'ri:moon-foggy-line' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Light',
|
label: 'Light',
|
||||||
key: 'light',
|
key: 'light',
|
||||||
icon: renderIcon('ri:sun-foggy-line'),
|
icon: iconRender({ icon: 'ri:sun-foggy-line' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Auto',
|
label: 'Auto',
|
||||||
key: 'auto',
|
key: 'auto',
|
||||||
icon: renderIcon('ri:contrast-line'),
|
icon: iconRender({ icon: 'ri:contrast-line' }),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
function renderIcon(icon: string) {
|
|
||||||
return () => {
|
|
||||||
return h(SvgIcon, { icon })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleThemeChange(key: 'light' | 'dark' | 'auto') {
|
function handleThemeChange(key: 'light' | 'dark' | 'auto') {
|
||||||
appStore.setTheme(key)
|
appStore.setTheme(key)
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,6 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
},
|
},
|
||||||
// @vueuse/core 不能通过 vue-tsc 检查,所以这里需要忽略,以后将移除
|
|
||||||
"types": ["vite/client", "node", "naive-ui/volar", "web-bluetooth"]
|
"types": ["vite/client", "node", "naive-ui/volar", "web-bluetooth"]
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "dist", "service"]
|
"exclude": ["node_modules", "dist", "service"]
|
||||||
|
Reference in New Issue
Block a user