chore: version 2.7.1 (#99)
* feat: 调整流输出为实验性质 * feat: 取消回答按钮 * feat: 更新版本查看 * feat: 单消息复制和删除功能 * feat: 消除警告 * feat: 优化删除功能 * chore: version 2.7.1
This commit is contained in:
parent
10058f151c
commit
1e2f893ef6
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,3 +1,17 @@
|
||||||
|
## v2.7.1
|
||||||
|
|
||||||
|
`2023-02-23`
|
||||||
|
|
||||||
|
因为消息流在 `accessToken` 中存在解析失败和消息不完整等一系列的问题,调整回正常消息形式
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
- 现在可以中断请求过长没有答复的消息
|
||||||
|
- 现在可以删除单条消息
|
||||||
|
- 设置中显示当前版本信息
|
||||||
|
|
||||||
|
### BugFix
|
||||||
|
- 回退 `2.7.0` 的消息不稳定的问题
|
||||||
|
|
||||||
## v2.7.0
|
## v2.7.0
|
||||||
|
|
||||||
`2023-02-23`
|
`2023-02-23`
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "chatgpt-web",
|
"name": "chatgpt-web",
|
||||||
"version": "2.7.0",
|
"version": "2.7.1",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "ChatGPT Web",
|
"description": "ChatGPT Web",
|
||||||
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
|
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
|
||||||
|
|
|
@ -65,6 +65,7 @@ async function chatReply(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 实验性质的函数,用于处理聊天过程中的中间结果 */
|
||||||
async function chatReplyProcess(
|
async function chatReplyProcess(
|
||||||
message: string,
|
message: string,
|
||||||
lastContext?: { conversationId?: string; parentMessageId?: string },
|
lastContext?: { conversationId?: string; parentMessageId?: string },
|
||||||
|
|
|
@ -26,6 +26,7 @@ router.post('/chat', async (req, res) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 实验性质的函数,用于处理聊天过程中的中间结果 */
|
||||||
router.post('/chat-process', async (req, res) => {
|
router.post('/chat-process', async (req, res) => {
|
||||||
res.setHeader('Content-type', 'application/octet-stream')
|
res.setHeader('Content-type', 'application/octet-stream')
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ export function fetchChatAPI<T = any>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 实验性质的函数,用于处理聊天过程中的中间结果 */
|
||||||
export function fetchChatAPIProcess<T = any>(
|
export function fetchChatAPIProcess<T = any>(
|
||||||
params: {
|
params: {
|
||||||
prompt: string
|
prompt: string
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script setup lang='ts'>
|
<script setup lang='ts'>
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { NCard, NModal } from 'naive-ui'
|
import { NCard, NModal } from 'naive-ui'
|
||||||
|
import pkg from '../../../../package.json'
|
||||||
import { fetchChatConfig } from '@/api'
|
import { fetchChatConfig } from '@/api'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -55,9 +56,16 @@ watch(
|
||||||
<NModal v-model:show="show" style="width: 80%; max-width: 460px;">
|
<NModal v-model:show="show" style="width: 80%; max-width: 460px;">
|
||||||
<NCard>
|
<NCard>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h1 class="text-xl font-bold">
|
<h2 class="text-xl font-bold text-center">
|
||||||
当前后台设置
|
Version - {{ pkg.version }}
|
||||||
</h1>
|
</h2>
|
||||||
|
<hr>
|
||||||
|
<p>
|
||||||
|
此项目开源于
|
||||||
|
<a class="text-blue-600" href="https://github.com/Chanzhaoyu/chatgpt-web" target="_blank">Github</a>
|
||||||
|
,免费并且协议为 MIT,其他来源均为盗版,使用时请注意。如果你觉得此项目对你有帮助,请帮我点个 Star,谢谢!
|
||||||
|
</p>
|
||||||
|
<hr>
|
||||||
<p>API方式:{{ config?.apiModel ?? '-' }}</p>
|
<p>API方式:{{ config?.apiModel ?? '-' }}</p>
|
||||||
<p>反向代理:{{ config?.reverseProxy ?? '-' }}</p>
|
<p>反向代理:{{ config?.reverseProxy ?? '-' }}</p>
|
||||||
<p>超时时间:{{ config?.timeoutMs ?? '-' }}</p>
|
<p>超时时间:{{ config?.timeoutMs ?? '-' }}</p>
|
||||||
|
|
|
@ -2,6 +2,8 @@ import type { App, Directive } from 'vue'
|
||||||
import hljs from 'highlight.js'
|
import hljs from 'highlight.js'
|
||||||
import includeCode from '@/utils/functions/includeCode'
|
import includeCode from '@/utils/functions/includeCode'
|
||||||
|
|
||||||
|
hljs.configure({ ignoreUnescapedHTML: true })
|
||||||
|
|
||||||
function highlightCode(el: HTMLElement) {
|
function highlightCode(el: HTMLElement) {
|
||||||
if (includeCode(el.textContent))
|
if (includeCode(el.textContent))
|
||||||
hljs.highlightBlock(el)
|
hljs.highlightBlock(el)
|
||||||
|
|
|
@ -110,6 +110,22 @@ export const useChatStore = defineStore('chat-store', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
deleteChatByUuid(uuid: number, index: number) {
|
||||||
|
if (!uuid || uuid === 0) {
|
||||||
|
if (this.chat.length) {
|
||||||
|
this.chat[0].data.splice(index, 1)
|
||||||
|
this.recordState()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatIndex = this.chat.findIndex(item => item.uuid === uuid)
|
||||||
|
if (chatIndex !== -1) {
|
||||||
|
this.chat[chatIndex].data.splice(index, 1)
|
||||||
|
this.recordState()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
clearChatByUuid(uuid: number) {
|
clearChatByUuid(uuid: number) {
|
||||||
if (!uuid || uuid === 0) {
|
if (!uuid || uuid === 0) {
|
||||||
if (this.chat.length) {
|
if (this.chat.length) {
|
||||||
|
|
|
@ -36,7 +36,7 @@ const text = computed(() => {
|
||||||
<template>
|
<template>
|
||||||
<div :class="wrapClass">
|
<div :class="wrapClass">
|
||||||
<template v-if="loading">
|
<template v-if="loading">
|
||||||
<span class="w-[3px] h-[20px] block animate-blink" />
|
<span class="w-[5px] h-[20px] block animate-blink" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<code v-if="includeCode(text)" v-highlight class="leading-relaxed" v-text="text" />
|
<code v-if="includeCode(text)" v-highlight class="leading-relaxed" v-text="text" />
|
||||||
|
|
|
@ -13,12 +13,18 @@ interface Props {
|
||||||
|
|
||||||
interface Emit {
|
interface Emit {
|
||||||
(ev: 'regenerate'): void
|
(ev: 'regenerate'): void
|
||||||
|
(ev: 'copy'): void
|
||||||
|
(ev: 'delete'): void
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>()
|
defineProps<Props>()
|
||||||
|
|
||||||
const emit = defineEmits<Emit>()
|
const emit = defineEmits<Emit>()
|
||||||
|
|
||||||
|
function handleDelete() {
|
||||||
|
emit('delete')
|
||||||
|
}
|
||||||
|
|
||||||
function handleRegenerate() {
|
function handleRegenerate() {
|
||||||
emit('regenerate')
|
emit('regenerate')
|
||||||
}
|
}
|
||||||
|
@ -36,15 +42,28 @@ function handleRegenerate() {
|
||||||
<span class="text-xs text-[#b4bbc4]">
|
<span class="text-xs text-[#b4bbc4]">
|
||||||
{{ dateTime }}
|
{{ dateTime }}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex items-end mt-2">
|
<div class="flex items-end gap-2 mt-2" :class="[inversion ? 'flex-row-reverse' : 'flex-row']">
|
||||||
<Text :inversion="inversion" :error="error" :text="text" :loading="loading" />
|
<Text
|
||||||
<button
|
:inversion="inversion"
|
||||||
v-if="!inversion && !loading"
|
:error="error"
|
||||||
class="mb-2 ml-2 transition text-neutral-400 hover:text-neutral-800"
|
:text="text"
|
||||||
@click="handleRegenerate"
|
:loading="loading"
|
||||||
>
|
/>
|
||||||
<SvgIcon icon="ri:restart-line" />
|
<div class="flex flex-col">
|
||||||
</button>
|
<button
|
||||||
|
v-if="!inversion"
|
||||||
|
class="mb-2 transition text-neutral-400 hover:text-neutral-800"
|
||||||
|
@click="handleRegenerate"
|
||||||
|
>
|
||||||
|
<SvgIcon icon="ri:restart-line" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="mb-1 transition text-neutral-400 hover:text-neutral-800"
|
||||||
|
@click="handleDelete"
|
||||||
|
>
|
||||||
|
<SvgIcon icon="ri:delete-bin-6-line" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
<script setup lang='ts'>
|
<script setup lang='ts'>
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { NButton, NInput, useDialog } from 'naive-ui'
|
import { NButton, NInput, useDialog, useMessage } from 'naive-ui'
|
||||||
import { Message } from './components'
|
import { Message } from './components'
|
||||||
import { useScroll } from './hooks/useScroll'
|
import { useScroll } from './hooks/useScroll'
|
||||||
import { useChat } from './hooks/useChat'
|
import { useChat } from './hooks/useChat'
|
||||||
import { HoverButton, SvgIcon } from '@/components/common'
|
import { HoverButton, SvgIcon } from '@/components/common'
|
||||||
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
||||||
import { useChatStore } from '@/store'
|
import { useChatStore } from '@/store'
|
||||||
import { fetchChatAPIProcess } from '@/api'
|
import { fetchChatAPI } from '@/api'
|
||||||
|
|
||||||
let controller = new AbortController()
|
let controller = new AbortController()
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
|
const ms = useMessage()
|
||||||
|
|
||||||
const chatStore = useChatStore()
|
const chatStore = useChatStore()
|
||||||
|
|
||||||
|
@ -80,39 +81,22 @@ async function onConversation() {
|
||||||
)
|
)
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
|
|
||||||
let offset = 0
|
|
||||||
try {
|
try {
|
||||||
await fetchChatAPIProcess<Chat.ConversationResponse>({
|
const { data } = await fetchChatAPI<Chat.ConversationResponse>(message, options, controller.signal)
|
||||||
prompt: message,
|
updateChat(
|
||||||
options,
|
+uuid,
|
||||||
signal: controller.signal,
|
dataSources.value.length - 1,
|
||||||
onDownloadProgress: ({ event }) => {
|
{
|
||||||
const xhr = event.target
|
dateTime: new Date().toLocaleString(),
|
||||||
const { responseText } = xhr
|
text: data.text ?? '',
|
||||||
const chunk = responseText.substring(offset)
|
inversion: false,
|
||||||
offset = responseText.length
|
error: false,
|
||||||
try {
|
loading: false,
|
||||||
const data = JSON.parse(chunk)
|
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
|
||||||
updateChat(
|
requestOptions: { prompt: message, options: { ...options } },
|
||||||
+uuid,
|
|
||||||
dataSources.value.length - 1,
|
|
||||||
{
|
|
||||||
dateTime: new Date().toLocaleString(),
|
|
||||||
text: data.text ?? '',
|
|
||||||
inversion: false,
|
|
||||||
error: false,
|
|
||||||
loading: false,
|
|
||||||
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
|
|
||||||
requestOptions: { prompt: message, options: { ...options } },
|
|
||||||
},
|
|
||||||
)
|
|
||||||
scrollToBottom()
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
|
scrollToBottom()
|
||||||
}
|
}
|
||||||
catch (error: any) {
|
catch (error: any) {
|
||||||
let errorMessage = error?.message ?? 'Something went wrong, please try again later.'
|
let errorMessage = error?.message ?? 'Something went wrong, please try again later.'
|
||||||
|
@ -136,7 +120,6 @@ async function onConversation() {
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
offset = 0
|
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,41 +155,24 @@ async function onRegenerate(index: number) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
let offset = 0
|
|
||||||
try {
|
try {
|
||||||
await fetchChatAPIProcess<Chat.ConversationResponse>({
|
const { data } = await fetchChatAPI<Chat.ConversationResponse>(message, options, controller.signal)
|
||||||
prompt: message,
|
updateChat(
|
||||||
options,
|
+uuid,
|
||||||
signal: controller.signal,
|
index,
|
||||||
onDownloadProgress: ({ event }) => {
|
{
|
||||||
const xhr = event.target
|
dateTime: new Date().toLocaleString(),
|
||||||
const { responseText } = xhr
|
text: data.text ?? '',
|
||||||
const chunk = responseText.substring(offset)
|
inversion: false,
|
||||||
offset = responseText.length
|
error: false,
|
||||||
try {
|
loading: false,
|
||||||
const data = JSON.parse(chunk)
|
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
|
||||||
updateChat(
|
requestOptions: { prompt: message, ...options },
|
||||||
+uuid,
|
|
||||||
index,
|
|
||||||
{
|
|
||||||
dateTime: new Date().toLocaleString(),
|
|
||||||
text: data.text ?? '',
|
|
||||||
inversion: false,
|
|
||||||
error: false,
|
|
||||||
loading: false,
|
|
||||||
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
|
|
||||||
requestOptions: { prompt: message, ...options },
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
catch (error: any) {
|
catch (error: any) {
|
||||||
let errorMessage = error?.message ?? 'Something went wrong, please try again later.'
|
let errorMessage = 'Something went wrong, please try again later.'
|
||||||
|
|
||||||
if (error.message === 'canceled')
|
if (error.message === 'canceled')
|
||||||
errorMessage = 'Request canceled. Please try again.'
|
errorMessage = 'Request canceled. Please try again.'
|
||||||
|
@ -227,10 +193,25 @@ async function onRegenerate(index: number) {
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
offset = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleDelete(index: number) {
|
||||||
|
if (loading.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
dialog.warning({
|
||||||
|
title: 'Delete Message',
|
||||||
|
content: 'Are you sure to delete this message?',
|
||||||
|
positiveText: 'Yes',
|
||||||
|
negativeText: 'No',
|
||||||
|
onPositiveClick: () => {
|
||||||
|
chatStore.deleteChatByUuid(+uuid, index)
|
||||||
|
ms.success('Message deleted successfully.')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function handleClear() {
|
function handleClear() {
|
||||||
if (loading.value)
|
if (loading.value)
|
||||||
return
|
return
|
||||||
|
@ -253,6 +234,13 @@ function handleEnter(event: KeyboardEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleStop() {
|
||||||
|
if (loading.value) {
|
||||||
|
controller.abort()
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const buttonDisabled = computed(() => {
|
const buttonDisabled = computed(() => {
|
||||||
return loading.value || !prompt.value || prompt.value.trim() === ''
|
return loading.value || !prompt.value || prompt.value.trim() === ''
|
||||||
})
|
})
|
||||||
|
@ -302,7 +290,16 @@ onUnmounted(() => {
|
||||||
:error="item.error"
|
:error="item.error"
|
||||||
:loading="item.loading"
|
:loading="item.loading"
|
||||||
@regenerate="onRegenerate(index)"
|
@regenerate="onRegenerate(index)"
|
||||||
|
@delete="handleDelete(index)"
|
||||||
/>
|
/>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<NButton v-if="loading" ghost @click="handleStop">
|
||||||
|
<template #icon>
|
||||||
|
<SvgIcon icon="ri:stop-circle-line" />
|
||||||
|
</template>
|
||||||
|
Stop Responding
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue