feat: release chat and mindspace UI updates
This commit is contained in:
+73
-53
@@ -52,6 +52,7 @@ import {
|
||||
import { buildAbsoluteAssetImageUrl } from '../utils/mindspaceCards';
|
||||
import {
|
||||
buildUserMessage,
|
||||
mergeConversationSnapshot,
|
||||
normalizeConversationMessages,
|
||||
getDisplayText,
|
||||
getToolConfirmation,
|
||||
@@ -60,7 +61,12 @@ import {
|
||||
isRelayServerErrorMessage,
|
||||
pushMessage,
|
||||
} from '../utils/message';
|
||||
import { prependUnique, shouldShowNewChatTitle, sortAndTrim, touchSession } from '../utils/sessions';
|
||||
import {
|
||||
mergeSessionLists,
|
||||
prependUnique,
|
||||
shouldShowNewChatTitle,
|
||||
touchSession,
|
||||
} from '../utils/sessions';
|
||||
|
||||
const INSUFFICIENT_BALANCE_NOTICE = '余额不足,请充值后继续使用';
|
||||
|
||||
@@ -338,7 +344,7 @@ export function useTKMindChat(
|
||||
setSessionsLoading(true);
|
||||
try {
|
||||
const items = await listSessions();
|
||||
setSessions(sortAndTrim(items));
|
||||
setSessions((prev) => mergeSessionLists(prev, items));
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError && err.status === 0) {
|
||||
setError('网络不可用,无法刷新历史列表');
|
||||
@@ -385,6 +391,7 @@ export function useTKMindChat(
|
||||
const syncSessionMessages = useCallback(async (sessionId: string) => {
|
||||
try {
|
||||
const detail = await loadSessionDetail(sessionId);
|
||||
setSessions((prev) => prependUnique(prev, detail.session));
|
||||
if (sessionRef.current?.id !== sessionId) return;
|
||||
messagesRef.current = detail.messages;
|
||||
setMessages(detail.messages);
|
||||
@@ -467,12 +474,14 @@ export function useTKMindChat(
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'UpdateConversation':
|
||||
messagesRef.current = normalizeConversationMessages(
|
||||
case 'UpdateConversation': {
|
||||
const snapshot = normalizeConversationMessages(
|
||||
event.conversation.filter((m) => m.metadata?.userVisible),
|
||||
);
|
||||
messagesRef.current = mergeConversationSnapshot(messagesRef.current, snapshot);
|
||||
setMessages(messagesRef.current);
|
||||
return;
|
||||
}
|
||||
case 'Error':
|
||||
setError(event.error);
|
||||
setChatState('error');
|
||||
@@ -519,11 +528,22 @@ export function useTKMindChat(
|
||||
sessionId,
|
||||
(event) => {
|
||||
const rid = activeRequestId.current;
|
||||
if (rid) processEvent(event, rid, sessionId);
|
||||
else if (event.type === 'ActiveRequests' && event.request_ids.length > 0) {
|
||||
activeRequestId.current = event.request_ids[0];
|
||||
setChatState('streaming');
|
||||
if (event.type === 'ActiveRequests') {
|
||||
if (!rid && event.request_ids.length > 0) {
|
||||
// SSE reconnected while agent was running — adopt the active request.
|
||||
activeRequestId.current = event.request_ids[0];
|
||||
setChatState('streaming');
|
||||
} else if (rid && !event.request_ids.includes(rid)) {
|
||||
// Our request is no longer active — agent finished while SSE was paused.
|
||||
// Sync the final state from the server.
|
||||
activeRequestId.current = null;
|
||||
setChatState('idle');
|
||||
setPendingTool(null);
|
||||
void syncSessionMessages(sessionId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (rid) processEvent(event, rid, sessionId);
|
||||
},
|
||||
(err) => {
|
||||
if (err instanceof ApiError && err.status === 401) return;
|
||||
@@ -531,6 +551,8 @@ export function useTKMindChat(
|
||||
notifyInsufficientBalance();
|
||||
return;
|
||||
}
|
||||
// SSE 断连时如果 agent 还在跑,不打断 chatState,等重连后事件恢复
|
||||
if (activeRequestId.current) return;
|
||||
setError(err.message);
|
||||
},
|
||||
{
|
||||
@@ -643,7 +665,6 @@ export function useTKMindChat(
|
||||
setError(null);
|
||||
|
||||
const previousSessionId = readStoredSessionId(userRef.current?.id);
|
||||
let sessionId: string | null = null;
|
||||
let staleSession = false;
|
||||
|
||||
if (previousSessionId) {
|
||||
@@ -667,20 +688,12 @@ export function useTKMindChat(
|
||||
}
|
||||
}
|
||||
|
||||
const freshSession = await startSession();
|
||||
sessionId = freshSession.id;
|
||||
writeStoredSessionId(userRef.current?.id, sessionId);
|
||||
if (staleSession) {
|
||||
setNotice('上次会话已失效,已为你新建对话');
|
||||
}
|
||||
|
||||
if (cancelled) return;
|
||||
await connectSessionRef.current(sessionId, {
|
||||
skipResume: true,
|
||||
seedSession: freshSession,
|
||||
});
|
||||
if (cancelled) return;
|
||||
void loadProjectMemory(sessionId, false);
|
||||
setChatState('idle');
|
||||
void refreshSessions();
|
||||
} catch (err) {
|
||||
if (!cancelled) {
|
||||
@@ -745,7 +758,7 @@ export function useTKMindChat(
|
||||
imageUrls?: string[],
|
||||
) => {
|
||||
const normalizedImageUrls = (imageUrls ?? []).filter((value) => typeof value === 'string' && value.trim());
|
||||
if (!session || (!text.trim() && normalizedImageUrls.length === 0)) return;
|
||||
if (!text.trim() && normalizedImageUrls.length === 0) return;
|
||||
if (chatState === 'streaming' || chatState === 'loading' || chatState === 'connecting') return;
|
||||
|
||||
const trimmed = text.trim();
|
||||
@@ -764,15 +777,41 @@ export function useTKMindChat(
|
||||
activeRequestId.current = requestId;
|
||||
messagesRef.current = [...messagesRef.current, userMessage];
|
||||
setMessages(messagesRef.current);
|
||||
setSessions((prev) => touchSession(prev, session.id, 1));
|
||||
setChatState('streaming');
|
||||
setError(null);
|
||||
setPendingTool(null);
|
||||
|
||||
let activeSessionId = session?.id ?? null;
|
||||
|
||||
if (!activeSessionId) {
|
||||
try {
|
||||
const created = await startSession();
|
||||
activeSessionId = created.id;
|
||||
writeStoredSessionId(userRef.current?.id, created.id);
|
||||
setSession(created);
|
||||
setSessions((prev) => prependUnique(prev, created));
|
||||
subscribeToSession(created.id);
|
||||
void ensureProvider(created.id);
|
||||
void loadProjectMemory(created.id, false);
|
||||
void refreshSessions();
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError && err.status === 402) {
|
||||
notifyInsufficientBalance();
|
||||
} else {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
setChatState('error');
|
||||
}
|
||||
activeRequestId.current = null;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
setSessions((prev) => touchSession(prev, activeSessionId!, 1));
|
||||
}
|
||||
|
||||
try {
|
||||
await sendReply(session.id, requestId, userMessage);
|
||||
await sendReply(activeSessionId, requestId, userMessage);
|
||||
} catch (err) {
|
||||
setSessions((prev) => touchSession(prev, session.id, -1));
|
||||
if (session) setSessions((prev) => touchSession(prev, activeSessionId!, -1));
|
||||
if (err instanceof ApiError && err.status === 402) {
|
||||
notifyInsufficientBalance();
|
||||
} else {
|
||||
@@ -782,7 +821,7 @@ export function useTKMindChat(
|
||||
activeRequestId.current = null;
|
||||
}
|
||||
},
|
||||
[notifyInsufficientBalance, session, chatState, grantedSkills],
|
||||
[notifyInsufficientBalance, session, chatState, grantedSkills, subscribeToSession, ensureProvider, loadProjectMemory, refreshSessions],
|
||||
);
|
||||
|
||||
const stop = useCallback(async () => {
|
||||
@@ -808,37 +847,18 @@ export function useTKMindChat(
|
||||
const newSession = useCallback(() => {
|
||||
if (chatState === 'streaming') return;
|
||||
|
||||
resetSessionView();
|
||||
connectTokenRef.current += 1;
|
||||
unsubscribeRef.current?.();
|
||||
unsubscribeRef.current = null;
|
||||
clearStoredSessionId(userRef.current?.id);
|
||||
activeRequestId.current = null;
|
||||
messagesRef.current = [];
|
||||
setMessages([]);
|
||||
setSession(null);
|
||||
const token = connectTokenRef.current;
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
const created = await startSession();
|
||||
if (token !== connectTokenRef.current) return;
|
||||
|
||||
writeStoredSessionId(userRef.current?.id, created.id);
|
||||
setSession(created);
|
||||
setSessions((prev) => prependUnique(prev, created));
|
||||
|
||||
void loadProjectMemory(created.id, false);
|
||||
void refreshSessions();
|
||||
|
||||
await ensureProvider(created.id);
|
||||
if (token !== connectTokenRef.current) return;
|
||||
|
||||
await connectSession(created.id, { showLoading: false });
|
||||
} catch (err) {
|
||||
if (token !== connectTokenRef.current) return;
|
||||
if (err instanceof ApiError && err.status === 402) {
|
||||
notifyInsufficientBalance();
|
||||
return;
|
||||
}
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
setChatState('error');
|
||||
}
|
||||
})();
|
||||
}, [chatState, connectSession, ensureProvider, loadProjectMemory, notifyInsufficientBalance, refreshSessions, resetSessionView]);
|
||||
setError(null);
|
||||
setPendingTool(null);
|
||||
setChatState('idle');
|
||||
}, [chatState]);
|
||||
|
||||
const refreshProjectMemory = useCallback(async () => {
|
||||
if (!session || chatState === 'streaming') return;
|
||||
|
||||
Reference in New Issue
Block a user