From 3c72e9fa500e504c1a8c061608e54a00aae10066 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Sun, 10 May 2026 21:53:34 +0200 Subject: [PATCH 01/20] feat: Add cell rendering functions for duration, method, status, and type with corresponding tests --- .../GetCellDurationDom/GetCellDurationDom.ts | 15 ++++++ .../GetCellMethodDom/GetCellMethodDom.ts | 15 ++++++ .../GetCellStatusDom/GetCellStatusDom.ts | 15 ++++++ .../parts/GetCellTypeDom/GetCellTypeDom.ts | 15 ++++++ .../parts/GetRowCellNodes/GetRowCellNodes.ts | 47 ++++--------------- .../test/GetCellDurationDom.test.ts | 25 ++++++++++ .../test/GetCellMethodDom.test.ts | 24 ++++++++++ .../test/GetCellStatusDom.test.ts | 25 ++++++++++ .../test/GetCellTypeDom.test.ts | 24 ++++++++++ 9 files changed, 167 insertions(+), 38 deletions(-) create mode 100644 packages/chat-debug-view/src/parts/GetCellDurationDom/GetCellDurationDom.ts create mode 100644 packages/chat-debug-view/src/parts/GetCellMethodDom/GetCellMethodDom.ts create mode 100644 packages/chat-debug-view/src/parts/GetCellStatusDom/GetCellStatusDom.ts create mode 100644 packages/chat-debug-view/src/parts/GetCellTypeDom/GetCellTypeDom.ts create mode 100644 packages/chat-debug-view/test/GetCellDurationDom.test.ts create mode 100644 packages/chat-debug-view/test/GetCellMethodDom.test.ts create mode 100644 packages/chat-debug-view/test/GetCellStatusDom.test.ts create mode 100644 packages/chat-debug-view/test/GetCellTypeDom.test.ts diff --git a/packages/chat-debug-view/src/parts/GetCellDurationDom/GetCellDurationDom.ts b/packages/chat-debug-view/src/parts/GetCellDurationDom/GetCellDurationDom.ts new file mode 100644 index 00000000..4cd56a65 --- /dev/null +++ b/packages/chat-debug-view/src/parts/GetCellDurationDom/GetCellDurationDom.ts @@ -0,0 +1,15 @@ +import { mergeClassNames, type VirtualDomNode, VirtualDomElements, text } from '@lvce-editor/virtual-dom-worker' +import type { ChatViewEvent } from '../ChatViewEvent/ChatViewEvent.ts' +import { ChatDebugViewCellDuration, TableCell } from '../ClassNames/ClassNames.ts' +import { getEventTableDurationText } from '../GetEventTableDurationText/GetEventTableDurationText.ts' + +export const getCellDurationDom = (event: ChatViewEvent): readonly VirtualDomNode[] => { + return [ + { + childCount: 1, + className: mergeClassNames(TableCell, ChatDebugViewCellDuration), + type: VirtualDomElements.Td, + }, + text(getEventTableDurationText(event)), + ] +} diff --git a/packages/chat-debug-view/src/parts/GetCellMethodDom/GetCellMethodDom.ts b/packages/chat-debug-view/src/parts/GetCellMethodDom/GetCellMethodDom.ts new file mode 100644 index 00000000..18e30318 --- /dev/null +++ b/packages/chat-debug-view/src/parts/GetCellMethodDom/GetCellMethodDom.ts @@ -0,0 +1,15 @@ +import { type VirtualDomNode, VirtualDomElements, text } from '@lvce-editor/virtual-dom-worker' +import type { ChatViewEvent } from '../ChatViewEvent/ChatViewEvent.ts' +import { TableCell } from '../ClassNames/ClassNames.ts' +import { getEventTableMethodLabel } from '../GetEventTableMethodLabel/GetEventTableMethodLabel.ts' + +export const getCellMethodDom = (event: ChatViewEvent): readonly VirtualDomNode[] => { + return [ + { + childCount: 1, + className: TableCell, + type: VirtualDomElements.Td, + }, + text(getEventTableMethodLabel(event)), + ] +} diff --git a/packages/chat-debug-view/src/parts/GetCellStatusDom/GetCellStatusDom.ts b/packages/chat-debug-view/src/parts/GetCellStatusDom/GetCellStatusDom.ts new file mode 100644 index 00000000..1fc84532 --- /dev/null +++ b/packages/chat-debug-view/src/parts/GetCellStatusDom/GetCellStatusDom.ts @@ -0,0 +1,15 @@ +import { mergeClassNames, type VirtualDomNode, VirtualDomElements, text } from '@lvce-editor/virtual-dom-worker' +import type { ChatViewEvent } from '../ChatViewEvent/ChatViewEvent.ts' +import { ChatDebugViewCellStatusError, TableCell } from '../ClassNames/ClassNames.ts' +import { getStatusText } from '../GetStatusText/GetStatusText.ts' + +export const getCellStatusDom = (event: ChatViewEvent, isErrorStatus: boolean): readonly VirtualDomNode[] => { + return [ + { + childCount: 1, + className: mergeClassNames(TableCell, isErrorStatus ? ChatDebugViewCellStatusError : ''), + type: VirtualDomElements.Td, + }, + text(getStatusText(event)), + ] +} diff --git a/packages/chat-debug-view/src/parts/GetCellTypeDom/GetCellTypeDom.ts b/packages/chat-debug-view/src/parts/GetCellTypeDom/GetCellTypeDom.ts new file mode 100644 index 00000000..824123bc --- /dev/null +++ b/packages/chat-debug-view/src/parts/GetCellTypeDom/GetCellTypeDom.ts @@ -0,0 +1,15 @@ +import { type VirtualDomNode, VirtualDomElements, text } from '@lvce-editor/virtual-dom-worker' +import type { ChatViewEvent } from '../ChatViewEvent/ChatViewEvent.ts' +import { TableCell } from '../ClassNames/ClassNames.ts' +import { getEventTableTypeLabel } from '../GetEventTableTypeLabel/GetEventTableTypeLabel.ts' + +export const getCellTypeDom = (event: ChatViewEvent): readonly VirtualDomNode[] => { + return [ + { + childCount: 1, + className: TableCell, + type: VirtualDomElements.Td, + }, + text(getEventTableTypeLabel(event)), + ] +} diff --git a/packages/chat-debug-view/src/parts/GetRowCellNodes/GetRowCellNodes.ts b/packages/chat-debug-view/src/parts/GetRowCellNodes/GetRowCellNodes.ts index cce06f0d..0a0f297c 100644 --- a/packages/chat-debug-view/src/parts/GetRowCellNodes/GetRowCellNodes.ts +++ b/packages/chat-debug-view/src/parts/GetRowCellNodes/GetRowCellNodes.ts @@ -1,50 +1,21 @@ -import { mergeClassNames, type VirtualDomNode, VirtualDomElements, text } from '@lvce-editor/virtual-dom-worker' +import type { VirtualDomNode } from '@lvce-editor/virtual-dom-worker' import type { ChatViewEvent } from '../ChatViewEvent/ChatViewEvent.ts' -import { ChatDebugViewCellDuration, ChatDebugViewCellStatusError, TableCell } from '../ClassNames/ClassNames.ts' -import { getEventTableDurationText } from '../GetEventTableDurationText/GetEventTableDurationText.ts' -import { getEventTableMethodLabel } from '../GetEventTableMethodLabel/GetEventTableMethodLabel.ts' -import { getEventTableTypeLabel } from '../GetEventTableTypeLabel/GetEventTableTypeLabel.ts' -import { getStatusText } from '../GetStatusText/GetStatusText.ts' +import * as GetCellDurationDom from '../GetCellDurationDom/GetCellDurationDom.ts' +import * as GetCellMethodDom from '../GetCellMethodDom/GetCellMethodDom.ts' +import * as GetCellStatusDom from '../GetCellStatusDom/GetCellStatusDom.ts' +import * as GetCellTypeDom from '../GetCellTypeDom/GetCellTypeDom.ts' import * as TableColumn from '../TableColumn/TableColumn.ts' const getTableCellDom = (column: TableColumn.TableColumnName, event: ChatViewEvent, isErrorStatus: boolean): readonly VirtualDomNode[] => { switch (column) { case TableColumn.Duration: - return [ - { - childCount: 1, - className: mergeClassNames(TableCell, ChatDebugViewCellDuration), - type: VirtualDomElements.Td, - }, - text(getEventTableDurationText(event)), - ] + return GetCellDurationDom.getCellDurationDom(event) case TableColumn.Method: - return [ - { - childCount: 1, - className: TableCell, - type: VirtualDomElements.Td, - }, - text(getEventTableMethodLabel(event)), - ] + return GetCellMethodDom.getCellMethodDom(event) case TableColumn.Status: - return [ - { - childCount: 1, - className: mergeClassNames(TableCell, isErrorStatus ? ChatDebugViewCellStatusError : ''), - type: VirtualDomElements.Td, - }, - text(getStatusText(event)), - ] + return GetCellStatusDom.getCellStatusDom(event, isErrorStatus) case TableColumn.Type: - return [ - { - childCount: 1, - className: TableCell, - type: VirtualDomElements.Td, - }, - text(getEventTableTypeLabel(event)), - ] + return GetCellTypeDom.getCellTypeDom(event) default: return [] } diff --git a/packages/chat-debug-view/test/GetCellDurationDom.test.ts b/packages/chat-debug-view/test/GetCellDurationDom.test.ts new file mode 100644 index 00000000..892c5ddc --- /dev/null +++ b/packages/chat-debug-view/test/GetCellDurationDom.test.ts @@ -0,0 +1,25 @@ +import { expect, test } from '@jest/globals' +import { VirtualDomElements, text } from '@lvce-editor/virtual-dom-worker' +import { getCellDurationDom } from '../src/parts/GetCellDurationDom/GetCellDurationDom.ts' + +test('getCellDurationDom should render the duration cell', () => { + const event = { + ended: '2026-03-08T00:00:01.250Z', + eventId: 1, + sessionId: 'session-1', + started: '2026-03-08T00:00:01.000Z', + timestamp: '2026-03-08T00:00:01.000Z', + type: 'tool-execution', + } + + const result = getCellDurationDom(event) + + expect(result).toEqual([ + { + childCount: 1, + className: 'TableCell ChatDebugViewCellDuration', + type: VirtualDomElements.Td, + }, + text('250 ms'), + ]) +}) diff --git a/packages/chat-debug-view/test/GetCellMethodDom.test.ts b/packages/chat-debug-view/test/GetCellMethodDom.test.ts new file mode 100644 index 00000000..17075c77 --- /dev/null +++ b/packages/chat-debug-view/test/GetCellMethodDom.test.ts @@ -0,0 +1,24 @@ +import { expect, test } from '@jest/globals' +import { VirtualDomElements, text } from '@lvce-editor/virtual-dom-worker' +import { getCellMethodDom } from '../src/parts/GetCellMethodDom/GetCellMethodDom.ts' + +test('getCellMethodDom should render the method cell', () => { + const event = { + eventId: 1, + name: 'read_file', + sessionId: 'session-1', + timestamp: '2026-04-02T07:26:35.172Z', + type: 'tool-execution', + } + + const result = getCellMethodDom(event) + + expect(result).toEqual([ + { + childCount: 1, + className: 'TableCell', + type: VirtualDomElements.Td, + }, + text('GET'), + ]) +}) diff --git a/packages/chat-debug-view/test/GetCellStatusDom.test.ts b/packages/chat-debug-view/test/GetCellStatusDom.test.ts new file mode 100644 index 00000000..a4e51240 --- /dev/null +++ b/packages/chat-debug-view/test/GetCellStatusDom.test.ts @@ -0,0 +1,25 @@ +import { expect, test } from '@jest/globals' +import { VirtualDomElements, text } from '@lvce-editor/virtual-dom-worker' +import { getCellStatusDom } from '../src/parts/GetCellStatusDom/GetCellStatusDom.ts' + +test('getCellStatusDom should render the error status cell', () => { + const event = { + error: 'Invalid argument: uri must be an absolute URI.', + eventId: 1, + name: 'list_files', + sessionId: 'session-1', + timestamp: '2026-04-02T07:26:35.172Z', + type: 'tool-execution', + } + + const result = getCellStatusDom(event, true) + + expect(result).toEqual([ + { + childCount: 1, + className: 'TableCell ChatDebugViewCellStatusError', + type: VirtualDomElements.Td, + }, + text('400'), + ]) +}) diff --git a/packages/chat-debug-view/test/GetCellTypeDom.test.ts b/packages/chat-debug-view/test/GetCellTypeDom.test.ts new file mode 100644 index 00000000..0b2c802c --- /dev/null +++ b/packages/chat-debug-view/test/GetCellTypeDom.test.ts @@ -0,0 +1,24 @@ +import { expect, test } from '@jest/globals' +import { VirtualDomElements, text } from '@lvce-editor/virtual-dom-worker' +import { getCellTypeDom } from '../src/parts/GetCellTypeDom/GetCellTypeDom.ts' + +test('getCellTypeDom should render the type cell', () => { + const event = { + eventId: 1, + name: 'list_files', + sessionId: 'session-1', + timestamp: '2026-04-02T07:26:35.172Z', + type: 'tool-execution', + } + + const result = getCellTypeDom(event) + + expect(result).toEqual([ + { + childCount: 1, + className: 'TableCell', + type: VirtualDomElements.Td, + }, + text('list_files'), + ]) +}) From beb81441f62a24dbe0df4433e27bb845dac522b9 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Sun, 10 May 2026 21:57:31 +0200 Subject: [PATCH 02/20] better display --- .../src/parts/ChatViewEvent/ChatViewEvent.ts | 2 ++ .../src/parts/GetDurationText/GetDurationText.ts | 3 +++ .../GetEventTableMethodLabel/GetEventTableMethodLabel.ts | 4 ++++ .../src/parts/ToPrettyEvents/ToPrettyEvents.ts | 6 ++++++ 4 files changed, 15 insertions(+) diff --git a/packages/chat-debug-view/src/parts/ChatViewEvent/ChatViewEvent.ts b/packages/chat-debug-view/src/parts/ChatViewEvent/ChatViewEvent.ts index 9f2b2003..f2f18bd8 100644 --- a/packages/chat-debug-view/src/parts/ChatViewEvent/ChatViewEvent.ts +++ b/packages/chat-debug-view/src/parts/ChatViewEvent/ChatViewEvent.ts @@ -4,10 +4,12 @@ export interface ChatViewEvent { readonly ended?: number | string readonly endTime?: number | string readonly eventId: number + readonly method?: string readonly [key: string]: unknown readonly sessionId?: string readonly started?: number | string readonly startTime?: number | string + readonly time?: string readonly timestamp?: number | string readonly type: string } diff --git a/packages/chat-debug-view/src/parts/GetDurationText/GetDurationText.ts b/packages/chat-debug-view/src/parts/GetDurationText/GetDurationText.ts index e99ef45c..eabb3e6c 100644 --- a/packages/chat-debug-view/src/parts/GetDurationText/GetDurationText.ts +++ b/packages/chat-debug-view/src/parts/GetDurationText/GetDurationText.ts @@ -2,6 +2,9 @@ import type { ChatViewEvent } from '../ChatViewEvent/ChatViewEvent.ts' import { toTimeNumber } from '../ToTimeNumber/ToTimeNumber.ts' export const getDurationText = (event: ChatViewEvent): string => { + if (event.time) { + return event.time + } const explicitDuration = event.durationMs ?? event.duration if (typeof explicitDuration === 'number' && Number.isFinite(explicitDuration)) { return `${explicitDuration}ms` diff --git a/packages/chat-debug-view/src/parts/GetEventTableMethodLabel/GetEventTableMethodLabel.ts b/packages/chat-debug-view/src/parts/GetEventTableMethodLabel/GetEventTableMethodLabel.ts index 765a09f2..583e7a2d 100644 --- a/packages/chat-debug-view/src/parts/GetEventTableMethodLabel/GetEventTableMethodLabel.ts +++ b/packages/chat-debug-view/src/parts/GetEventTableMethodLabel/GetEventTableMethodLabel.ts @@ -6,6 +6,10 @@ const postMethods = new Set(['create_directory', 'create_file', 'mkdir', 'write_ const deleteMethods = new Set(['delete_directory', 'delete_file', 'delete_folder', 'remove_directory', 'remove_file', 'remove_folder']) export const getEventTableMethodLabel = (event: ChatViewEvent): string => { + if (event.method) { + return event.method + } + console.log({ event }) const toolName = getToolName(event) if (!toolName) { return '' diff --git a/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts b/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts index a9d32018..f5d631b9 100644 --- a/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts +++ b/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts @@ -12,8 +12,14 @@ export const toPrettyEvents = (rawEvents: ListChatViewEventsResult): readonly Ch if (item.type === 'ai-request' && 'requestId' in item && typeof item.requestId === 'string') { const response = map[item.requestId] if (response) { + const parsedStart = new Date(item.timestamp || '') + const parsedEnd = new Date(response.timestamp || '') + const durationMs = parsedEnd.getTime() - parsedStart.getTime() + const formattedDuration = `${durationMs}ms` pretty.push({ eventId: item.eventId, + method: 'POST', + time: formattedDuration, type: 'ai-request-response', }) } else { From 6094c5684df2f5d06ff14dce9d54435de995bd6d Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Sun, 10 May 2026 21:57:50 +0200 Subject: [PATCH 03/20] memory --- packages/build/src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/build/src/config.ts b/packages/build/src/config.ts index 73877b4a..0192978c 100644 --- a/packages/build/src/config.ts +++ b/packages/build/src/config.ts @@ -1,7 +1,7 @@ import { join } from 'node:path' import { root } from './root.ts' -export const threshold = 600_000 +export const threshold = 620_000 export const instantiations = 200_000 From 028a1210317adc770dc5dd72cb6b398ce1d8625c Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Sun, 10 May 2026 22:01:53 +0200 Subject: [PATCH 04/20] fix --- .../GetEventTableMethodLabel.ts | 1 - .../parts/GetRowCellNodes/GetRowCellNodes.ts | 22 +---------- .../parts/GetTableCellDom/GetTableCellDom.ts | 22 +++++++++++ .../test/GetTableCellDom.test.ts | 38 +++++++++++++++++++ 4 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 packages/chat-debug-view/src/parts/GetTableCellDom/GetTableCellDom.ts create mode 100644 packages/chat-debug-view/test/GetTableCellDom.test.ts diff --git a/packages/chat-debug-view/src/parts/GetEventTableMethodLabel/GetEventTableMethodLabel.ts b/packages/chat-debug-view/src/parts/GetEventTableMethodLabel/GetEventTableMethodLabel.ts index 583e7a2d..e016369d 100644 --- a/packages/chat-debug-view/src/parts/GetEventTableMethodLabel/GetEventTableMethodLabel.ts +++ b/packages/chat-debug-view/src/parts/GetEventTableMethodLabel/GetEventTableMethodLabel.ts @@ -9,7 +9,6 @@ export const getEventTableMethodLabel = (event: ChatViewEvent): string => { if (event.method) { return event.method } - console.log({ event }) const toolName = getToolName(event) if (!toolName) { return '' diff --git a/packages/chat-debug-view/src/parts/GetRowCellNodes/GetRowCellNodes.ts b/packages/chat-debug-view/src/parts/GetRowCellNodes/GetRowCellNodes.ts index 0a0f297c..c2bfe141 100644 --- a/packages/chat-debug-view/src/parts/GetRowCellNodes/GetRowCellNodes.ts +++ b/packages/chat-debug-view/src/parts/GetRowCellNodes/GetRowCellNodes.ts @@ -1,29 +1,11 @@ import type { VirtualDomNode } from '@lvce-editor/virtual-dom-worker' import type { ChatViewEvent } from '../ChatViewEvent/ChatViewEvent.ts' -import * as GetCellDurationDom from '../GetCellDurationDom/GetCellDurationDom.ts' -import * as GetCellMethodDom from '../GetCellMethodDom/GetCellMethodDom.ts' -import * as GetCellStatusDom from '../GetCellStatusDom/GetCellStatusDom.ts' -import * as GetCellTypeDom from '../GetCellTypeDom/GetCellTypeDom.ts' +import * as GetTableCellDom from '../GetTableCellDom/GetTableCellDom.ts' import * as TableColumn from '../TableColumn/TableColumn.ts' -const getTableCellDom = (column: TableColumn.TableColumnName, event: ChatViewEvent, isErrorStatus: boolean): readonly VirtualDomNode[] => { - switch (column) { - case TableColumn.Duration: - return GetCellDurationDom.getCellDurationDom(event) - case TableColumn.Method: - return GetCellMethodDom.getCellMethodDom(event) - case TableColumn.Status: - return GetCellStatusDom.getCellStatusDom(event, isErrorStatus) - case TableColumn.Type: - return GetCellTypeDom.getCellTypeDom(event) - default: - return [] - } -} - export const getRowCellNodes = (event: ChatViewEvent, isErrorStatus: boolean, visibleTableColumns: readonly string[]): readonly VirtualDomNode[] => { const orderedVisibleTableColumns = TableColumn.getOrderedVisibleTableColumns(visibleTableColumns) return orderedVisibleTableColumns.flatMap((column) => { - return getTableCellDom(column, event, isErrorStatus) + return GetTableCellDom.getTableCellDom(column, event, isErrorStatus) }) } diff --git a/packages/chat-debug-view/src/parts/GetTableCellDom/GetTableCellDom.ts b/packages/chat-debug-view/src/parts/GetTableCellDom/GetTableCellDom.ts new file mode 100644 index 00000000..6499f4e2 --- /dev/null +++ b/packages/chat-debug-view/src/parts/GetTableCellDom/GetTableCellDom.ts @@ -0,0 +1,22 @@ +import type { VirtualDomNode } from '@lvce-editor/virtual-dom-worker' +import type { ChatViewEvent } from '../ChatViewEvent/ChatViewEvent.ts' +import * as GetCellDurationDom from '../GetCellDurationDom/GetCellDurationDom.ts' +import * as GetCellMethodDom from '../GetCellMethodDom/GetCellMethodDom.ts' +import * as GetCellStatusDom from '../GetCellStatusDom/GetCellStatusDom.ts' +import * as GetCellTypeDom from '../GetCellTypeDom/GetCellTypeDom.ts' +import * as TableColumn from '../TableColumn/TableColumn.ts' + +export const getTableCellDom = (column: TableColumn.TableColumnName, event: ChatViewEvent, isErrorStatus: boolean): readonly VirtualDomNode[] => { + switch (column) { + case TableColumn.Duration: + return GetCellDurationDom.getCellDurationDom(event) + case TableColumn.Method: + return GetCellMethodDom.getCellMethodDom(event) + case TableColumn.Status: + return GetCellStatusDom.getCellStatusDom(event, isErrorStatus) + case TableColumn.Type: + return GetCellTypeDom.getCellTypeDom(event) + default: + return [] + } +} diff --git a/packages/chat-debug-view/test/GetTableCellDom.test.ts b/packages/chat-debug-view/test/GetTableCellDom.test.ts new file mode 100644 index 00000000..4e21b2e2 --- /dev/null +++ b/packages/chat-debug-view/test/GetTableCellDom.test.ts @@ -0,0 +1,38 @@ +import { expect, test } from '@jest/globals' +import { VirtualDomElements, text } from '@lvce-editor/virtual-dom-worker' +import { getTableCellDom } from '../src/parts/GetTableCellDom/GetTableCellDom.ts' +import * as TableColumn from '../src/parts/TableColumn/TableColumn.ts' + +test('getTableCellDom should render a method cell', () => { + const event = { + eventId: 1, + name: 'read_file', + sessionId: 'session-1', + timestamp: '2026-04-02T07:26:35.172Z', + type: 'tool-execution', + } + + const result = getTableCellDom(TableColumn.Method, event, false) + + expect(result).toEqual([ + { + childCount: 1, + className: 'TableCell', + type: VirtualDomElements.Td, + }, + text('GET'), + ]) +}) + +test('getTableCellDom should return an empty array for unknown columns', () => { + const event = { + eventId: 1, + sessionId: 'session-1', + timestamp: '2026-04-02T07:26:35.172Z', + type: 'tool-execution', + } + + const result = getTableCellDom('unknown-column' as TableColumn.TableColumnName, event, false) + + expect(result).toEqual([]) +}) From 626c2f29c9125da883aa09dec69e5819b481a778 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Sun, 10 May 2026 20:11:01 +0000 Subject: [PATCH 05/20] fix --- .../src/parts/ToPrettyEvents/ToPrettyEvents.ts | 10 ++++++---- packages/chat-debug-view/test/ToPrettyEvents.test.ts | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts b/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts index f5d631b9..0ea11261 100644 --- a/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts +++ b/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts @@ -15,13 +15,15 @@ export const toPrettyEvents = (rawEvents: ListChatViewEventsResult): readonly Ch const parsedStart = new Date(item.timestamp || '') const parsedEnd = new Date(response.timestamp || '') const durationMs = parsedEnd.getTime() - parsedStart.getTime() - const formattedDuration = `${durationMs}ms` - pretty.push({ + const mergedEvent: ChatViewEvent = { eventId: item.eventId, method: 'POST', - time: formattedDuration, type: 'ai-request-response', - }) + } + if (Number.isFinite(durationMs)) { + mergedEvent.time = `${durationMs}ms` + } + pretty.push(mergedEvent) } else { pretty.push(item) } diff --git a/packages/chat-debug-view/test/ToPrettyEvents.test.ts b/packages/chat-debug-view/test/ToPrettyEvents.test.ts index b6f0553b..14dfbaea 100644 --- a/packages/chat-debug-view/test/ToPrettyEvents.test.ts +++ b/packages/chat-debug-view/test/ToPrettyEvents.test.ts @@ -21,6 +21,7 @@ test('toPrettyEvents should merge matching ai request and ai response events', ( expect(result).toEqual([ { eventId: 1, + method: 'POST', type: 'ai-request-response', }, ]) From 39dda4e4dd5cb482ffb0b1f44335e69347372e72 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Sun, 10 May 2026 20:13:06 +0000 Subject: [PATCH 06/20] fix --- .../parts/ToPrettyEvents/ToPrettyEvents.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts b/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts index 0ea11261..24825e57 100644 --- a/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts +++ b/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts @@ -15,14 +15,18 @@ export const toPrettyEvents = (rawEvents: ListChatViewEventsResult): readonly Ch const parsedStart = new Date(item.timestamp || '') const parsedEnd = new Date(response.timestamp || '') const durationMs = parsedEnd.getTime() - parsedStart.getTime() - const mergedEvent: ChatViewEvent = { - eventId: item.eventId, - method: 'POST', - type: 'ai-request-response', - } - if (Number.isFinite(durationMs)) { - mergedEvent.time = `${durationMs}ms` - } + const mergedEvent: ChatViewEvent = Number.isFinite(durationMs) + ? { + eventId: item.eventId, + method: 'POST', + time: `${durationMs}ms`, + type: 'ai-request-response', + } + : { + eventId: item.eventId, + method: 'POST', + type: 'ai-request-response', + } pretty.push(mergedEvent) } else { pretty.push(item) From b4e6c9af0a2ddfd6e2e839db1d2c3c2c958058c8 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Sun, 10 May 2026 20:16:03 +0000 Subject: [PATCH 07/20] fix --- packages/chat-debug-view/test/Refresh.test.ts | 2 +- packages/chat-debug-view/test/SetSessionId.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/chat-debug-view/test/Refresh.test.ts b/packages/chat-debug-view/test/Refresh.test.ts index 8f2bea1b..efc2261e 100644 --- a/packages/chat-debug-view/test/Refresh.test.ts +++ b/packages/chat-debug-view/test/Refresh.test.ts @@ -62,7 +62,7 @@ test('refresh should return session-not-found state when latest events are empty }) test('refresh should update events with latest data from chat storage worker', async () => { - const events = [{ eventId: 1, time: 1, type: 'request' }] + const events = [{ eventId: 1, time: '1ms', type: 'request' }] const listChatViewEventsSpy = jest.spyOn(refreshDependencies, 'listChatViewEvents').mockResolvedValue({ events, type: 'success', diff --git a/packages/chat-debug-view/test/SetSessionId.test.ts b/packages/chat-debug-view/test/SetSessionId.test.ts index 4e897e40..31ceb772 100644 --- a/packages/chat-debug-view/test/SetSessionId.test.ts +++ b/packages/chat-debug-view/test/SetSessionId.test.ts @@ -9,7 +9,7 @@ afterEach(() => { }) test('setSessionId should load events for the given session id and clear selection state', async () => { - const events = [{ eventId: 1, time: 1, type: 'request' }] + const events = [{ eventId: 1, time: '1ms', type: 'request' }] const listChatViewEventsSpy = jest.spyOn(setSessionIdDependencies, 'listChatViewEvents').mockResolvedValue({ events, type: 'success', From 7468ec8d739b62924344d0dbb32198e0bcdd34f5 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Sun, 10 May 2026 22:08:15 +0200 Subject: [PATCH 08/20] feat: Implement createDevtoolsRows function and associated tests for row state management (#252) --- .../CreateDevtoolsRows/CreateDevtoolsRows.ts | 16 +++++ .../src/parts/DevtoolsRow/DevtoolsRow.ts | 9 +++ .../parts/GetDevtoolsDom/GetDevtoolsDom.ts | 4 +- .../parts/GetDevtoolsRows/GetDevtoolsRows.ts | 21 ++---- .../test/CreateDevtoolsRows.test.ts | 68 +++++++++++++++++++ .../test/GetDevtoolsRows.test.ts | 31 ++++++--- 6 files changed, 126 insertions(+), 23 deletions(-) create mode 100644 packages/chat-debug-view/src/parts/CreateDevtoolsRows/CreateDevtoolsRows.ts create mode 100644 packages/chat-debug-view/src/parts/DevtoolsRow/DevtoolsRow.ts create mode 100644 packages/chat-debug-view/test/CreateDevtoolsRows.test.ts diff --git a/packages/chat-debug-view/src/parts/CreateDevtoolsRows/CreateDevtoolsRows.ts b/packages/chat-debug-view/src/parts/CreateDevtoolsRows/CreateDevtoolsRows.ts new file mode 100644 index 00000000..33bb84e6 --- /dev/null +++ b/packages/chat-debug-view/src/parts/CreateDevtoolsRows/CreateDevtoolsRows.ts @@ -0,0 +1,16 @@ +import type { ChatViewEvent } from '../ChatViewEvent/ChatViewEvent.ts' +import type { DevtoolsRow } from '../DevtoolsRow/DevtoolsRow.ts' +import { hasErrorStatus } from '../HasErrorStatus/HasErrorStatus.ts' + +export const createDevtoolsRows = (events: readonly ChatViewEvent[], selectedEventIndex: number | null, startIndex = 0): readonly DevtoolsRow[] => { + return events.map((event, index) => { + const actualIndex = startIndex + index + return { + event, + index: actualIndex, + isErrorStatus: hasErrorStatus(event), + isEven: actualIndex % 2 === 1, + isSelected: selectedEventIndex === actualIndex, + } + }) +} diff --git a/packages/chat-debug-view/src/parts/DevtoolsRow/DevtoolsRow.ts b/packages/chat-debug-view/src/parts/DevtoolsRow/DevtoolsRow.ts new file mode 100644 index 00000000..08ac2798 --- /dev/null +++ b/packages/chat-debug-view/src/parts/DevtoolsRow/DevtoolsRow.ts @@ -0,0 +1,9 @@ +import type { ChatViewEvent } from '../ChatViewEvent/ChatViewEvent.ts' + +export interface DevtoolsRow { + readonly event: ChatViewEvent + readonly index: number + readonly isErrorStatus: boolean + readonly isEven: boolean + readonly isSelected: boolean +} diff --git a/packages/chat-debug-view/src/parts/GetDevtoolsDom/GetDevtoolsDom.ts b/packages/chat-debug-view/src/parts/GetDevtoolsDom/GetDevtoolsDom.ts index f952fe21..e97b22d5 100644 --- a/packages/chat-debug-view/src/parts/GetDevtoolsDom/GetDevtoolsDom.ts +++ b/packages/chat-debug-view/src/parts/GetDevtoolsDom/GetDevtoolsDom.ts @@ -5,6 +5,7 @@ import type { TimelineInfo } from '../GetTimelineInfo/GetTimelineInfo.ts' import * as ChatDebugStrings from '../ChatDebugStrings/ChatDebugStrings.ts' import { ChatDebugView, ChatDebugViewDevtools } from '../ClassNames/ClassNames.ts' import { createDetailTabs } from '../CreateDetailTabs/CreateDetailTabs.ts' +import { createDevtoolsRows } from '../CreateDevtoolsRows/CreateDevtoolsRows.ts' import { getDetailsDom } from '../GetDetailsDom/GetDetailsDom.ts' import { getDevtoolsRows } from '../GetDevtoolsRows/GetDevtoolsRows.ts' import { getEffectiveTimelineRange } from '../GetEffectiveTimelineRange/GetEffectiveTimelineRange.ts' @@ -49,7 +50,8 @@ export const getDevtoolsDom = ( maxLineY = events.length, ): readonly VirtualDomNode[] => { const visibleEvents = events.slice(minLineY, maxLineY) - const rowNodes = getDevtoolsRows(visibleEvents, selectedEventIndex, visibleTableColumns, minLineY) + const rows = createDevtoolsRows(visibleEvents, selectedEventIndex, minLineY) + const rowNodes = getDevtoolsRows(rows, visibleTableColumns) const effectiveRange = getEffectiveTimelineRange( timelineStartSeconds, timelineEndSeconds, diff --git a/packages/chat-debug-view/src/parts/GetDevtoolsRows/GetDevtoolsRows.ts b/packages/chat-debug-view/src/parts/GetDevtoolsRows/GetDevtoolsRows.ts index 08100961..cc07baed 100644 --- a/packages/chat-debug-view/src/parts/GetDevtoolsRows/GetDevtoolsRows.ts +++ b/packages/chat-debug-view/src/parts/GetDevtoolsRows/GetDevtoolsRows.ts @@ -1,28 +1,21 @@ import { mergeClassNames, type VirtualDomNode, VirtualDomElements } from '@lvce-editor/virtual-dom-worker' -import type { ChatViewEvent } from '../ChatViewEvent/ChatViewEvent.ts' +import type { DevtoolsRow } from '../DevtoolsRow/DevtoolsRow.ts' import { TableRow, TableRowEven, TableRowOdd, TableRowSelected } from '../ClassNames/ClassNames.ts' import { getRowCellNodes } from '../GetRowCellNodes/GetRowCellNodes.ts' -import { hasErrorStatus } from '../HasErrorStatus/HasErrorStatus.ts' import { defaultVisibleTableColumns } from '../TableColumn/TableColumn.ts' export const getDevtoolsRows = ( - events: readonly ChatViewEvent[], - selectedEventIndex: number | null, + rows: readonly DevtoolsRow[], visibleTableColumns: readonly string[] = defaultVisibleTableColumns, - startIndex = 0, ): readonly VirtualDomNode[] => { - return events.flatMap((event, i) => { - const actualIndex = startIndex + i - const isEvenRow = actualIndex % 2 === 1 - const rowClassName = isEvenRow ? TableRowEven : TableRowOdd - const isSelected = selectedEventIndex === actualIndex - const isErrorStatus = hasErrorStatus(event) - const rowCellNodes = getRowCellNodes(event, isErrorStatus, visibleTableColumns) + return rows.flatMap((row) => { + const rowClassName = row.isEven ? TableRowEven : TableRowOdd + const rowCellNodes = getRowCellNodes(row.event, row.isErrorStatus, visibleTableColumns) return [ { childCount: visibleTableColumns.length, - className: mergeClassNames(TableRow, rowClassName, isSelected ? TableRowSelected : ''), - 'data-index': `${actualIndex}`, + className: mergeClassNames(TableRow, rowClassName, row.isSelected ? TableRowSelected : ''), + 'data-index': `${row.index}`, type: VirtualDomElements.Tr, }, ...rowCellNodes, diff --git a/packages/chat-debug-view/test/CreateDevtoolsRows.test.ts b/packages/chat-debug-view/test/CreateDevtoolsRows.test.ts new file mode 100644 index 00000000..5a2c38d1 --- /dev/null +++ b/packages/chat-debug-view/test/CreateDevtoolsRows.test.ts @@ -0,0 +1,68 @@ +import { expect, test } from '@jest/globals' +import * as CreateDevtoolsRows from '../src/parts/CreateDevtoolsRows/CreateDevtoolsRows.ts' + +test('createDevtoolsRows should derive row state for a virtualized slice', () => { + const events = [ + { + eventId: 3, + sessionId: 'session-1', + timestamp: '2026-03-08T00:00:02.000Z', + type: 'request', + }, + { + eventId: 4, + sessionId: 'session-1', + timestamp: '2026-03-08T00:00:03.000Z', + type: 'response', + }, + ] + + const result = CreateDevtoolsRows.createDevtoolsRows(events, 3, 2) + + expect(result).toEqual([ + { + event: events[0], + index: 2, + isErrorStatus: false, + isEven: false, + isSelected: false, + }, + { + event: events[1], + index: 3, + isErrorStatus: false, + isEven: true, + isSelected: true, + }, + ]) +}) + +test('createDevtoolsRows should mark error rows from event status', () => { + const events = [ + { + arguments: { + uri: '/test/playground', + }, + eventId: 1, + name: 'list_files', + result: { + error: 'Invalid argument: uri must be an absolute URI.', + }, + sessionId: 'session-1', + timestamp: '2026-04-02T07:26:35.172Z', + type: 'tool-execution', + }, + ] + + const result = CreateDevtoolsRows.createDevtoolsRows(events, null) + + expect(result).toEqual([ + { + event: events[0], + index: 0, + isErrorStatus: true, + isEven: false, + isSelected: false, + }, + ]) +}) diff --git a/packages/chat-debug-view/test/GetDevtoolsRows.test.ts b/packages/chat-debug-view/test/GetDevtoolsRows.test.ts index 78f5c106..909b8cce 100644 --- a/packages/chat-debug-view/test/GetDevtoolsRows.test.ts +++ b/packages/chat-debug-view/test/GetDevtoolsRows.test.ts @@ -1,8 +1,20 @@ import { expect, test } from '@jest/globals' import { VirtualDomElements, text } from '@lvce-editor/virtual-dom-worker' +import type { DevtoolsRow } from '../src/parts/DevtoolsRow/DevtoolsRow.ts' import * as GetDevtoolsRows from '../src/parts/GetDevtoolsRows/GetDevtoolsRows.ts' import * as TableColumn from '../src/parts/TableColumn/TableColumn.ts' +const createRow = (event: Readonly>, index = 0, overrides: Readonly> = {}): DevtoolsRow => { + return { + event: event as DevtoolsRow['event'], + index, + isErrorStatus: false, + isEven: index % 2 === 1, + isSelected: false, + ...overrides, + } +} + test('getDevtoolsRows should render tool execution labels with the tool name', () => { const events = [ { @@ -14,7 +26,7 @@ test('getDevtoolsRows should render tool execution labels with the tool name', ( }, ] - const result = GetDevtoolsRows.getDevtoolsRows(events, null) + const result = GetDevtoolsRows.getDevtoolsRows([createRow(events[0])]) expect(result).toEqual([ { @@ -64,7 +76,7 @@ test('getDevtoolsRows should render tool execution labels with tool name from ar }, ] - const result = GetDevtoolsRows.getDevtoolsRows(events, null) + const result = GetDevtoolsRows.getDevtoolsRows([createRow(events[0])]) expect(result).toEqual([ { @@ -116,7 +128,7 @@ test('getDevtoolsRows should render tool execution labels with tool name from to }, ] - const result = GetDevtoolsRows.getDevtoolsRows(events, null) + const result = GetDevtoolsRows.getDevtoolsRows([createRow(events[0], 0, { isErrorStatus: true })]) expect(result).toEqual([ { @@ -169,7 +181,7 @@ test('getDevtoolsRows should render 400 status when tool error is nested in resu }, ] - const result = GetDevtoolsRows.getDevtoolsRows(events, null) + const result = GetDevtoolsRows.getDevtoolsRows([createRow(events[0], 0, { isErrorStatus: true })]) expect(result).toEqual([ { @@ -221,7 +233,7 @@ test('getDevtoolsRows should add odd and even row classes to table rows', () => }, ] - const result = GetDevtoolsRows.getDevtoolsRows(events, null) + const result = GetDevtoolsRows.getDevtoolsRows([createRow(events[0]), createRow(events[1], 1)]) expect(result).toEqual([ { @@ -312,7 +324,7 @@ test('getDevtoolsRows should render merged ai request duration from timestamps', }, ] - const result = GetDevtoolsRows.getDevtoolsRows(events, null) + const result = GetDevtoolsRows.getDevtoolsRows([createRow(events[0])]) expect(result).toEqual([ { @@ -360,7 +372,7 @@ test('getDevtoolsRows should omit hidden columns', () => { }, ] - const result = GetDevtoolsRows.getDevtoolsRows(events, null, [TableColumn.Type, TableColumn.Status]) + const result = GetDevtoolsRows.getDevtoolsRows([createRow(events[0])], [TableColumn.Type, TableColumn.Status]) expect(result).toEqual([ { @@ -400,7 +412,10 @@ test('getDevtoolsRows should preserve row parity and selection for a virtualized }, ] - const result = GetDevtoolsRows.getDevtoolsRows(events, 3, TableColumn.defaultVisibleTableColumns, 2) + const result = GetDevtoolsRows.getDevtoolsRows( + [createRow(events[0], 2), createRow(events[1], 3, { isEven: true, isSelected: true })], + TableColumn.defaultVisibleTableColumns, + ) expect(result).toEqual([ { From 3fc111fdfb5d0fa5ca0ee936e94e6d47375850a3 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Sun, 10 May 2026 22:08:17 +0200 Subject: [PATCH 09/20] refactor: Simplify state destructuring in handlePreviewTextScrollBarPointerMove function (#256) --- .../HandlePreviewTextScrollBarPointerMove.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/chat-debug-view/src/parts/HandlePreviewTextScrollBarPointerMove/HandlePreviewTextScrollBarPointerMove.ts b/packages/chat-debug-view/src/parts/HandlePreviewTextScrollBarPointerMove/HandlePreviewTextScrollBarPointerMove.ts index 6fa90a06..f5f08a5a 100644 --- a/packages/chat-debug-view/src/parts/HandlePreviewTextScrollBarPointerMove/HandlePreviewTextScrollBarPointerMove.ts +++ b/packages/chat-debug-view/src/parts/HandlePreviewTextScrollBarPointerMove/HandlePreviewTextScrollBarPointerMove.ts @@ -3,16 +3,17 @@ import { getPreviewTextBodyY } from '../PreviewTextBodyY/PreviewTextBodyY.ts' import { getPreviewTextViewportHeight, getPreviewVirtualizationState, setPreviewTextDeltaY } from '../PreviewVirtualization/PreviewVirtualization.ts' export const handlePreviewTextScrollBarPointerMove = (state: ChatDebugViewState, eventY: number): ChatDebugViewState => { - if (!state.previewTextScrollBarPointerActive) { + const { previewTextDeltaY, previewTextScrollBarHandleOffset, previewTextScrollBarPointerActive, selectedEvent } = state + if (!previewTextScrollBarPointerActive) { return state } const viewportHeight = getPreviewTextViewportHeight(state) - const virtualization = getPreviewVirtualizationState(state.selectedEvent, viewportHeight, state.previewTextDeltaY) + const virtualization = getPreviewVirtualizationState(selectedEvent, viewportHeight, previewTextDeltaY) if (viewportHeight === 0 || virtualization.scrollBarHeight === 0) { return state } const relativeY = eventY - getPreviewTextBodyY(state) - const nextHandleTop = Math.max(0, Math.min(viewportHeight - virtualization.scrollBarHeight, relativeY - state.previewTextScrollBarHandleOffset)) + const nextHandleTop = Math.max(0, Math.min(viewportHeight - virtualization.scrollBarHeight, relativeY - previewTextScrollBarHandleOffset)) const percent = nextHandleTop / Math.max(1, viewportHeight - virtualization.scrollBarHeight) const nextState = setPreviewTextDeltaY(state, percent * virtualization.maxDeltaY) return { From 7d6c8ee95303faf2b1778c7093f4396b750a1ed4 Mon Sep 17 00:00:00 2001 From: "lvce-editor-helper-bot[bot]" <155071229+lvce-editor-helper-bot[bot]@users.noreply.github.com> Date: Sun, 10 May 2026 21:43:08 +0000 Subject: [PATCH 10/20] feature: update dependencies (#260) Co-authored-by: lvce-editor-helper-bot[bot] <155071229+lvce-editor-helper-bot[bot]@users.noreply.github.com> --- packages/server/package-lock.json | 32 +++++++++++++++---------------- packages/server/package.json | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/server/package-lock.json b/packages/server/package-lock.json index 585407b0..6a115be7 100644 --- a/packages/server/package-lock.json +++ b/packages/server/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@lvce-editor/server": "0.80.18" + "@lvce-editor/server": "0.80.19" } }, "node_modules/@babel/code-frame": { @@ -86,9 +86,9 @@ } }, "node_modules/@lvce-editor/extension-host-helper-process": { - "version": "0.80.18", - "resolved": "https://registry.npmjs.org/@lvce-editor/extension-host-helper-process/-/extension-host-helper-process-0.80.18.tgz", - "integrity": "sha512-6CYQrJUGlYdQgELOc/UE5rbDT88qHzZEQe5YxhCg+BKdii3g3ucmEwC0nrVKAN3obImW6OA8/Raez1sLWv8TRA==", + "version": "0.80.19", + "resolved": "https://registry.npmjs.org/@lvce-editor/extension-host-helper-process/-/extension-host-helper-process-0.80.19.tgz", + "integrity": "sha512-JTG83sELB8WfE2w4wGuyZ836UKuZwXBHGJs7ry+PBuelT6tJMYL/qa0ucvVATwpdlwTAiINQULUVLTqRSx6DWQ==", "license": "MIT", "dependencies": { "@lvce-editor/assert": "^1.5.1", @@ -506,13 +506,13 @@ "optional": true }, "node_modules/@lvce-editor/server": { - "version": "0.80.18", - "resolved": "https://registry.npmjs.org/@lvce-editor/server/-/server-0.80.18.tgz", - "integrity": "sha512-1x0FqtQ5IM/MZA3f5hFo7nO1OGzNWb3p589gZCTypYmLUcf/rBQhdtJc1b3eXiJJXLVam2JyhdQT5KoVSzP1FQ==", + "version": "0.80.19", + "resolved": "https://registry.npmjs.org/@lvce-editor/server/-/server-0.80.19.tgz", + "integrity": "sha512-EVyWdduuLmAp57/NLDCy6v3HzM9fpe9g/JpGNZ3kiyh5Zr5ljyF7QgY/T1lX9BcvLSgUkFeWm5zZ8cT/tuwW+Q==", "license": "MIT", "dependencies": { - "@lvce-editor/shared-process": "0.80.18", - "@lvce-editor/static-server": "0.80.18" + "@lvce-editor/shared-process": "0.80.19", + "@lvce-editor/static-server": "0.80.19" }, "bin": { "server": "bin/server.js" @@ -522,14 +522,14 @@ } }, "node_modules/@lvce-editor/shared-process": { - "version": "0.80.18", - "resolved": "https://registry.npmjs.org/@lvce-editor/shared-process/-/shared-process-0.80.18.tgz", - "integrity": "sha512-lB2SZDBCeVp4rgdZKidrPmXmGOLpN1DJf0mp2voq6UwvdAym66TrDcKW/ZGAuy9DjODELOXZEnv0mc6EXi4f8A==", + "version": "0.80.19", + "resolved": "https://registry.npmjs.org/@lvce-editor/shared-process/-/shared-process-0.80.19.tgz", + "integrity": "sha512-zeMj62tdm1uOowyVd8ik9XEOu/pECUijt8q14TKS5oTOkkYeGVI9ak68uhyuUef+SKoersAYty++6AyxZtNdDQ==", "license": "MIT", "dependencies": { "@lvce-editor/assert": "1.5.1", "@lvce-editor/auth-process": "1.6.0", - "@lvce-editor/extension-host-helper-process": "0.80.18", + "@lvce-editor/extension-host-helper-process": "0.80.19", "@lvce-editor/ipc": "16.0.0", "@lvce-editor/json-rpc": "8.0.0", "@lvce-editor/jsonc-parser": "1.5.0", @@ -559,9 +559,9 @@ } }, "node_modules/@lvce-editor/static-server": { - "version": "0.80.18", - "resolved": "https://registry.npmjs.org/@lvce-editor/static-server/-/static-server-0.80.18.tgz", - "integrity": "sha512-jNT9REfS7QWHZWzcBzkdMZgb7XFN7AlSAHRKfu9zvK47U+9sHWFKzcJ0iITWGQAMB3qRg9gzPO0ywCKa8Rg8ig==", + "version": "0.80.19", + "resolved": "https://registry.npmjs.org/@lvce-editor/static-server/-/static-server-0.80.19.tgz", + "integrity": "sha512-DtN+eCw7VCZ9yw+YKA3yAr4CgEyRkQW3iQl8h8mOYFuOL58kjnHyInkqWDg7JdUZIzMWnH5kzCTodsul2eXGiQ==", "license": "MIT", "engines": { "node": ">=24" diff --git a/packages/server/package.json b/packages/server/package.json index 13d7729b..47187ff6 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -11,6 +11,6 @@ "author": "", "license": "MIT", "dependencies": { - "@lvce-editor/server": "0.80.18" + "@lvce-editor/server": "0.80.19" } } From bcfe3f1a33d8d171ff2796e3e0d8dc007cc14596 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Tue, 12 May 2026 15:07:14 +0200 Subject: [PATCH 11/20] feat: Add SelectedEventDetails and SelectedEventDetailsOpenAiRequestResponse interfaces (#264) --- .../SelectedEventDetails.ts | 7 ++ ...lectedEventDetailsOpenAiRequestResponse.ts | 65 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 packages/chat-debug-view/src/parts/SelectedEventDetails/SelectedEventDetails.ts create mode 100644 packages/chat-debug-view/src/parts/SelectedEventDetailsOpenAiRequestResponse/SelectedEventDetailsOpenAiRequestResponse.ts diff --git a/packages/chat-debug-view/src/parts/SelectedEventDetails/SelectedEventDetails.ts b/packages/chat-debug-view/src/parts/SelectedEventDetails/SelectedEventDetails.ts new file mode 100644 index 00000000..63e51a83 --- /dev/null +++ b/packages/chat-debug-view/src/parts/SelectedEventDetails/SelectedEventDetails.ts @@ -0,0 +1,7 @@ +import type { SelectedEventDetailsOpenAiRequestResponse } from '../SelectedEventDetailsOpenAiRequestResponse/SelectedEventDetailsOpenAiRequestResponse.ts' + +export interface SelectedEventDetailsBase { + readonly eventId: number +} + +export type SelectedEventDetails = SelectedEventDetailsOpenAiRequestResponse diff --git a/packages/chat-debug-view/src/parts/SelectedEventDetailsOpenAiRequestResponse/SelectedEventDetailsOpenAiRequestResponse.ts b/packages/chat-debug-view/src/parts/SelectedEventDetailsOpenAiRequestResponse/SelectedEventDetailsOpenAiRequestResponse.ts new file mode 100644 index 00000000..fca8580e --- /dev/null +++ b/packages/chat-debug-view/src/parts/SelectedEventDetailsOpenAiRequestResponse/SelectedEventDetailsOpenAiRequestResponse.ts @@ -0,0 +1,65 @@ +export interface InputItem { + content: string + role: string +} + +export interface Body { + input: InputItem[] + model: string +} + +export interface Headers { + readonly [key: string]: string +} + +export interface ContentItem { + annotations: unknown[] + text: string + type: string +} + +export interface OutputItem { + content: ContentItem[] + id: string + role: string + status: string + type: string +} + +export interface Value { + created_at: number + id: string + model: string + object: string + output: OutputItem[] + output_text: string + parallel_tool_calls: boolean + status: string + tools: unknown[] +} + +export interface EndValue { + eventId: number + headers: Headers + requestId: string + sessionId: string + statusCode: number + timestamp: string + toolCalls: unknown[] + turnId: string + type: string + value: Value +} + +export interface SelectedEventDetailsOpenAiRequestResponse { + readonly body: Body + readonly endValue: EndValue + readonly eventId: number + readonly headers: Headers + readonly method: string + readonly requestId: string + readonly sessionId: string + readonly timestamp: string + readonly turnId: string + readonly type: string +} From 00c086c30b9660a3055cd5c8357c61f4718ac2d8 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Tue, 12 May 2026 15:07:42 +0200 Subject: [PATCH 12/20] feat: Add BasicChatEvent types for request and response handling (#263) --- .../parts/BasicChatEvent/BasicChatEvent.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 packages/chat-debug-view/src/parts/BasicChatEvent/BasicChatEvent.ts diff --git a/packages/chat-debug-view/src/parts/BasicChatEvent/BasicChatEvent.ts b/packages/chat-debug-view/src/parts/BasicChatEvent/BasicChatEvent.ts new file mode 100644 index 00000000..9fb2bfa3 --- /dev/null +++ b/packages/chat-debug-view/src/parts/BasicChatEvent/BasicChatEvent.ts @@ -0,0 +1,21 @@ +type BasicChatEventMethod = 'POST' | 'GET' | 'DELETE' + +export interface BaseBasicChatEvent { + readonly endId: number + readonly method: BasicChatEventMethod + readonly startId: number +} + +export interface BasicChatEventRequestResponse extends BaseBasicChatEvent { + readonly method: 'POST' + readonly type: 'ai-request-responseT' +} + +export interface BasicChatEventRequest { + readonly method: 'POST' + readonly type: 'ai-request' +} + +// TODO add types for tool call events + +export type BasicChatEvent = BasicChatEventRequestResponse | BasicChatEventRequest From f42373c34d15762bf144e4276178d5598faa24d8 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Tue, 12 May 2026 15:08:50 +0200 Subject: [PATCH 13/20] feat: Enhance loadSelectedEvent to include endEventId handling for improved event data retrieval (#265) --- .../src/parts/LoadSelectedEvent/LoadSelectedEvent.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/chat-debug-view/src/parts/LoadSelectedEvent/LoadSelectedEvent.ts b/packages/chat-debug-view/src/parts/LoadSelectedEvent/LoadSelectedEvent.ts index f3764b24..05cab55c 100644 --- a/packages/chat-debug-view/src/parts/LoadSelectedEvent/LoadSelectedEvent.ts +++ b/packages/chat-debug-view/src/parts/LoadSelectedEvent/LoadSelectedEvent.ts @@ -9,6 +9,16 @@ export const loadSelectedEvent = async ( _sessionIdIndexName: string, eventId: number, type: string, + endEventId?: number, ): Promise => { - return ChatStorageWorkerClient.loadSelectedEvent(sessionId, eventId, type) + const raw = await ChatStorageWorkerClient.loadSelectedEvent(sessionId, eventId, type) + if (endEventId && endEventId !== -1) { + const end = await ChatStorageWorkerClient.loadSelectedEvent(sessionId, endEventId, type) + // @ts-ignore + return { + ...raw, + endValue: end, + } + } + return raw } From 4a3c66e706ff0d0ed6cc069357a1dad82fe23984 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Tue, 12 May 2026 15:13:27 +0200 Subject: [PATCH 14/20] feat: Include eventEndId in loadSelectedEvent parameters for enhanced event detail retrieval (#262) --- .../src/parts/SelectEventAtIndex/SelectEventAtIndex.ts | 2 ++ packages/chat-debug-view/test/HandleEventRowClickAt.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/chat-debug-view/src/parts/SelectEventAtIndex/SelectEventAtIndex.ts b/packages/chat-debug-view/src/parts/SelectEventAtIndex/SelectEventAtIndex.ts index dd5fb050..48cc4aab 100644 --- a/packages/chat-debug-view/src/parts/SelectEventAtIndex/SelectEventAtIndex.ts +++ b/packages/chat-debug-view/src/parts/SelectEventAtIndex/SelectEventAtIndex.ts @@ -45,6 +45,8 @@ export const selectEventAtIndex = async ( sessionIdIndexName, selectedEvent.eventId, selectedEvent.type, + // @ts-ignore + selectedEvent['eventEndId'] || 0, ) const resolvedSelectedEvent = await withPreparedSelectedEventPreview(mergeSelectedEventDetails(selectedEvent, selectedEventDetails)) return withSelectedEventVisible({ diff --git a/packages/chat-debug-view/test/HandleEventRowClickAt.test.ts b/packages/chat-debug-view/test/HandleEventRowClickAt.test.ts index 961fcd84..08e11f59 100644 --- a/packages/chat-debug-view/test/HandleEventRowClickAt.test.ts +++ b/packages/chat-debug-view/test/HandleEventRowClickAt.test.ts @@ -8,7 +8,7 @@ import { hasDetailTab } from '../src/parts/HasDetailTab/HasDetailTab.ts' import { createDefaultState } from '../src/parts/State/CreateDefaultState.ts' import { applyVirtualTableState } from '../src/parts/VirtualTable/VirtualTable.ts' -type LoadSelectedEventFn = typeof LoadSelectedEvent.loadSelectedEvent +type LoadSelectedEventFn = (...args: readonly unknown[]) => ReturnType const tableClientX = 30 const row0ClientY = 180 @@ -74,7 +74,7 @@ test('handleEventRowClick should select the clicked event row and load details', startTime: '2026-03-08T00:00:02.000Z', type: 'request', }) - expect(loadSelectedEvent).toHaveBeenCalledWith('lvce-chat-view-sessions', 2, 'chat-view-events', 'session-1', 'sessionId', 3, 'request') + expect(loadSelectedEvent).toHaveBeenCalledWith('lvce-chat-view-sessions', 2, 'chat-view-events', 'session-1', 'sessionId', 3, 'request', 0) }) test('handleEventRowClick should ignore clicks outside the table body', async () => { From 1a75cab19004938d8f450e055508a369aa210e44 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Tue, 12 May 2026 15:19:14 +0200 Subject: [PATCH 15/20] fix: Add eventEndId to ai-request-response objects in toPrettyEvents function (#261) --- .../src/parts/ToPrettyEvents/ToPrettyEvents.ts | 8 ++++++++ packages/chat-debug-view/test/ToPrettyEvents.test.ts | 1 + 2 files changed, 9 insertions(+) diff --git a/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts b/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts index 24825e57..49c5c26d 100644 --- a/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts +++ b/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts @@ -12,6 +12,7 @@ export const toPrettyEvents = (rawEvents: ListChatViewEventsResult): readonly Ch if (item.type === 'ai-request' && 'requestId' in item && typeof item.requestId === 'string') { const response = map[item.requestId] if (response) { +<<<<<<< HEAD const parsedStart = new Date(item.timestamp || '') const parsedEnd = new Date(response.timestamp || '') const durationMs = parsedEnd.getTime() - parsedStart.getTime() @@ -28,6 +29,13 @@ export const toPrettyEvents = (rawEvents: ListChatViewEventsResult): readonly Ch type: 'ai-request-response', } pretty.push(mergedEvent) +======= + pretty.push({ + eventEndId: response.eventId, + eventId: item.eventId, + type: 'ai-request-response', + }) +>>>>>>> 40480644f9d8 (fix: Add eventEndId to ai-request-response objects in toPrettyEvents function (#261)) } else { pretty.push(item) } diff --git a/packages/chat-debug-view/test/ToPrettyEvents.test.ts b/packages/chat-debug-view/test/ToPrettyEvents.test.ts index 14dfbaea..aad15b45 100644 --- a/packages/chat-debug-view/test/ToPrettyEvents.test.ts +++ b/packages/chat-debug-view/test/ToPrettyEvents.test.ts @@ -20,6 +20,7 @@ test('toPrettyEvents should merge matching ai request and ai response events', ( expect(result).toEqual([ { + eventEndId: 2, eventId: 1, method: 'POST', type: 'ai-request-response', From b2029813d7289943b3dcf383f4fa1422a5a0ffd9 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Tue, 12 May 2026 15:20:07 +0200 Subject: [PATCH 16/20] =?UTF-8?q?refactor:=20Remove=20unused=20loadSelecte?= =?UTF-8?q?dEvent=20parameter=20from=20handleEventR=E2=80=A6=20(#266)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/parts/FocusIndex/FocusIndex.ts | 3 +- .../src/parts/FocusLast/FocusLast.ts | 2 +- .../src/parts/FocusNext/FocusNext.ts | 2 +- .../src/parts/FocusPrevious/FocusPrevious.ts | 2 +- .../GetTableBodyEventIndex.ts | 2 +- .../HandleEventRowClick.ts | 12 +--- .../HandleEventRowClickAt.ts | 6 +- .../HandleTableRowCopy/HandleTableRowCopy.ts | 2 +- .../HandleTableRowOpenInNewTab.ts | 2 +- .../src/parts/SelectCurrent/SelectCurrent.ts | 2 +- .../SelectEventAtIndex/SelectEventAtIndex.ts | 15 +---- .../test/HandleEventRowClickAt.test.ts | 66 ++++++++++--------- .../test/SelectEventAtIndex.test.ts | 35 +++++----- 13 files changed, 67 insertions(+), 84 deletions(-) diff --git a/packages/chat-debug-view/src/parts/FocusIndex/FocusIndex.ts b/packages/chat-debug-view/src/parts/FocusIndex/FocusIndex.ts index 4114d5ce..6eac35c3 100644 --- a/packages/chat-debug-view/src/parts/FocusIndex/FocusIndex.ts +++ b/packages/chat-debug-view/src/parts/FocusIndex/FocusIndex.ts @@ -1,5 +1,6 @@ import type { ChatDebugViewState } from '../State/ChatDebugViewState.ts' -import { getCurrentEvents, selectEventAtIndex } from '../SelectEventAtIndex/SelectEventAtIndex.ts' +import { getCurrentEvents } from '../PreserveSelection/GetCurrentEvents/GetCurrentEvents.ts' +import { selectEventAtIndex } from '../SelectEventAtIndex/SelectEventAtIndex.ts' export const focusIndex = async (state: ChatDebugViewState, index: number): Promise => { const currentEvents = getCurrentEvents(state) diff --git a/packages/chat-debug-view/src/parts/FocusLast/FocusLast.ts b/packages/chat-debug-view/src/parts/FocusLast/FocusLast.ts index 34a8d6b2..007e4d1c 100644 --- a/packages/chat-debug-view/src/parts/FocusLast/FocusLast.ts +++ b/packages/chat-debug-view/src/parts/FocusLast/FocusLast.ts @@ -1,6 +1,6 @@ import type { ChatDebugViewState } from '../State/ChatDebugViewState.ts' import { focusIndex } from '../FocusIndex/FocusIndex.ts' -import { getCurrentEvents } from '../SelectEventAtIndex/SelectEventAtIndex.ts' +import { getCurrentEvents } from '../LoadEvents/GetCurrentEvents/GetCurrentEvents.ts' export const focusLast = async (state: ChatDebugViewState): Promise => { const currentEvents = getCurrentEvents(state) diff --git a/packages/chat-debug-view/src/parts/FocusNext/FocusNext.ts b/packages/chat-debug-view/src/parts/FocusNext/FocusNext.ts index 2f451b07..85ba54f1 100644 --- a/packages/chat-debug-view/src/parts/FocusNext/FocusNext.ts +++ b/packages/chat-debug-view/src/parts/FocusNext/FocusNext.ts @@ -1,6 +1,6 @@ import type { ChatDebugViewState } from '../State/ChatDebugViewState.ts' import { focusIndex } from '../FocusIndex/FocusIndex.ts' -import { getCurrentEvents } from '../SelectEventAtIndex/SelectEventAtIndex.ts' +import { getCurrentEvents } from '../LoadEvents/GetCurrentEvents/GetCurrentEvents.ts' export const focusNext = async (state: ChatDebugViewState): Promise => { const currentEvents = getCurrentEvents(state) diff --git a/packages/chat-debug-view/src/parts/FocusPrevious/FocusPrevious.ts b/packages/chat-debug-view/src/parts/FocusPrevious/FocusPrevious.ts index 552ed015..94105b93 100644 --- a/packages/chat-debug-view/src/parts/FocusPrevious/FocusPrevious.ts +++ b/packages/chat-debug-view/src/parts/FocusPrevious/FocusPrevious.ts @@ -1,6 +1,6 @@ import type { ChatDebugViewState } from '../State/ChatDebugViewState.ts' import { focusIndex } from '../FocusIndex/FocusIndex.ts' -import { getCurrentEvents } from '../SelectEventAtIndex/SelectEventAtIndex.ts' +import { getCurrentEvents } from '../LoadEvents/GetCurrentEvents/GetCurrentEvents.ts' export const focusPrevious = async (state: ChatDebugViewState): Promise => { const currentEvents = getCurrentEvents(state) diff --git a/packages/chat-debug-view/src/parts/GetTableBodyEventIndex/GetTableBodyEventIndex.ts b/packages/chat-debug-view/src/parts/GetTableBodyEventIndex/GetTableBodyEventIndex.ts index 9c024eb8..647d05e9 100644 --- a/packages/chat-debug-view/src/parts/GetTableBodyEventIndex/GetTableBodyEventIndex.ts +++ b/packages/chat-debug-view/src/parts/GetTableBodyEventIndex/GetTableBodyEventIndex.ts @@ -1,6 +1,6 @@ import type { ChatDebugViewState } from '../State/ChatDebugViewState.ts' import { getTableBodyY } from '../GetTableBodyY/GetTableBodyY.ts' -import { getCurrentEvents } from '../SelectEventAtIndex/SelectEventAtIndex.ts' +import { getCurrentEvents } from '../LoadEvents/GetCurrentEvents/GetCurrentEvents.ts' import { clampTableWidth } from '../SplitLayout/SplitLayout.ts' import { devtoolsTableRowHeight } from '../TableMetrics/TableMetrics.ts' diff --git a/packages/chat-debug-view/src/parts/HandleEventRowClick/HandleEventRowClick.ts b/packages/chat-debug-view/src/parts/HandleEventRowClick/HandleEventRowClick.ts index bfd99115..3c0fd09e 100644 --- a/packages/chat-debug-view/src/parts/HandleEventRowClick/HandleEventRowClick.ts +++ b/packages/chat-debug-view/src/parts/HandleEventRowClick/HandleEventRowClick.ts @@ -1,19 +1,11 @@ -import type * as LoadSelectedEvent from '../LoadSelectedEvent/LoadSelectedEvent.ts' import type { ChatDebugViewState } from '../State/ChatDebugViewState.ts' import { selectEventAtIndex } from '../SelectEventAtIndex/SelectEventAtIndex.ts' -type LoadSelectedEventFn = typeof LoadSelectedEvent.loadSelectedEvent - const isPrimaryButton = (button: number): boolean => { return button === 0 } -export const handleEventRowClick = async ( - state: ChatDebugViewState, - index: string | number, - button: number = 0, - loadSelectedEvent?: LoadSelectedEventFn, -): Promise => { +export const handleEventRowClick = async (state: ChatDebugViewState, index: string | number, button: number = 0): Promise => { const actual = typeof index === 'string' ? Number.parseInt(index, 10) : index if (!isPrimaryButton(button)) { return state @@ -21,5 +13,5 @@ export const handleEventRowClick = async ( if (actual === -1) { return state } - return selectEventAtIndex(state, actual, loadSelectedEvent) + return selectEventAtIndex(state, actual) } diff --git a/packages/chat-debug-view/src/parts/HandleEventRowClickAt/HandleEventRowClickAt.ts b/packages/chat-debug-view/src/parts/HandleEventRowClickAt/HandleEventRowClickAt.ts index 28775353..457fe421 100644 --- a/packages/chat-debug-view/src/parts/HandleEventRowClickAt/HandleEventRowClickAt.ts +++ b/packages/chat-debug-view/src/parts/HandleEventRowClickAt/HandleEventRowClickAt.ts @@ -1,17 +1,13 @@ -import type * as LoadSelectedEvent from '../LoadSelectedEvent/LoadSelectedEvent.ts' import type { ChatDebugViewState } from '../State/ChatDebugViewState.ts' import { getTableBodyEventIndex } from '../GetTableBodyEventIndex/GetTableBodyEventIndex.ts' import { handleEventRowClick } from '../HandleEventRowClick/HandleEventRowClick.ts' -type LoadSelectedEventFn = typeof LoadSelectedEvent.loadSelectedEvent - export const handleEventRowClickAt = async ( state: ChatDebugViewState, eventX: number, eventY: number, button: number = 0, - loadSelectedEvent?: LoadSelectedEventFn, ): Promise => { const selectedEventIndex = getTableBodyEventIndex(state, eventX, eventY) - return handleEventRowClick(state, selectedEventIndex, button, loadSelectedEvent) + return handleEventRowClick(state, selectedEventIndex, button) } diff --git a/packages/chat-debug-view/src/parts/HandleTableRowCopy/HandleTableRowCopy.ts b/packages/chat-debug-view/src/parts/HandleTableRowCopy/HandleTableRowCopy.ts index 534b9c8f..6b3070cf 100644 --- a/packages/chat-debug-view/src/parts/HandleTableRowCopy/HandleTableRowCopy.ts +++ b/packages/chat-debug-view/src/parts/HandleTableRowCopy/HandleTableRowCopy.ts @@ -1,6 +1,6 @@ import { RendererWorker } from '@lvce-editor/rpc-registry' import type { ChatDebugViewState } from '../State/ChatDebugViewState.ts' -import { getCurrentEvents } from '../SelectEventAtIndex/SelectEventAtIndex.ts' +import { getCurrentEvents } from '../LoadEvents/GetCurrentEvents/GetCurrentEvents.ts' export const handleTableRowCopy = async (state: ChatDebugViewState, eventIndex: number): Promise => { const currentEvents = getCurrentEvents(state) diff --git a/packages/chat-debug-view/src/parts/HandleTableRowOpenInNewTab/HandleTableRowOpenInNewTab.ts b/packages/chat-debug-view/src/parts/HandleTableRowOpenInNewTab/HandleTableRowOpenInNewTab.ts index b9ea4a28..f448ed68 100644 --- a/packages/chat-debug-view/src/parts/HandleTableRowOpenInNewTab/HandleTableRowOpenInNewTab.ts +++ b/packages/chat-debug-view/src/parts/HandleTableRowOpenInNewTab/HandleTableRowOpenInNewTab.ts @@ -1,6 +1,6 @@ import { RendererWorker } from '@lvce-editor/rpc-registry' import type { ChatDebugViewState } from '../State/ChatDebugViewState.ts' -import { getCurrentEvents } from '../SelectEventAtIndex/SelectEventAtIndex.ts' +import { getCurrentEvents } from '../LoadEvents/GetCurrentEvents/GetCurrentEvents.ts' const toDataUri = (text: string): string => { return `data:application/json,${encodeURIComponent(text)}` diff --git a/packages/chat-debug-view/src/parts/SelectCurrent/SelectCurrent.ts b/packages/chat-debug-view/src/parts/SelectCurrent/SelectCurrent.ts index dc5d46b7..89d0ecd0 100644 --- a/packages/chat-debug-view/src/parts/SelectCurrent/SelectCurrent.ts +++ b/packages/chat-debug-view/src/parts/SelectCurrent/SelectCurrent.ts @@ -1,6 +1,6 @@ import type { ChatDebugViewState } from '../State/ChatDebugViewState.ts' import { focusIndex } from '../FocusIndex/FocusIndex.ts' -import { getCurrentEvents } from '../SelectEventAtIndex/SelectEventAtIndex.ts' +import { getCurrentEvents } from '../LoadEvents/GetCurrentEvents/GetCurrentEvents.ts' export const selectCurrent = async (state: ChatDebugViewState): Promise => { const currentEvents = getCurrentEvents(state) diff --git a/packages/chat-debug-view/src/parts/SelectEventAtIndex/SelectEventAtIndex.ts b/packages/chat-debug-view/src/parts/SelectEventAtIndex/SelectEventAtIndex.ts index 48cc4aab..03d33e57 100644 --- a/packages/chat-debug-view/src/parts/SelectEventAtIndex/SelectEventAtIndex.ts +++ b/packages/chat-debug-view/src/parts/SelectEventAtIndex/SelectEventAtIndex.ts @@ -1,22 +1,13 @@ -import type { ChatViewEvent } from '../ChatViewEvent/ChatViewEvent.ts' import type { ChatDebugViewState } from '../State/ChatDebugViewState.ts' import { createDetailTabs } from '../CreateDetailTabs/CreateDetailTabs.ts' import { getSelectedDetailTab } from '../GetSelectedDetailTab/GetSelectedDetailTab.ts' -import { getCurrentEvents as getSharedCurrentEvents } from '../LoadEvents/GetCurrentEvents/GetCurrentEvents.ts' +import { getCurrentEvents } from '../LoadEvents/GetCurrentEvents/GetCurrentEvents.ts' import * as LoadSelectedEvent from '../LoadSelectedEvent/LoadSelectedEvent.ts' import { mergeSelectedEventDetails } from '../MergeSelectedEventDetails/MergeSelectedEventDetails.ts' import { withSelectedEventVisible } from '../VirtualTable/VirtualTable.ts' import { withPreparedSelectedEventPreview } from '../WithPreparedSelectedEventPreview/WithPreparedSelectedEventPreview.ts' -type LoadSelectedEventFn = typeof LoadSelectedEvent.loadSelectedEvent - -export const getCurrentEvents = (state: ChatDebugViewState): readonly ChatViewEvent[] => getSharedCurrentEvents(state) - -export const selectEventAtIndex = async ( - state: ChatDebugViewState, - selectedEventIndex: number, - loadSelectedEvent: LoadSelectedEventFn = LoadSelectedEvent.loadSelectedEvent, -): Promise => { +export const selectEventAtIndex = async (state: ChatDebugViewState, selectedEventIndex: number): Promise => { const { databaseName, dataBaseVersion, detailTabs, eventStoreName, sessionId, sessionIdIndexName } = state const selectedDetailTab = getSelectedDetailTab(detailTabs) const currentEvents = getCurrentEvents(state) @@ -37,7 +28,7 @@ export const selectEventAtIndex = async ( selectedEventIndex, } } - const selectedEventDetails = await loadSelectedEvent( + const selectedEventDetails = await LoadSelectedEvent.loadSelectedEvent( databaseName, dataBaseVersion, eventStoreName, diff --git a/packages/chat-debug-view/test/HandleEventRowClickAt.test.ts b/packages/chat-debug-view/test/HandleEventRowClickAt.test.ts index 08e11f59..b30cb0a2 100644 --- a/packages/chat-debug-view/test/HandleEventRowClickAt.test.ts +++ b/packages/chat-debug-view/test/HandleEventRowClickAt.test.ts @@ -1,5 +1,5 @@ -import { afterEach, expect, jest, test } from '@jest/globals' -import type * as LoadSelectedEvent from '../src/parts/LoadSelectedEvent/LoadSelectedEvent.ts' +import { expect, test } from '@jest/globals' +import { ChatStorageWorker } from '@lvce-editor/rpc-registry' import type { ChatDebugViewState } from '../src/parts/State/ChatDebugViewState.ts' import { createDetailTabs } from '../src/parts/CreateDetailTabs/CreateDetailTabs.ts' import { getSelectedDetailTab } from '../src/parts/GetSelectedDetailTab/GetSelectedDetailTab.ts' @@ -7,9 +7,6 @@ import { handleEventRowClickAt } from '../src/parts/HandleEventRowClickAt/Handle import { hasDetailTab } from '../src/parts/HasDetailTab/HasDetailTab.ts' import { createDefaultState } from '../src/parts/State/CreateDefaultState.ts' import { applyVirtualTableState } from '../src/parts/VirtualTable/VirtualTable.ts' - -type LoadSelectedEventFn = (...args: readonly unknown[]) => ReturnType - const tableClientX = 30 const row0ClientY = 180 const row1ClientY = 197 @@ -27,16 +24,7 @@ const createClickableState = (overrides: Partial = {}): Chat }) } -afterEach(() => { - jest.restoreAllMocks() -}) - test('handleEventRowClick should select the clicked event row and load details', async () => { - const loadSelectedEvent = jest.fn().mockResolvedValue({ - detail: 'value', - eventId: 3, - type: 'request', - }) const state = createClickableState({ events: [ { @@ -63,7 +51,15 @@ test('handleEventRowClick should select the clicked event row and load details', ], sessionId: 'session-1', }) - const result = await handleEventRowClickAt(state, tableClientX, row2ClientY, 0, loadSelectedEvent) + using mockRpc = ChatStorageWorker.registerMockRpc({ + 'ChatStorage.loadSelectedEvent': () => ({ + detail: 'value', + eventId: 3, + type: 'request', + }), + }) + + const result = await handleEventRowClickAt(state, tableClientX, row2ClientY, 0) expect(result.selectedEventIndex).toBe(2) expect(result.selectedEvent).toEqual({ @@ -74,13 +70,14 @@ test('handleEventRowClick should select the clicked event row and load details', startTime: '2026-03-08T00:00:02.000Z', type: 'request', }) - expect(loadSelectedEvent).toHaveBeenCalledWith('lvce-chat-view-sessions', 2, 'chat-view-events', 'session-1', 'sessionId', 3, 'request', 0) + expect(mockRpc.invocations).toEqual([['ChatStorage.loadSelectedEvent', 'session-1', 3, 'request']]) }) test('handleEventRowClick should ignore clicks outside the table body', async () => { const state = createClickableState({ selectedEventIndex: 1, }) + const result = await handleEventRowClickAt(state, tableClientX, 171, 0) expect(result).toBe(state) @@ -129,7 +126,6 @@ test('handleEventRowClick should fall back to the in-memory event when it has no }) test('handleEventRowClick should fall back to the selected list event when loading details returns null', async () => { - const loadSelectedEvent = jest.fn().mockResolvedValue(null) const state = createClickableState({ events: [ { @@ -142,8 +138,11 @@ test('handleEventRowClick should fall back to the selected list event when loadi ], sessionId: 'session-1', }) + using mockRpc = ChatStorageWorker.registerMockRpc({ + 'ChatStorage.loadSelectedEvent': () => null, + }) - const result = await handleEventRowClickAt(state, tableClientX, row0ClientY, 0, loadSelectedEvent) + const result = await handleEventRowClickAt(state, tableClientX, row0ClientY, 0) expect(result.selectedEventIndex).toBe(0) expect(result.selectedEventId).toBe(1) @@ -154,14 +153,10 @@ test('handleEventRowClick should fall back to the selected list event when loadi timestamp: '2026-03-08T00:00:00.000Z', type: 'request', }) + expect(mockRpc.invocations).toEqual([['ChatStorage.loadSelectedEvent', 'session-1', 1, 'request']]) }) test('handleEventRowClick should preserve selected detail tab when switching rows', async () => { - const loadSelectedEvent = jest.fn().mockResolvedValue({ - detail: 'preview', - eventId: 2, - type: 'response', - }) const state = createClickableState({ detailTabs: createDetailTabs('preview'), events: [ @@ -178,8 +173,15 @@ test('handleEventRowClick should preserve selected detail tab when switching row ], sessionId: 'session-1', }) + using mockRpc = ChatStorageWorker.registerMockRpc({ + 'ChatStorage.loadSelectedEvent': () => ({ + detail: 'preview', + eventId: 2, + type: 'response', + }), + }) - const result = await handleEventRowClickAt(state, tableClientX, row1ClientY, 0, loadSelectedEvent) + const result = await handleEventRowClickAt(state, tableClientX, row1ClientY, 0) expect(getSelectedDetailTab(result.detailTabs)).toBe('preview') expect(result.selectedEventIndex).toBe(1) @@ -189,14 +191,10 @@ test('handleEventRowClick should preserve selected detail tab when switching row timestamp: '2026-03-08T00:00:01.000Z', type: 'response', }) + expect(mockRpc.invocations).toEqual([['ChatStorage.loadSelectedEvent', 'session-1', 2, 'response']]) }) test('handleEventRowClick should fall back to response and hide timing when the selected event has no timing details', async () => { - const loadSelectedEvent = jest.fn().mockResolvedValue({ - detail: 'preview', - eventId: 2, - type: 'chat-message-added', - }) const state = createClickableState({ detailTabs: createDetailTabs('timing'), events: [ @@ -215,8 +213,15 @@ test('handleEventRowClick should fall back to response and hide timing when the ], sessionId: 'session-1', }) + using mockRpc = ChatStorageWorker.registerMockRpc({ + 'ChatStorage.loadSelectedEvent': () => ({ + detail: 'preview', + eventId: 2, + type: 'chat-message-added', + }), + }) - const result = await handleEventRowClickAt(state, tableClientX, row1ClientY, 0, loadSelectedEvent) + const result = await handleEventRowClickAt(state, tableClientX, row1ClientY, 0) expect(getSelectedDetailTab(result.detailTabs)).toBe('response') expect(hasDetailTab(result.detailTabs, 'timing')).toBe(false) @@ -228,4 +233,5 @@ test('handleEventRowClick should fall back to response and hide timing when the timestamp: '2026-03-08T00:00:01.000Z', type: 'chat-message-added', }) + expect(mockRpc.invocations).toEqual([['ChatStorage.loadSelectedEvent', 'session-1', 2, 'chat-message-added']]) }) diff --git a/packages/chat-debug-view/test/SelectEventAtIndex.test.ts b/packages/chat-debug-view/test/SelectEventAtIndex.test.ts index 8ea75090..8b992b5f 100644 --- a/packages/chat-debug-view/test/SelectEventAtIndex.test.ts +++ b/packages/chat-debug-view/test/SelectEventAtIndex.test.ts @@ -1,13 +1,10 @@ -import { expect, jest, test } from '@jest/globals' +import { expect, test } from '@jest/globals' +import { ChatStorageWorker } from '@lvce-editor/rpc-registry' import type { ChatViewEvent } from '../src/parts/ChatViewEvent/ChatViewEvent.ts' -import type * as LoadSelectedEvent from '../src/parts/LoadSelectedEvent/LoadSelectedEvent.ts' import { selectEventAtIndex } from '../src/parts/SelectEventAtIndex/SelectEventAtIndex.ts' import { createDefaultState } from '../src/parts/State/CreateDefaultState.ts' -type LoadSelectedEventFn = typeof LoadSelectedEvent.loadSelectedEvent - test('selectEventAtIndex should clear the selected event when the index is out of range', async () => { - const loadSelectedEvent = jest.fn() const state = { ...createDefaultState(), events: [ @@ -19,7 +16,7 @@ test('selectEventAtIndex should clear the selected event when the index is out o ], } - const result = await selectEventAtIndex(state, 3, loadSelectedEvent) + const result = await selectEventAtIndex(state, 3) expect(result).toEqual({ ...state, @@ -27,11 +24,9 @@ test('selectEventAtIndex should clear the selected event when the index is out o selectedEventId: null, selectedEventIndex: 3, }) - expect(loadSelectedEvent).toHaveBeenCalledTimes(0) }) test('selectEventAtIndex should keep the event selected when it has no numeric event id', async () => { - const loadSelectedEvent = jest.fn() const invalidEvent = { eventId: 'missing-id', timestamp: '2026-03-08T00:00:00.000Z', @@ -42,7 +37,7 @@ test('selectEventAtIndex should keep the event selected when it has no numeric e events: [invalidEvent], } - const result = await selectEventAtIndex(state, 0, loadSelectedEvent) + const result = await selectEventAtIndex(state, 0) expect(result).toEqual({ ...state, @@ -50,18 +45,9 @@ test('selectEventAtIndex should keep the event selected when it has no numeric e selectedEventId: null, selectedEventIndex: 0, }) - expect(loadSelectedEvent).toHaveBeenCalledTimes(0) }) test('selectEventAtIndex should preserve merged ai request and response details', async () => { - const loadSelectedEvent = jest.fn().mockResolvedValue({ - body: { - input: ['1+1'], - }, - eventId: 1, - requestId: 'request-1', - type: 'ai-request', - }) const mergedEvent = { ended: '2026-04-19T12:00:00.250Z', eventId: 1, @@ -87,8 +73,18 @@ test('selectEventAtIndex should preserve merged ai request and response details' events: [mergedEvent], sessionId: 'session-1', } + using mockRpc = ChatStorageWorker.registerMockRpc({ + 'ChatStorage.loadSelectedEvent': () => ({ + body: { + input: ['1+1'], + }, + eventId: 1, + requestId: 'request-1', + type: 'ai-request', + }), + }) - const result = await selectEventAtIndex(state, 0, loadSelectedEvent) + const result = await selectEventAtIndex(state, 0) expect(result.selectedEvent).toEqual( expect.objectContaining({ @@ -110,4 +106,5 @@ test('selectEventAtIndex should preserve merged ai request and response details' }, }), ) + expect(mockRpc.invocations).toEqual([['ChatStorage.loadSelectedEvent', 'session-1', 1, 'ai-request']]) }) From 29a9dfe003efaa808aaf0450209cf8fcc38873d2 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Tue, 12 May 2026 15:25:12 +0200 Subject: [PATCH 17/20] feat: Refactor getTokenUsageDetailsDom to use row view models for cleaner code and improved readability (#267) --- .../GetTokenUsageDetailsDom.ts | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/packages/chat-debug-view/src/parts/GetTokenUsageDetailsDom/GetTokenUsageDetailsDom.ts b/packages/chat-debug-view/src/parts/GetTokenUsageDetailsDom/GetTokenUsageDetailsDom.ts index 17af631d..ac6d7082 100644 --- a/packages/chat-debug-view/src/parts/GetTokenUsageDetailsDom/GetTokenUsageDetailsDom.ts +++ b/packages/chat-debug-view/src/parts/GetTokenUsageDetailsDom/GetTokenUsageDetailsDom.ts @@ -5,28 +5,46 @@ import { ChatDebugViewTiming } from '../ClassNames/ClassNames.ts' import { getTimingRowDom } from '../GetTimingRowDom/GetTimingRowDom.ts' import { getTokenUsageDetails } from '../GetTokenUsageDetails/GetTokenUsageDetails.ts' -export const getTokenUsageDetailsDom = (event: ChatViewEvent): readonly VirtualDomNode[] => { +type TokenUsageRowViewModel = { + readonly key: string + readonly value: number | undefined +} + +type DefinedTokenUsageRowViewModel = { + readonly key: string + readonly value: number +} + +const getRowViewModels = (event: ChatViewEvent): readonly DefinedTokenUsageRowViewModel[] => { const usageDetails = getTokenUsageDetails(event) if (!usageDetails) { return [] } - const rows: VirtualDomNode[] = [] - let rowCount = 0 - if (usageDetails.inputTokens !== undefined) { - rows.push(...getTimingRowDom(ChatDebugStrings.inputTokens(), String(usageDetails.inputTokens))) - rowCount++ - } - if (usageDetails.outputTokens !== undefined) { - rows.push(...getTimingRowDom(ChatDebugStrings.outputTokens(), String(usageDetails.outputTokens))) - rowCount++ - } - if (usageDetails.cachedTokens !== undefined) { - rows.push(...getTimingRowDom(ChatDebugStrings.cachedTokens(), String(usageDetails.cachedTokens))) - rowCount++ + return [ + { + key: ChatDebugStrings.inputTokens(), + value: usageDetails.inputTokens, + }, + { + key: ChatDebugStrings.outputTokens(), + value: usageDetails.outputTokens, + }, + { + key: ChatDebugStrings.cachedTokens(), + value: usageDetails.cachedTokens, + }, + ].filter((row: Readonly): row is DefinedTokenUsageRowViewModel => row.value !== undefined) +} + +export const getTokenUsageDetailsDom = (event: ChatViewEvent): readonly VirtualDomNode[] => { + const rowViewModels = getRowViewModels(event) + if (rowViewModels.length === 0) { + return [] } + const rows = rowViewModels.flatMap((row: Readonly) => getTimingRowDom(row.key, String(row.value))) return [ { - childCount: rowCount, + childCount: rowViewModels.length, className: ChatDebugViewTiming, type: VirtualDomElements.Div, }, From 418a4b5185a03a5363683ce2553d427e2bc72dd7 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Tue, 12 May 2026 15:35:02 +0200 Subject: [PATCH 18/20] fix --- .../parts/ToPrettyEvents/ToPrettyEvents.ts | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts b/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts index 49c5c26d..12eaae96 100644 --- a/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts +++ b/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts @@ -12,30 +12,26 @@ export const toPrettyEvents = (rawEvents: ListChatViewEventsResult): readonly Ch if (item.type === 'ai-request' && 'requestId' in item && typeof item.requestId === 'string') { const response = map[item.requestId] if (response) { -<<<<<<< HEAD const parsedStart = new Date(item.timestamp || '') const parsedEnd = new Date(response.timestamp || '') const durationMs = parsedEnd.getTime() - parsedStart.getTime() - const mergedEvent: ChatViewEvent = Number.isFinite(durationMs) - ? { - eventId: item.eventId, - method: 'POST', - time: `${durationMs}ms`, - type: 'ai-request-response', - } - : { - eventId: item.eventId, - method: 'POST', - type: 'ai-request-response', - } + const mergedEvent: ChatViewEvent = + Number.isFinite(durationMs) && durationMs >= 0 + ? { + durationMs, + eventEndId: response.eventId, + eventId: item.eventId, + method: 'POST', + time: `${durationMs}ms`, + type: 'ai-request-response', + } + : { + eventEndId: response.eventId, + eventId: item.eventId, + method: 'POST', + type: 'ai-request-response', + } pretty.push(mergedEvent) -======= - pretty.push({ - eventEndId: response.eventId, - eventId: item.eventId, - type: 'ai-request-response', - }) ->>>>>>> 40480644f9d8 (fix: Add eventEndId to ai-request-response objects in toPrettyEvents function (#261)) } else { pretty.push(item) } From a54c4a0a2d2a47e8c36bb2258517002d758821fc Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Tue, 12 May 2026 15:37:42 +0200 Subject: [PATCH 19/20] fix --- .../src/parts/ToPrettyEvents/ToPrettyEvents.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts b/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts index e382a5b5..0f4a247b 100644 --- a/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts +++ b/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts @@ -12,7 +12,6 @@ export const toPrettyEvents = (rawEvents: ListChatViewEventsResult): readonly Ch if (item.type === 'ai-request' && 'requestId' in item && typeof item.requestId === 'string') { const response = map[item.requestId] if (response) { -<<<<<<< HEAD const parsedStart = new Date(item.timestamp || '') const parsedEnd = new Date(response.timestamp || '') const durationMs = parsedEnd.getTime() - parsedStart.getTime() @@ -23,7 +22,6 @@ export const toPrettyEvents = (rawEvents: ListChatViewEventsResult): readonly Ch eventEndId: response.eventId, eventId: item.eventId, method: 'POST', - time: `${durationMs}ms`, type: 'ai-request-response', } : { @@ -33,13 +31,6 @@ export const toPrettyEvents = (rawEvents: ListChatViewEventsResult): readonly Ch type: 'ai-request-response', } pretty.push(mergedEvent) -======= - pretty.push({ - eventEndId: response.eventId, - eventId: item.eventId, - type: 'ai-request-response', - }) ->>>>>>> origin/main } else { pretty.push(item) } From 4ee3865b82139daa3caad6b205fc507de18526de Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Tue, 12 May 2026 15:42:09 +0200 Subject: [PATCH 20/20] fix --- .../parts/ToPrettyEvents/ToPrettyEvents.ts | 43 +++++++++++-------- .../test/ToPrettyEvents.test.ts | 30 +++++++++++++ 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts b/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts index 0f4a247b..9fd05a1d 100644 --- a/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts +++ b/packages/chat-debug-view/src/parts/ToPrettyEvents/ToPrettyEvents.ts @@ -2,6 +2,29 @@ import type { ChatViewEvent } from '../ChatViewEvent/ChatViewEvent.ts' import type { ListChatViewEventsResult } from '../ListChatViewEventsResult/ListChatViewEventsResult.ts' import * as GetResponseMap from '../GetResponseMap/GetResponseMap.ts' +const getMergedRequestResponseEvent = (item: ChatViewEvent, response: ChatViewEvent): ChatViewEvent => { + const parsedStart = new Date(item.timestamp || '') + const parsedEnd = new Date(response.timestamp || '') + const durationMs = parsedEnd.getTime() - parsedStart.getTime() + + if (Number.isFinite(durationMs) && durationMs >= 0) { + return { + durationMs, + eventEndId: response.eventId, + eventId: item.eventId, + method: 'POST', + type: 'ai-request-response', + } + } + + return { + eventEndId: response.eventId, + eventId: item.eventId, + method: 'POST', + type: 'ai-request-response', + } +} + export const toPrettyEvents = (rawEvents: ListChatViewEventsResult): readonly ChatViewEvent[] => { if (rawEvents.type === 'error') { return [] @@ -12,25 +35,7 @@ export const toPrettyEvents = (rawEvents: ListChatViewEventsResult): readonly Ch if (item.type === 'ai-request' && 'requestId' in item && typeof item.requestId === 'string') { const response = map[item.requestId] if (response) { - const parsedStart = new Date(item.timestamp || '') - const parsedEnd = new Date(response.timestamp || '') - const durationMs = parsedEnd.getTime() - parsedStart.getTime() - const mergedEvent: ChatViewEvent = - Number.isFinite(durationMs) && durationMs >= 0 - ? { - durationMs, - eventEndId: response.eventId, - eventId: item.eventId, - method: 'POST', - type: 'ai-request-response', - } - : { - eventEndId: response.eventId, - eventId: item.eventId, - method: 'POST', - type: 'ai-request-response', - } - pretty.push(mergedEvent) + pretty.push(getMergedRequestResponseEvent(item, response)) } else { pretty.push(item) } diff --git a/packages/chat-debug-view/test/ToPrettyEvents.test.ts b/packages/chat-debug-view/test/ToPrettyEvents.test.ts index aad15b45..172437dd 100644 --- a/packages/chat-debug-view/test/ToPrettyEvents.test.ts +++ b/packages/chat-debug-view/test/ToPrettyEvents.test.ts @@ -1,6 +1,36 @@ import { expect, test } from '@jest/globals' import { toPrettyEvents } from '../src/parts/ToPrettyEvents/ToPrettyEvents.ts' +test('toPrettyEvents should include duration for merged ai request and ai response events', () => { + const requestEvent = { + eventId: 1, + requestId: 'request-1', + timestamp: '2026-05-12T10:00:00.000Z', + type: 'ai-request', + } + const responseEvent = { + eventId: 2, + requestId: 'request-1', + timestamp: '2026-05-12T10:00:00.125Z', + type: 'ai-response', + } + + const result = toPrettyEvents({ + events: [requestEvent, responseEvent], + type: 'success', + }) + + expect(result).toEqual([ + { + durationMs: 125, + eventEndId: 2, + eventId: 1, + method: 'POST', + type: 'ai-request-response', + }, + ]) +}) + test('toPrettyEvents should merge matching ai request and ai response events', () => { const requestEvent = { eventId: 1,