diff --git a/frontend/app/app/api/claw/context-budget/route.ts b/frontend/app/app/api/claw/context-budget/route.ts index d1f8c05..74bc77b 100644 --- a/frontend/app/app/api/claw/context-budget/route.ts +++ b/frontend/app/app/api/claw/context-budget/route.ts @@ -10,7 +10,7 @@ export async function GET(req: Request) { const incoming = new URL(req.url); const url = new URL(`${CLAW_API_URL}/api/context-budget`); url.searchParams.set("account_id", account.id); - const sessionId = incoming.searchParams.get("session_id"); + const sessionId = incoming.searchParams.get("session_id")?.trim(); if (sessionId) url.searchParams.set("session_id", sessionId); try { diff --git a/frontend/app/app/api/claw/sessions/[sessionId]/route.ts b/frontend/app/app/api/claw/sessions/[sessionId]/route.ts index ea3b2c3..d608ff2 100644 --- a/frontend/app/app/api/claw/sessions/[sessionId]/route.ts +++ b/frontend/app/app/api/claw/sessions/[sessionId]/route.ts @@ -10,7 +10,8 @@ export async function GET( if (!account) return Response.json({ error: "请先登录账号" }, { status: 401 }); - const { sessionId } = await params; + const { sessionId: rawSessionId } = await params; + const sessionId = rawSessionId.trim(); const url = new URL(`${CLAW_API_URL}/api/sessions/${sessionId}`); url.searchParams.set("account_id", account.id); const response = await fetch(url, { cache: "no-store" }); @@ -32,7 +33,8 @@ export async function DELETE( if (!account) return Response.json({ error: "请先登录账号" }, { status: 401 }); - const { sessionId } = await params; + const { sessionId: rawSessionId } = await params; + const sessionId = rawSessionId.trim(); const url = new URL(`${CLAW_API_URL}/api/sessions/${sessionId}`); url.searchParams.set("account_id", account.id); const response = await fetch(url, { method: "DELETE", cache: "no-store" }); @@ -54,7 +56,8 @@ export async function PATCH( if (!account) return Response.json({ error: "请先登录账号" }, { status: 401 }); - const { sessionId } = await params; + const { sessionId: rawSessionId } = await params; + const sessionId = rawSessionId.trim(); const url = new URL(`${CLAW_API_URL}/api/sessions/${sessionId}`); url.searchParams.set("account_id", account.id); const response = await fetch(url, { diff --git a/frontend/app/components/assistant-ui/activity-panel.tsx b/frontend/app/components/assistant-ui/activity-panel.tsx index a88a575..8dff7df 100644 --- a/frontend/app/components/assistant-ui/activity-panel.tsx +++ b/frontend/app/components/assistant-ui/activity-panel.tsx @@ -209,13 +209,15 @@ function SessionFilesPanel({ useEffect(() => { let cancelled = false; async function loadFiles() { - const activeSessionId = - sessionId ?? window.localStorage.getItem("claw.activeSessionId"); + const activeSessionId = normalizeSessionId( + sessionId ?? window.localStorage.getItem("claw.activeSessionId"), + ); if (!activeSessionId) { setPayload({ input: [], output: [] }); setStatus("当前还没有可用会话。"); return; } + window.localStorage.setItem("claw.activeSessionId", activeSessionId); setStatus("正在读取聊天中的文件..."); try { const url = new URL("/api/claw/files", window.location.origin); @@ -280,6 +282,12 @@ function SessionFilesPanel({ ); } +function normalizeSessionId(value: unknown) { + if (typeof value !== "string") return null; + const trimmed = value.trim(); + return trimmed || null; +} + function FileSection({ title, files, diff --git a/frontend/app/components/assistant-ui/thread.tsx b/frontend/app/components/assistant-ui/thread.tsx index a4862c5..a24fc6e 100644 --- a/frontend/app/components/assistant-ui/thread.tsx +++ b/frontend/app/components/assistant-ui/thread.tsx @@ -519,8 +519,9 @@ function useComposerContextStatus() { }); useEffect(() => { - if (typeof latestSessionId === "string" && latestSessionId) { - window.localStorage.setItem("claw.activeSessionId", latestSessionId); + const cleanLatestSessionId = normalizeSessionId(latestSessionId); + if (cleanLatestSessionId) { + window.localStorage.setItem("claw.activeSessionId", cleanLatestSessionId); window.setTimeout(() => { window.dispatchEvent(new Event("claw-sessions-changed")); }, 0); @@ -548,11 +549,15 @@ function useComposerContextStatus() { const statePayload = stateResponse.ok ? ((await stateResponse.json()) as ClawState) : {}; - const sessionId = - (typeof latestSessionId === "string" && latestSessionId) || - window.localStorage.getItem("claw.activeSessionId") || - statePayload.active_session_id || - null; + const sessionId = normalizeSessionId( + normalizeSessionId(latestSessionId) || + normalizeSessionId( + window.localStorage.getItem("claw.activeSessionId"), + ) || + normalizeSessionId(statePayload.active_session_id), + ); + if (sessionId) + window.localStorage.setItem("claw.activeSessionId", sessionId); let sessionPayload: ClawStoredSession | null = null; let contextBudget: ContextBudget | undefined; if (sessionId) { @@ -680,6 +685,12 @@ function readMetadataValue(metadata: unknown, key: string): unknown { return undefined; } +function normalizeSessionId(value: unknown) { + if (typeof value !== "string") return null; + const trimmed = value.trim(); + return trimmed || null; +} + function formatCompactTokenCount(value: number) { if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`; if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`; diff --git a/frontend/app/components/assistant-ui/threadlist-sidebar.tsx b/frontend/app/components/assistant-ui/threadlist-sidebar.tsx index da50e90..6bf3065 100644 --- a/frontend/app/components/assistant-ui/threadlist-sidebar.tsx +++ b/frontend/app/components/assistant-ui/threadlist-sidebar.tsx @@ -357,15 +357,20 @@ function useSidebarSessions(open: boolean) { repository: ExportedMessageRepository, ) => void, ) { - window.localStorage.setItem("claw.activeSessionId", sessionId); + const cleanSessionId = normalizeSessionId(sessionId); + if (!cleanSessionId) return; + window.localStorage.setItem("claw.activeSessionId", cleanSessionId); setLoadingSessionId(sessionId); try { - const response = await fetch(`/api/claw/sessions/${sessionId}`, { + const response = await fetch(`/api/claw/sessions/${cleanSessionId}`, { cache: "no-store", }); if (!response.ok) return; const payload = (await response.json()) as ReplaySessionPayload; - replaySession(sessionId, toReplayRepository(payload, sessionId)); + replaySession( + cleanSessionId, + toReplayRepository(payload, cleanSessionId), + ); } finally { setLoadingSessionId(null); } @@ -430,13 +435,15 @@ function AccountMenu({ : []; setState(nextState); setSessions(nextSessions); - const activeSessionId = + const activeSessionId = normalizeSessionId( window.localStorage.getItem("claw.activeSessionId") ?? - nextState?.active_session_id; + nextState?.active_session_id, + ); if (!activeSessionId) { setActiveSession(null); return; } + window.localStorage.setItem("claw.activeSessionId", activeSessionId); const response = await fetch(`/api/claw/sessions/${activeSessionId}`, { cache: "no-store", }); @@ -610,6 +617,12 @@ function AccountMenu({ ); } +function normalizeSessionId(value: unknown) { + if (typeof value !== "string") return null; + const trimmed = value.trim(); + return trimmed || null; +} + function aggregateUsageByModel(sessions: ClawSessionSummary[]) { const byModel = new Map< string,