mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-17 22:01:57 +08:00
feat(sidebar): improve running session state tracking
Add a running-session view to the sidebar, including header controls, running counts, empty states, and row-level processing indicators so active provider work is visible outside the current chat. Hydrate running state after refresh through a status-only /api/providers/sessions/running endpoint backed by chatRunRegistry.listRunningRuns, then sync and poll the frontend processingSessions map from AppContent without attaching to chat streams or replaying messages. Preserve fresh local processing entries during sync so newly sent messages are not cleared before the backend registry catches up, and clear completed sessions once the status endpoint no longer reports them. Thread active session state through sidebar project/session components, show rotating loaders for processing sessions, and keep the running search mode expanded and filterable. Fix optimistic local user-message dedupe so repeated prompts are only collapsed when a matching server echo appears from the same send window, preventing sent messages from disappearing until assistant completion. Add registry test coverage for listing currently running app sessions. Tests: npx eslint on changed files; npx tsc --noEmit -p tsconfig.json; npx tsc --noEmit -p server/tsconfig.json; npx tsx --tsconfig server/tsconfig.json --test server/modules/websocket/tests/chat-run-registry.test.ts.
This commit is contained in:
@@ -128,12 +128,44 @@ function createEmptySlot(): SessionSlot {
|
||||
* assistant echo (same trimmed text), so finalized stream rows do not stack
|
||||
* on top of the persisted copy before realtime is cleared.
|
||||
*/
|
||||
const LOCAL_USER_DEDUPE_WINDOW_MS = 5 * 60 * 1000;
|
||||
const LOCAL_USER_DEDUPE_CLOCK_SKEW_MS = 10_000;
|
||||
|
||||
function userTextFingerprint(m: NormalizedMessage): string | null {
|
||||
if (m.kind !== 'text' || m.role !== 'user') return null;
|
||||
const t = (m.content || '').trim();
|
||||
return t.length > 0 ? t : null;
|
||||
}
|
||||
|
||||
function readMessageTime(m: NormalizedMessage): number | null {
|
||||
const time = Date.parse(m.timestamp);
|
||||
return Number.isFinite(time) ? time : null;
|
||||
}
|
||||
|
||||
function hasServerEchoForLocalUser(
|
||||
localMessage: NormalizedMessage,
|
||||
serverMessages: NormalizedMessage[],
|
||||
): boolean {
|
||||
const localText = userTextFingerprint(localMessage);
|
||||
const localTime = readMessageTime(localMessage);
|
||||
if (!localText || localTime === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return serverMessages.some((serverMessage) => {
|
||||
if (userTextFingerprint(serverMessage) !== localText) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const serverTime = readMessageTime(serverMessage);
|
||||
return (
|
||||
serverTime !== null
|
||||
&& serverTime >= localTime - LOCAL_USER_DEDUPE_CLOCK_SKEW_MS
|
||||
&& serverTime - localTime <= LOCAL_USER_DEDUPE_WINDOW_MS
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* After `finalizeStreaming`, the client holds a synthetic assistant `text` row
|
||||
* while the sessions API soon returns the same reply with a different id.
|
||||
@@ -175,16 +207,13 @@ function computeMerged(server: NormalizedMessage[], realtime: NormalizedMessage[
|
||||
if (realtime.length === 0) return server;
|
||||
if (server.length === 0) return dedupeAdjacentAssistantEchoes(realtime);
|
||||
const serverIds = new Set(server.map(m => m.id));
|
||||
const serverUserTexts = new Set(
|
||||
server.map(userTextFingerprint).filter((t): t is string => t !== null),
|
||||
);
|
||||
const extra = realtime.filter((m) => {
|
||||
if (serverIds.has(m.id)) return false;
|
||||
// Optimistic user rows use `local_*` ids; once the same text exists on the
|
||||
// server-backed copy, drop the realtime echo to avoid duplicate bubbles.
|
||||
// server-backed copy from the same send window, drop the realtime echo to
|
||||
// avoid duplicate bubbles without hiding repeated prompts from history.
|
||||
if (m.id.startsWith('local_')) {
|
||||
const fp = userTextFingerprint(m);
|
||||
if (fp && serverUserTexts.has(fp)) return false;
|
||||
if (hasServerEchoForLocalUser(m, server)) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user