|
|
|
@ -11,7 +11,7 @@ import {
|
|
|
|
|
useIonRouter
|
|
|
|
|
} from '@ionic/vue'
|
|
|
|
|
import { showConfirm, showError, showHelpToast } from '@/utils/dialog'
|
|
|
|
|
import { ChatCompleteChunkInfo } from '@/types/base'
|
|
|
|
|
import { ChatCompleteChunkInfo, ChatCompleteMessage } from '@/types/base'
|
|
|
|
|
import { DEFAULT_BOT_AVATAR, secondTimestamp, setPageTitle } from '@/utils/other'
|
|
|
|
|
import { pinConversation } from '@/utils/actions'
|
|
|
|
|
import { moduleConversationRoute } from '@/types/enum'
|
|
|
|
@ -39,6 +39,7 @@ const mainRouterOutlet = inject<Ref<IonRouterOutletInstance>>('mainRouterOutlet'
|
|
|
|
|
const state = reactive({
|
|
|
|
|
conversationLoading: false,
|
|
|
|
|
errorMessage: null as string | null,
|
|
|
|
|
chatCompleteError: null as null | string,
|
|
|
|
|
conversationTitle: '加载中...',
|
|
|
|
|
conversationId: 0,
|
|
|
|
|
pinned: false,
|
|
|
|
@ -55,6 +56,8 @@ const state = reactive({
|
|
|
|
|
lastUserMsgId: [] as [number, string] | [],
|
|
|
|
|
lastAssistantMsgId: [] as [number, string] | [],
|
|
|
|
|
|
|
|
|
|
streamingMessage: null as null | string,
|
|
|
|
|
|
|
|
|
|
editingId: null as [number, string] | null,
|
|
|
|
|
formMessage: '',
|
|
|
|
|
pointCost: '0',
|
|
|
|
@ -108,11 +111,20 @@ const onSendMessage = async () => {
|
|
|
|
|
ignoreAutoScroll = false
|
|
|
|
|
|
|
|
|
|
state.conversationLoading = true
|
|
|
|
|
|
|
|
|
|
let latestChunk: ConversationChunkInfo | null = null
|
|
|
|
|
let newUserMessage: ChatCompleteMessage | null = null
|
|
|
|
|
try {
|
|
|
|
|
let question = state.formMessage
|
|
|
|
|
|
|
|
|
|
let paramEditingId: [number, string] | undefined = undefined
|
|
|
|
|
if (state.editingId?.length && state.editingId[1] !== 'error-user-message') {
|
|
|
|
|
paramEditingId = state.editingId
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const startRes = await api.chat.startChatComplete(pageStore.title, question, {
|
|
|
|
|
conversationId: state.conversationId,
|
|
|
|
|
editMessageId: state.editingId || undefined,
|
|
|
|
|
editMessageId: paramEditingId,
|
|
|
|
|
botId: state.botId,
|
|
|
|
|
inCollection: settingsStore.collectionMode,
|
|
|
|
|
extractLimit: settingsStore.docExtractLimit
|
|
|
|
@ -120,7 +132,14 @@ const onSendMessage = async () => {
|
|
|
|
|
state.formMessage = ''
|
|
|
|
|
await editingToast?.dismiss()
|
|
|
|
|
|
|
|
|
|
let latestChunk: ConversationChunkInfo
|
|
|
|
|
// 删除所有遇到错误的消息
|
|
|
|
|
if (state.chatCompleteError !== null) {
|
|
|
|
|
state.messageChunkList.forEach((chunk) => {
|
|
|
|
|
chunk.message_data = chunk.message_data.filter((message) => message.id !== 'error-user-message')
|
|
|
|
|
})
|
|
|
|
|
state.chatCompleteError = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (state.messageChunkList.length === 0) {
|
|
|
|
|
state.messageChunkList.push({
|
|
|
|
|
id: startRes.chunk_id,
|
|
|
|
@ -150,6 +169,7 @@ const onSendMessage = async () => {
|
|
|
|
|
message_data: []
|
|
|
|
|
}
|
|
|
|
|
state.messageChunkList.unshift(latestChunk)
|
|
|
|
|
latestChunk = state.messageChunkList[0] // 获取reactive对象
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -158,33 +178,35 @@ const onSendMessage = async () => {
|
|
|
|
|
latestChunk.message_data.push({
|
|
|
|
|
role: 'user',
|
|
|
|
|
content: question,
|
|
|
|
|
time: Date.now(),
|
|
|
|
|
time: secondTimestamp(),
|
|
|
|
|
tokens: 0
|
|
|
|
|
})
|
|
|
|
|
let userMessage = latestChunk.message_data[latestChunk.message_data.length - 1]
|
|
|
|
|
newUserMessage = userMessage
|
|
|
|
|
|
|
|
|
|
latestChunk.message_data.push({
|
|
|
|
|
role: 'assistant',
|
|
|
|
|
content: '',
|
|
|
|
|
tokens: 0,
|
|
|
|
|
streaming: true
|
|
|
|
|
state.streamingMessage = ''
|
|
|
|
|
const chatCompleteRes = await api.chat.chatCompleteStreamOutput(startRes.task_id, (message) => {
|
|
|
|
|
state.streamingMessage += message
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
let userMessage = latestChunk.message_data[latestChunk.message_data.length - 2]
|
|
|
|
|
let assistantMessage = latestChunk.message_data[latestChunk.message_data.length - 1]
|
|
|
|
|
let responseMessage = state.streamingMessage
|
|
|
|
|
|
|
|
|
|
const chatCompleteRes = await api.chat.chatCompleteStreamOutput(startRes.task_id, (message) => {
|
|
|
|
|
assistantMessage.content += message
|
|
|
|
|
latestChunk.message_data.push({
|
|
|
|
|
role: 'assistant',
|
|
|
|
|
id: chatCompleteRes.response_message_id,
|
|
|
|
|
content: state.streamingMessage,
|
|
|
|
|
time: secondTimestamp(),
|
|
|
|
|
tokens: chatCompleteRes.message_tokens,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assistantMessage.streaming = false
|
|
|
|
|
|
|
|
|
|
state.streamingMessage = null
|
|
|
|
|
|
|
|
|
|
userMessage.id = chatCompleteRes.question_message_id
|
|
|
|
|
assistantMessage.id = chatCompleteRes.response_message_id
|
|
|
|
|
state.lastUserMsgId = [startRes.chunk_id, chatCompleteRes.question_message_id]
|
|
|
|
|
state.lastAssistantMsgId = [startRes.chunk_id, chatCompleteRes.response_message_id]
|
|
|
|
|
|
|
|
|
|
let description = assistantMessage.content.substring(0, 150)
|
|
|
|
|
if (description.length < assistantMessage.content.length) {
|
|
|
|
|
let description = responseMessage.substring(0, 150)
|
|
|
|
|
if (description.length < responseMessage.length) {
|
|
|
|
|
description += '...'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -198,7 +220,7 @@ const onSendMessage = async () => {
|
|
|
|
|
page_title: pageStore.title,
|
|
|
|
|
pinned: false,
|
|
|
|
|
description: description,
|
|
|
|
|
updated_at: Math.floor(Date.now() / 1000),
|
|
|
|
|
updated_at: secondTimestamp(),
|
|
|
|
|
extra: {}
|
|
|
|
|
})
|
|
|
|
|
setPageTitle('未命名对话')
|
|
|
|
@ -206,7 +228,7 @@ const onSendMessage = async () => {
|
|
|
|
|
|
|
|
|
|
state.conversationId = startRes.conversation_id
|
|
|
|
|
|
|
|
|
|
let newUrl = route.fullPath.replace(/\/\d+$/, '') + '/' + startRes.conversation_id
|
|
|
|
|
let newUrl = route.path.replace(/\/\d+\/?$/, '') + '/' + startRes.conversation_id
|
|
|
|
|
history.replaceState({}, '', newUrl)
|
|
|
|
|
|
|
|
|
|
conversationStore.currentId = startRes.conversation_id
|
|
|
|
@ -215,7 +237,7 @@ const onSendMessage = async () => {
|
|
|
|
|
} else {
|
|
|
|
|
conversationStore.updateById(startRes.conversation_id, {
|
|
|
|
|
description,
|
|
|
|
|
updated_at: Math.floor(Date.now() / 1000),
|
|
|
|
|
updated_at: secondTimestamp(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -226,12 +248,14 @@ const onSendMessage = async () => {
|
|
|
|
|
})
|
|
|
|
|
setPageTitle(chatCompleteRes.delta_data.title)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
conversationStore.sort() // 排序对话列表
|
|
|
|
|
|
|
|
|
|
state.conversationLoading = false
|
|
|
|
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
stopAutoScroll()
|
|
|
|
|
})
|
|
|
|
|
}, 150)
|
|
|
|
|
|
|
|
|
|
// 更新用户积分
|
|
|
|
|
let pointCost = chatCompleteRes.point_cost
|
|
|
|
@ -245,6 +269,15 @@ const onSendMessage = async () => {
|
|
|
|
|
userStore.refresh()
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
state.conversationLoading = false
|
|
|
|
|
state.streamingMessage = null
|
|
|
|
|
state.chatCompleteError = err.message
|
|
|
|
|
|
|
|
|
|
if (latestChunk && newUserMessage) {
|
|
|
|
|
if (!newUserMessage.id) {
|
|
|
|
|
newUserMessage.id = 'error-user-message'
|
|
|
|
|
}
|
|
|
|
|
state.lastUserMsgId = [latestChunk.id, newUserMessage.id]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -342,6 +375,7 @@ const loadConversation = async () => {
|
|
|
|
|
} else {
|
|
|
|
|
state.messageChunkList = []
|
|
|
|
|
state.errorMessage = null
|
|
|
|
|
state.chatCompleteError = null
|
|
|
|
|
state.formMessage = ''
|
|
|
|
|
|
|
|
|
|
conversationStore.currentId = state.conversationId
|
|
|
|
@ -379,16 +413,21 @@ const loadConversation = async () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置最新消息ID
|
|
|
|
|
let latestChunk = state.messageChunkList[0]
|
|
|
|
|
latestChunk.message_data.forEach((message) => {
|
|
|
|
|
if (message.id) {
|
|
|
|
|
if (message.role === 'user') {
|
|
|
|
|
state.lastUserMsgId = [latestChunk.id, message.id]
|
|
|
|
|
} else {
|
|
|
|
|
state.lastAssistantMsgId = [latestChunk.id, message.id]
|
|
|
|
|
for (let chunk of state.messageChunkList) {
|
|
|
|
|
chunk.message_data.forEach((message) => {
|
|
|
|
|
if (message.id) {
|
|
|
|
|
if (message.role === 'user') {
|
|
|
|
|
state.lastUserMsgId = [chunk.id, message.id]
|
|
|
|
|
} else {
|
|
|
|
|
state.lastAssistantMsgId = [chunk.id, message.id]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (state.lastUserMsgId.length > 0 && state.lastAssistantMsgId.length > 0) {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
state.errorMessage = err.message
|
|
|
|
@ -464,7 +503,7 @@ const onEditMessage = async (chunkId: number, messageId?: string) => {
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await editingToast.present()
|
|
|
|
|
|
|
|
|
@ -498,6 +537,23 @@ const onChangeResponse = async (chunkId: number, messageId?: string) => {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const onChangeLastResponse = async () => {
|
|
|
|
|
let lastUserMessageIdx = state.messageChunkList[0]?.message_data
|
|
|
|
|
.findLastIndex((message) => message.role === 'user')
|
|
|
|
|
|
|
|
|
|
if (lastUserMessageIdx === undefined || lastUserMessageIdx < 0) {
|
|
|
|
|
showError('未找到先前的提问,请刷新页面重试', '提示')
|
|
|
|
|
} else {
|
|
|
|
|
if (await showConfirm('你确定要重新生成这段对话吗?')) {
|
|
|
|
|
let lastUserMessage = state.messageChunkList[0].message_data[lastUserMessageIdx]
|
|
|
|
|
// 删除最后一个用户提问
|
|
|
|
|
state.messageChunkList[0].message_data.splice(lastUserMessageIdx, 1)
|
|
|
|
|
state.formMessage = lastUserMessage.content
|
|
|
|
|
onSendMessage()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const onCreateBranch = async (chunkId: number, messageId?: string) => {
|
|
|
|
|
if (!messageId) {
|
|
|
|
|
showError('无法在这个回答上创建分支,请等待当前生成完成', '提示')
|
|
|
|
@ -514,7 +570,7 @@ const onCreateBranch = async (chunkId: number, messageId?: string) => {
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 生成新分支的标题
|
|
|
|
|
let newTitle = `分支:${state.conversationTitle}`;
|
|
|
|
|
let newTitle = `分支:${state.conversationTitle}`
|
|
|
|
|
for (var i = 1; i <= 99; i++) {
|
|
|
|
|
newTitle = `分支${i.toString().padStart(2, '0')}:${state.conversationTitle}`
|
|
|
|
|
if (!conversationStore.list.find((conversation) => conversation.title === newTitle)) {
|
|
|
|
@ -741,7 +797,7 @@ onMounted(async () => {
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
if (contentRef.value) {
|
|
|
|
|
let contentEl = contentRef.value.$el
|
|
|
|
|
contentEl.scrollEl.removeEventListener('scroll', onMainContentScroll)
|
|
|
|
|
contentEl.scrollEl?.removeEventListener('scroll', onMainContentScroll)
|
|
|
|
|
if (mainContentResizeObserver) {
|
|
|
|
|
mainContentResizeObserver.disconnect()
|
|
|
|
|
mainContentResizeObserver = null
|
|
|
|
@ -814,6 +870,13 @@ watch(() => conversationInfo.value, (currentConversation) => {
|
|
|
|
|
@click-change="onChangeResponse(chunk.id, message.id)"></chat-message>
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- 流式输出消息 -->
|
|
|
|
|
<div v-if="state.streamingMessage !== null || state.chatCompleteError !== null" class="message-chunk">
|
|
|
|
|
<chat-message :sender="state.botName" :avatar="state.botAvatar"
|
|
|
|
|
:content="state.streamingMessage ?? undefined" :error="state.chatCompleteError ?? undefined" assistant cursor
|
|
|
|
|
:change="state.chatCompleteError !== null"
|
|
|
|
|
@click-change="onChangeLastResponse()"></chat-message>
|
|
|
|
|
</div>
|
|
|
|
|
</transition-group>
|
|
|
|
|
</ion-content>
|
|
|
|
|
|
|
|
|
|