This commit is contained in:
Your Name
2026-07-22 09:49:30 +08:00
parent 7803c46b96
commit 2530ddada6
10 changed files with 1932 additions and 629 deletions
+80 -26
View File
@@ -193,36 +193,44 @@ export const useChatStore = defineStore('chat', () => {
}
}
async function sendMessage(content, attachments = [], agentId = selectedAgentId.value) {
if (!currentId.value) {
await createConversation(selectedModelId.value)
} else if (selectedModelId.value != null) {
// 发送前确保当前会话已绑定侧边栏所选模型
const conv = conversations.value.find(c => c.id === currentId.value)
if (conv && Number(conv.model_id) !== Number(selectedModelId.value)) {
await setSelectedModel(selectedModelId.value)
}
}
async function sendMessage(content, attachments = [], agentId = selectedAgentId.value, options = {}) {
if (sending.value) return
const userMsg = {
id: Date.now(),
role: 'user',
content,
attachments,
created_at: new Date().toISOString()
}
messages.value.push(userMsg)
sending.value = true
streamingContent.value = ''
streamingAttachments.value = []
let userMsg = null
try {
await streamChat(content, attachments, agentId)
if (!currentId.value) {
await createConversation(selectedModelId.value)
} else if (selectedModelId.value != null) {
// 发送前确保当前会话已绑定侧边栏所选模型
const conv = conversations.value.find(c => c.id === currentId.value)
if (conv && Number(conv.model_id) !== Number(selectedModelId.value)) {
await setSelectedModel(selectedModelId.value)
}
}
userMsg = {
id: Date.now(),
role: 'user',
content,
attachments,
created_at: new Date().toISOString()
}
messages.value.push(userMsg)
await streamChat(content, attachments, agentId, options)
await loadMessages()
} catch (e) {
// 连接断开/代理超时:任务可能已落库为 pending,先同步再决定是否抛错
messages.value = messages.value.filter(m => m.id !== userMsg.id)
const cancelled = e?.name === 'AbortError' || options.signal?.aborted
if (userMsg) {
messages.value = messages.value.filter(m => m.id !== userMsg.id)
}
await loadMessages().catch(() => {})
if (cancelled) throw e
if (hasPendingJobs(messages.value)) {
startPendingJobPolling()
return
@@ -235,7 +243,7 @@ export const useChatStore = defineStore('chat', () => {
}
}
async function streamChat(content, attachments, agentId = null) {
async function streamChat(content, attachments, agentId = null, options = {}) {
const token = localStorage.getItem('token')
const response = await fetch('/api/chat/completions', {
method: 'POST',
@@ -243,11 +251,13 @@ export const useChatStore = defineStore('chat', () => {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
signal: options.signal,
body: JSON.stringify({
conversation_id: currentId.value,
content,
attachments,
agent_id: agentId || null,
image_tool: options.imageTool || null,
stream: true
})
})
@@ -268,8 +278,47 @@ export const useChatStore = defineStore('chat', () => {
const decoder = new TextDecoder()
let buffer = ''
let fullContent = ''
let receivedContent = ''
let gotAttachments = false
let pendingDone = false
let agentRenderVersion = 0
let agentRenderQueue = Promise.resolve()
const isAgentStream = !!agentId
const appendVisibleContent = (content) => {
if (!content) return
receivedContent += content
if (!isAgentStream) {
fullContent += content
streamingContent.value = fullContent
return
}
const version = agentRenderVersion
const characters = Array.from(content)
const frameCount = Math.min(10, Math.max(2, Math.ceil(characters.length / 8)))
const frameSize = Math.ceil(characters.length / frameCount)
agentRenderQueue = agentRenderQueue.then(async () => {
for (let index = 0; index < characters.length; index += frameSize) {
if (version !== agentRenderVersion) return
fullContent += characters.slice(index, index + frameSize).join('')
streamingContent.value = fullContent
if (index + frameSize < characters.length) {
await new Promise(resolve => setTimeout(resolve, 14))
}
}
})
}
const replaceVisibleContent = (content) => {
agentRenderVersion++
agentRenderQueue = Promise.resolve()
receivedContent = content
fullContent = content
streamingContent.value = content
}
while (true) {
const { done, value } = await reader.read()
@@ -285,6 +334,7 @@ export const useChatStore = defineStore('chat', () => {
const { event, data } = parseSseBlock(block)
if (event === 'error') {
agentRenderVersion++
throw new Error(data?.message || 'AI 请求失败')
}
@@ -298,8 +348,11 @@ export const useChatStore = defineStore('chat', () => {
}
if (event === 'message' && data?.content) {
fullContent += data.content
streamingContent.value = fullContent
appendVisibleContent(data.content)
}
if (event === 'replace' && typeof data?.content === 'string') {
replaceVisibleContent(data.content)
}
if (event === 'done') {
@@ -309,9 +362,8 @@ export const useChatStore = defineStore('chat', () => {
streamingContent.value = data.content
}
}
if (data?.content && !fullContent) {
fullContent = data.content
streamingContent.value = fullContent
if (data?.content && !receivedContent) {
appendVisibleContent(data.content)
}
if (Array.isArray(data?.attachments) && data.attachments.length) {
streamingAttachments.value = data.attachments
@@ -321,6 +373,8 @@ export const useChatStore = defineStore('chat', () => {
}
}
await agentRenderQueue
if (pendingDone) {
await loadMessages().catch(() => {})
startPendingJobPolling()