Browse Source

fix(frontend): 正文完成后立即解除发送状态并后台补齐引用来源

feature/test
wei-py 3 weeks ago
parent
commit
8004efc87d
  1. 31
      frontend/src/sdk-test/SdkTestPanel.vue
  2. 46
      frontend/src/views/ChatPanel.vue

31
frontend/src/sdk-test/SdkTestPanel.vue

@ -288,6 +288,7 @@ import '@tdesign-vue-next/chat/es/style/index.css'
import { MessagePlugin } from 'tdesign-vue-next' import { MessagePlugin } from 'tdesign-vue-next'
import { readSSEStream } from '@/utils/sse' import { readSSEStream } from '@/utils/sse'
import { renderMarkdown } from '@/utils/markdown' import { renderMarkdown } from '@/utils/markdown'
import { toast } from '@/utils/toast'
import { toChatData, type ChatMessage } from '@/utils/chatAdapter' import { toChatData, type ChatMessage } from '@/utils/chatAdapter'
// ==================== 常量 ==================== // ==================== 常量 ====================
@ -673,17 +674,21 @@ async function sendDemo(val?: string): Promise<void> {
demoMessages.value.push({ id: genId(), role: 'user', content: text, streaming: false, time: fmtTime() }) demoMessages.value.push({ id: genId(), role: 'user', content: text, streaming: false, time: fmtTime() })
const assistantMsg: ChatMessage = { id: genId(), role: 'assistant', content: '', streaming: true, time: fmtTime(), sources: [] } const assistantMsg: ChatMessage = { id: genId(), role: 'assistant', content: '', streaming: true, time: fmtTime(), sources: [] }
demoMessages.value.push(assistantMsg) demoMessages.value.push(assistantMsg)
await scrollDemoBottom()
const cid = demoChatId.value
const requestMode = demoMode.value
const opts = { enableRag: demoRag.value, rewriteStrategy: demoRag.value ? demoStrategy.value : undefined } const opts = { enableRag: demoRag.value, rewriteStrategy: demoRag.value ? demoStrategy.value : undefined }
const url = buildDemoUrl(demoMode.value === 'sync' ? 'chat' : 'chat/stream', text, demoChatId.value, opts)
// 提前构建 URL,固定本轮角色、账号、域名和检索配置。
const url = buildDemoUrl(requestMode === 'sync' ? 'chat' : 'chat/stream', text, cid, opts)
const sourcesUrl = opts.enableRag ? buildDemoUrl('chat/sources', text, cid, opts) : ''
const headers = sdkAuthHeaders() const headers = sdkAuthHeaders()
await scrollDemoBottom()
try { try {
if (abortController) abortController.abort() if (abortController) abortController.abort()
abortController = new AbortController() abortController = new AbortController()
if (demoMode.value === 'sync') {
if (requestMode === 'sync') {
const res = await fetch(url, { headers, signal: abortController.signal }) const res = await fetch(url, { headers, signal: abortController.signal })
if (!res.ok) throw new Error('HTTP ' + res.status) if (!res.ok) throw new Error('HTTP ' + res.status)
assistantMsg.content = await res.text() assistantMsg.content = await res.text()
@ -701,15 +706,19 @@ async function sendDemo(val?: string): Promise<void> {
) )
} }
// RAG 模式下拉取引用来源
if (demoRag.value) {
try {
const sres = await fetch(buildDemoUrl('chat/sources', text, demoChatId.value, opts), { headers })
// 引用来源后台补齐,正文完成后立即结束发送状态。
if (opts.enableRag) {
void fetch(sourcesUrl, { headers }).then(async sres => {
const json = await sres.json() const json = await sres.json()
if (json && json.success) assistantMsg.sources = json.data || []
} catch {
/* 来源获取失败不影响主回答 */
}
if (!sres.ok || !json?.success) {
throw new Error(json?.message || (sres.ok ? '引用来源获取失败' : 'HTTP ' + sres.status))
}
if (demoChatId.value !== cid || !demoMessages.value.some(msg => msg.id === assistantMsg.id)) return
assistantMsg.sources = json.data || []
demoMessages.value = [...demoMessages.value]
}).catch((e: any) => {
toast('引用来源获取失败: ' + (e.message || e), 'warning')
})
} }
} catch (e: any) { } catch (e: any) {
if (e.name === 'AbortError') { if (e.name === 'AbortError') {

46
frontend/src/views/ChatPanel.vue

@ -491,6 +491,19 @@ async function send(): Promise<void> {
isSending.value = true isSending.value = true
currentSuggestions.value = [] // 清空建议 currentSuggestions.value = [] // 清空建议
const cid = chatId.value || ('web_' + Date.now())
chatId.value = cid
const requestMode = mode.value
// 固定本轮参数,后台引用请求不读取后续切换的角色或 RAG 配置。
const imageUrls = attachments.filter(a => a.type === 'image').map(a => a.url)
const chatOptions: ChatOptions = {
roleId: currentRoleId(),
enableRag: isRagMode.value,
rewriteStrategy: isRagMode.value ? ragStrategy.value : undefined,
categoryIds: [...selectedCategoryIds.value],
imageUrls: imageUrls.length ? imageUrls : undefined,
}
// 追加用户消息(含附件) // 追加用户消息(含附件)
messages.value.push({ messages.value.push({
id: generateMsgId(), role: 'user', content: text, id: generateMsgId(), role: 'user', content: text,
@ -505,32 +518,19 @@ async function send(): Promise<void> {
messages.value.push(assistantMsg) messages.value.push(assistantMsg)
await scrollToBottom() await scrollToBottom()
const cid = chatId.value || ('web_' + Date.now())
chatId.value = cid
try { try {
// 取消上一个 SSE 请求并创建新的 AbortController // 取消上一个 SSE 请求并创建新的 AbortController
if (sseAbortController) { sseAbortController.abort() } if (sseAbortController) { sseAbortController.abort() }
sseAbortController = new AbortController() sseAbortController = new AbortController()
const signal = sseAbortController.signal const signal = sseAbortController.signal
// 统一对话请求参数(RAG 与普通对话共用同一接口,由 enableRag 参数区分)
const imageUrls = attachments.filter(a => a.type === 'image').map(a => a.url)
const chatOptions: ChatOptions = {
roleId: currentRoleId(),
enableRag: isRagMode.value,
rewriteStrategy: isRagMode.value ? ragStrategy.value : undefined,
categoryIds: selectedCategoryIds.value,
imageUrls: imageUrls.length ? imageUrls : undefined,
}
if (mode.value === 'sync') {
if (requestMode === 'sync') {
// 同步调用 // 同步调用
assistantMsg.content = await chatSync(text, cid, chatOptions) assistantMsg.content = await chatSync(text, cid, chatOptions)
} else { } else {
// SSE 流式 // SSE 流式
const url = chatSSEUrl(text, cid, chatOptions) const url = chatSSEUrl(text, cid, chatOptions)
if (isRagMode.value) {
if (chatOptions.enableRag) {
await readSSEStreamWithEvents(url, { await readSSEStreamWithEvents(url, {
onMessage: async (chunk: string) => { onMessage: async (chunk: string) => {
assistantMsg.content += chunk assistantMsg.content += chunk
@ -562,12 +562,16 @@ async function send(): Promise<void> {
} }
} }
// RAG 模式下,拉取引用来源
if (isRagMode.value) {
try {
const sj = await ragSources(text, cid, chatOptions)
if (sj && sj.success) assistantMsg.sources = sj.data || []
} catch (_) { /* 来源获取失败不影响主回答 */ }
// 引用来源后台补齐,正文完成后立即结束发送状态。
if (chatOptions.enableRag) {
void ragSources(text, cid, chatOptions).then(sj => {
if (!sj?.success) throw new Error(sj?.message || '引用来源获取失败')
if (chatId.value !== cid || !messages.value.some(msg => msg.id === assistantMsg.id)) return
assistantMsg.sources = sj.data || []
messages.value = [...messages.value]
}).catch((e: any) => {
toast('引用来源获取失败: ' + (e.message || e), 'warning')
})
} }
} catch (e: any) { } catch (e: any) {
// AbortError 不是真正的错误,不显示错误信息 // AbortError 不是真正的错误,不显示错误信息

Loading…
Cancel
Save