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:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user