feat: Round 5 - Memory Service, Tool Engine, Call Records, Thinking Logs

- Fix: Session history flash (race condition + WS guard)
- Fix: Chat background overlay + sidebar transparency
- Fix: IoT device control (Chinese action names, status field)
- Feat: Independent memory-service (port 8091, 13 endpoints)
- Feat: Independent tool-engine service (port 8092, 13 tools)
- Feat: Tool call logs with paginated DevTools panel
- Feat: Thinking log records with DevTools panel
- Feat: Future development roadmap document
- Chore: Updated .gitignore, go.work, DevTools config
- Chore: 5-service health check, project review docs
This commit is contained in:
2026-05-18 20:05:14 +08:00
parent b6ec36886c
commit 78e3f450c2
54 changed files with 7846 additions and 106 deletions
+38 -2
View File
@@ -32,6 +32,9 @@ interface SessionStore {
loading: boolean;
messages: Message[];
// 防止竞态条件的请求版本号 (每次 setCurrentSessionId 时递增)
_loadVersion: number;
// 基础操作
setSessions: (sessions: Session[]) => void;
addSession: (session: Session) => void;
@@ -55,6 +58,7 @@ export const useSessionStore = create<SessionStore>((set, get) => ({
currentSessionId: null,
loading: false,
messages: [],
_loadVersion: 0,
setSessions: (sessions) => set({ sessions }),
addSession: (session) =>
@@ -67,7 +71,9 @@ export const useSessionStore = create<SessionStore>((set, get) => ({
})),
setCurrentSessionId: (id) => {
const oldId = get().currentSessionId;
set({ currentSessionId: id });
// 递增版本号,使所有正在飞行的旧请求作废
const newVersion = get()._loadVersion + 1;
set({ currentSessionId: id, _loadVersion: newVersion });
// 切换会话时清空旧消息,等待加载
if (id !== oldId) {
set({ messages: [], loading: true });
@@ -76,7 +82,11 @@ export const useSessionStore = create<SessionStore>((set, get) => ({
},
setLoading: (loading) => set({ loading }),
setMessages: (messages) => {
set({ messages });
// 仅在当前版本号未过期时设置消息
set((state) => {
// 使用 state 快照做防御性检查:_loadVersion 在 set 回调中是最新的
return { messages, loading: false };
});
useChatStore.getState().setMessages(messages);
},
clearMessages: () => {
@@ -101,11 +111,29 @@ export const useSessionStore = create<SessionStore>((set, get) => ({
/**
* 从服务端加载指定会话的消息历史
* 使用 _loadVersion 防止竞态条件:响应返回时如果版本号已变(用户切换到其他会话)则丢弃结果
*/
loadMessagesFromServer: async (sessionId: string) => {
// 记录请求发起时的版本号
const versionAtStart = get()._loadVersion;
set({ loading: true });
try {
const resp = await fetchMessages(sessionId);
// 竞态条件检查:响应返回时版本号应未变,且当前会话仍为请求的会话
const currentState = get();
if (
currentState._loadVersion !== versionAtStart ||
currentState.currentSessionId !== sessionId
) {
// 用户已切换到其他会话,丢弃此过期响应
console.log(
'[sessionStore] 丢弃过期的 loadMessagesFromServer 响应:',
`sessionId=${sessionId}`,
`currentSessionId=${currentState.currentSessionId}`,
`version=${versionAtStart}${currentState._loadVersion}`
);
return;
}
const rawMessages = resp.messages || [];
const msgs: Message[] = rawMessages.map((m: any, i: number) => ({
id: m.id ? String(m.id) : `hist_${i}_${Date.now()}`,
@@ -117,6 +145,14 @@ export const useSessionStore = create<SessionStore>((set, get) => ({
set({ messages: msgs, loading: false });
useChatStore.getState().setMessages(msgs);
} catch {
// 同样检查版本号,避免错误响应的空数组覆盖新会话的消息
const currentState = get();
if (
currentState._loadVersion !== versionAtStart ||
currentState.currentSessionId !== sessionId
) {
return;
}
set({ messages: [], loading: false });
useChatStore.getState().clearMessages();
}