From c866205eacd64ab294f2ca0d0291d3d646abe37b Mon Sep 17 00:00:00 2001 From: Lingxuan Zuo Date: Tue, 7 Apr 2026 01:37:06 +0800 Subject: [PATCH] Add execution graph to chat history (#776) --- electron/api/routes/sessions.ts | 39 ++++ src/components/layout/MainLayout.tsx | 4 +- src/components/layout/Sidebar.tsx | 4 +- src/i18n/locales/en/chat.json | 31 +++ src/i18n/locales/ja/chat.json | 31 +++ src/i18n/locales/zh/chat.json | 31 +++ src/pages/Chat/ChatMessage.tsx | 4 +- src/pages/Chat/ExecutionGraphCard.tsx | 187 +++++++++++++++++ src/pages/Chat/index.tsx | 263 +++++++++++++++++++---- src/pages/Chat/task-visualization.ts | 278 +++++++++++++++++++++++++ tests/e2e/chat-task-visualizer.spec.ts | 221 ++++++++++++++++++++ tests/e2e/fixtures/electron.ts | 60 ++++++ tests/unit/task-visualization.test.ts | 156 ++++++++++++++ 13 files changed, 1261 insertions(+), 48 deletions(-) create mode 100644 src/pages/Chat/ExecutionGraphCard.tsx create mode 100644 src/pages/Chat/task-visualization.ts create mode 100644 tests/e2e/chat-task-visualizer.spec.ts create mode 100644 tests/unit/task-visualization.test.ts diff --git a/electron/api/routes/sessions.ts b/electron/api/routes/sessions.ts index ed2dfa7fc..0ef478719 100644 --- a/electron/api/routes/sessions.ts +++ b/electron/api/routes/sessions.ts @@ -4,12 +4,51 @@ import { getOpenClawConfigDir } from '../../utils/paths'; import type { HostApiContext } from '../context'; import { parseJsonBody, sendJson } from '../route-utils'; +const SAFE_SESSION_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9_-]*$/; + export async function handleSessionRoutes( req: IncomingMessage, res: ServerResponse, url: URL, _ctx: HostApiContext, ): Promise { + if (url.pathname === '/api/sessions/transcript' && req.method === 'GET') { + try { + const agentId = url.searchParams.get('agentId')?.trim() || ''; + const sessionId = url.searchParams.get('sessionId')?.trim() || ''; + if (!agentId || !sessionId) { + sendJson(res, 400, { success: false, error: 'agentId and sessionId are required' }); + return true; + } + if (!SAFE_SESSION_SEGMENT.test(agentId) || !SAFE_SESSION_SEGMENT.test(sessionId)) { + sendJson(res, 400, { success: false, error: 'Invalid transcript identifier' }); + return true; + } + + const transcriptPath = join(getOpenClawConfigDir(), 'agents', agentId, 'sessions', `${sessionId}.jsonl`); + const fsP = await import('node:fs/promises'); + const raw = await fsP.readFile(transcriptPath, 'utf8'); + const lines = raw.split(/\r?\n/).filter(Boolean); + const messages = lines.flatMap((line) => { + try { + const entry = JSON.parse(line) as { type?: string; message?: unknown }; + return entry.type === 'message' && entry.message ? [entry.message] : []; + } catch { + return []; + } + }); + + sendJson(res, 200, { success: true, messages }); + } catch (error) { + if (typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT') { + sendJson(res, 404, { success: false, error: 'Transcript not found' }); + } else { + sendJson(res, 500, { success: false, error: 'Failed to load transcript' }); + } + } + return true; + } + if (url.pathname === '/api/sessions/delete' && req.method === 'POST') { try { const body = await parseJsonBody<{ sessionKey: string }>(req); diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx index ba2ac05c7..4f64bceb9 100644 --- a/src/components/layout/MainLayout.tsx +++ b/src/components/layout/MainLayout.tsx @@ -13,9 +13,9 @@ export function MainLayout() { {/* Below the title bar: sidebar + content */} -
+
-
+
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index c8f39009a..70cc81535 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -219,7 +219,7 @@ export function Sidebar() {