fix: async document processing in background threads, improve chat UX
- Fix knowledge_base_service calling async process_document synchronously in background threads — use asyncio.new_event_loop().run_until_complete() instead of bare call that returned unawaited coroutine - Same fix for _update_document and reindex_document - Replace ModeSelector with auto-detect: RAG mode when knowledge bases selected - Convert regenerateMessage to streaming (was synchronous API call) - Show "知识库检索模式" indicator above input when KB selected - Remove loadSessions side-effect from streaming onComplete - Fix chat page loading state to avoid flash - Improve forum page spacing and sizing Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+116
-39
@@ -54,14 +54,12 @@ export const useChatStore = create<ChatStore>((set, get) => ({
|
||||
|
||||
// 加载会话列表
|
||||
loadSessions: async () => {
|
||||
set({ isLoading: true, error: null });
|
||||
|
||||
try {
|
||||
const sessions = await chatAPI.getSessions();
|
||||
set({ sessions, isLoading: false });
|
||||
set({ sessions });
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "加载会话失败";
|
||||
set({ error: errorMessage, isLoading: false });
|
||||
set({ error: errorMessage });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -320,7 +318,7 @@ export const useChatStore = create<ChatStore>((set, get) => ({
|
||||
});
|
||||
}
|
||||
},
|
||||
(sessionId: number, messageId?: number, userMessageId?: number) => {
|
||||
(_sessionId: number, messageId?: number, userMessageId?: number) => {
|
||||
if (messageId) {
|
||||
set((state) => ({
|
||||
messages: state.messages.map(msg => {
|
||||
@@ -332,15 +330,9 @@ export const useChatStore = create<ChatStore>((set, get) => ({
|
||||
abortController: null,
|
||||
}));
|
||||
} else {
|
||||
const { currentSession } = get();
|
||||
if (currentSession) {
|
||||
get().loadMessages(currentSession.id);
|
||||
}
|
||||
// 前端已通过流式获得完整内容,不需要从服务端重新加载
|
||||
set({ isStreaming: false, abortController: null });
|
||||
}
|
||||
|
||||
// 刷新会话列表以确保新会话显示在顶部
|
||||
get().loadSessions();
|
||||
},
|
||||
(error: string) => {
|
||||
// 流式错误
|
||||
@@ -414,36 +406,121 @@ export const useChatStore = create<ChatStore>((set, get) => ({
|
||||
}
|
||||
},
|
||||
|
||||
// 重新生成消息(支持 assistant 消息 ID)
|
||||
// 重新生成消息(流式)
|
||||
regenerateMessage: async (messageId: number, model?: string) => {
|
||||
const { currentSession, messages } = get();
|
||||
if (!currentSession) return;
|
||||
|
||||
const messageIndex = messages.findIndex(msg => msg.id === messageId);
|
||||
if (messageIndex === -1) return;
|
||||
|
||||
// 找到该 assistant 消息之前的 user 消息内容
|
||||
const userContent = messages
|
||||
.slice(0, messageIndex)
|
||||
.reverse()
|
||||
.find(msg => msg.role === "user")?.content;
|
||||
if (!userContent) return;
|
||||
|
||||
// 删除该 assistant 消息及之后的所有消息,替换为流式占位符
|
||||
const trimmed = messages.slice(0, messageIndex);
|
||||
const assistantPlaceholder: ChatMessage = {
|
||||
id: Date.now() + 1,
|
||||
role: "assistant",
|
||||
content: "",
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const abortController = new AbortController();
|
||||
set({
|
||||
messages: [...trimmed, assistantPlaceholder],
|
||||
isStreaming: true,
|
||||
error: null,
|
||||
abortController,
|
||||
});
|
||||
|
||||
// 判断模式:检查之前的消息中是否有知识库相关内容
|
||||
const mode = "normal";
|
||||
|
||||
let thinkingSteps: any[] = [];
|
||||
|
||||
try {
|
||||
set({ isLoading: true, error: null });
|
||||
const response = await chatAPI.regenerateMessage(messageId, model);
|
||||
|
||||
// 找到该消息,删除它及之后的所有消息
|
||||
const messageIndex = get().messages.findIndex(msg => msg.id === messageId);
|
||||
if (messageIndex !== -1) {
|
||||
set((state) => ({
|
||||
messages: state.messages.slice(0, messageIndex),
|
||||
}));
|
||||
}
|
||||
|
||||
// 添加新的助手回复
|
||||
const newMessage: ChatMessage = {
|
||||
id: response.new_message_id,
|
||||
role: "assistant",
|
||||
content: response.content,
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
|
||||
set((state) => ({
|
||||
messages: [...state.messages, newMessage],
|
||||
isLoading: false,
|
||||
}));
|
||||
await chatAPI.streamMessage(
|
||||
userContent,
|
||||
currentSession.id,
|
||||
mode,
|
||||
undefined, // knowledgeBaseIds
|
||||
abortController.signal,
|
||||
(chunk: string) => {
|
||||
// 处理 chunk
|
||||
try {
|
||||
const data = JSON.parse(chunk);
|
||||
if (data.type === 'thinking') {
|
||||
thinkingSteps.push({
|
||||
stage: data.stage,
|
||||
message: data.message,
|
||||
doc_count: data.doc_count,
|
||||
time: data.time
|
||||
});
|
||||
requestAnimationFrame(() => {
|
||||
set((state) => ({
|
||||
messages: state.messages.map(msg =>
|
||||
msg.id === assistantPlaceholder.id
|
||||
? { ...msg, thinking: [...thinkingSteps] }
|
||||
: msg
|
||||
),
|
||||
}));
|
||||
});
|
||||
} else if (data.type === 'chunk') {
|
||||
requestAnimationFrame(() => {
|
||||
set((state) => ({
|
||||
messages: state.messages.map(msg =>
|
||||
msg.id === assistantPlaceholder.id
|
||||
? { ...msg, content: msg.content + data.content }
|
||||
: msg
|
||||
),
|
||||
}));
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// 纯文本 chunk
|
||||
requestAnimationFrame(() => {
|
||||
set((state) => ({
|
||||
messages: state.messages.map(msg =>
|
||||
msg.id === assistantPlaceholder.id
|
||||
? { ...msg, content: msg.content + chunk }
|
||||
: msg
|
||||
),
|
||||
}));
|
||||
});
|
||||
}
|
||||
},
|
||||
(_sessionId: number, messageId?: number) => {
|
||||
// onComplete: 只替换 assistant 占位符 ID,不重新加载
|
||||
if (messageId) {
|
||||
set((state) => ({
|
||||
messages: state.messages.map(msg =>
|
||||
msg.id === assistantPlaceholder.id
|
||||
? { ...msg, id: messageId }
|
||||
: msg
|
||||
),
|
||||
isStreaming: false,
|
||||
abortController: null,
|
||||
}));
|
||||
} else {
|
||||
set({ isStreaming: false, abortController: null });
|
||||
}
|
||||
},
|
||||
(error: string) => {
|
||||
set({ error, isStreaming: false, abortController: null });
|
||||
},
|
||||
undefined, // onStatus
|
||||
undefined, // onThinking
|
||||
undefined, // onThinkingContent
|
||||
model
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("重新生成消息失败:", error);
|
||||
const errorMessage = error instanceof Error ? error.message : "重新生成消息失败";
|
||||
set({ error: errorMessage, isLoading: false });
|
||||
const errorMessage = error instanceof Error ? error.message : "重新生成失败";
|
||||
set({ error: errorMessage, isStreaming: false, abortController: null });
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user