Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3c72e9f
feat: Add cell rendering functions for duration, method, status, and …
levivilet May 10, 2026
beb8144
better display
levivilet May 10, 2026
6094c56
memory
levivilet May 10, 2026
028a121
fix
levivilet May 10, 2026
e3587ac
Merge remote-tracking branch 'origin/main' into feature/dur
levivilet May 10, 2026
626c2f2
fix
levivilet May 10, 2026
39dda4e
fix
levivilet May 10, 2026
b4e6c9a
fix
levivilet May 10, 2026
7468ec8
feat: Implement createDevtoolsRows function and associated tests for …
levivilet May 10, 2026
3fc111f
refactor: Simplify state destructuring in handlePreviewTextScrollBarP…
levivilet May 10, 2026
7d6c8ee
feature: update dependencies (#260)
lvce-editor-helper-bot[bot] May 10, 2026
bcfe3f1
feat: Add SelectedEventDetails and SelectedEventDetailsOpenAiRequestR…
levivilet May 12, 2026
00c086c
feat: Add BasicChatEvent types for request and response handling (#263)
levivilet May 12, 2026
f42373c
feat: Enhance loadSelectedEvent to include endEventId handling for im…
levivilet May 12, 2026
4a3c66e
feat: Include eventEndId in loadSelectedEvent parameters for enhanced…
levivilet May 12, 2026
1a75cab
fix: Add eventEndId to ai-request-response objects in toPrettyEvents …
levivilet May 12, 2026
b202981
refactor: Remove unused loadSelectedEvent parameter from handleEventR…
levivilet May 12, 2026
29a9dfe
feat: Refactor getTokenUsageDetailsDom to use row view models for cle…
levivilet May 12, 2026
418a4b5
fix
levivilet May 12, 2026
a814fb4
Merge remote-tracking branch 'origin/main' into feature/dur
levivilet May 12, 2026
a54c4a0
fix
levivilet May 12, 2026
4ee3865
fix
levivilet May 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
@@ -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)),
]
}
Original file line number Diff line number Diff line change
@@ -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)),
]
}
Original file line number Diff line number Diff line change
@@ -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)),
]
}
Original file line number Diff line number Diff line change
@@ -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)),
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ 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
}
const toolName = getToolName(event)
if (!toolName) {
return ''
Expand Down
Original file line number Diff line number Diff line change
@@ -1,58 +1,11 @@
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 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 [
{
childCount: 1,
className: mergeClassNames(TableCell, ChatDebugViewCellDuration),
type: VirtualDomElements.Td,
},
text(getEventTableDurationText(event)),
]
case TableColumn.Method:
return [
{
childCount: 1,
className: TableCell,
type: VirtualDomElements.Td,
},
text(getEventTableMethodLabel(event)),
]
case TableColumn.Status:
return [
{
childCount: 1,
className: mergeClassNames(TableCell, isErrorStatus ? ChatDebugViewCellStatusError : ''),
type: VirtualDomElements.Td,
},
text(getStatusText(event)),
]
case TableColumn.Type:
return [
{
childCount: 1,
className: TableCell,
type: VirtualDomElements.Td,
},
text(getEventTableTypeLabel(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)
})
}
Original file line number Diff line number Diff line change
@@ -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 []
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 []
Expand All @@ -12,11 +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) {
pretty.push({
eventEndId: response.eventId,
eventId: item.eventId,
type: 'ai-request-response',
})
pretty.push(getMergedRequestResponseEvent(item, response))
} else {
pretty.push(item)
}
Expand Down
25 changes: 25 additions & 0 deletions packages/chat-debug-view/test/GetCellDurationDom.test.ts
Original file line number Diff line number Diff line change
@@ -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'),
])
})
24 changes: 24 additions & 0 deletions packages/chat-debug-view/test/GetCellMethodDom.test.ts
Original file line number Diff line number Diff line change
@@ -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'),
])
})
25 changes: 25 additions & 0 deletions packages/chat-debug-view/test/GetCellStatusDom.test.ts
Original file line number Diff line number Diff line change
@@ -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'),
])
})
24 changes: 24 additions & 0 deletions packages/chat-debug-view/test/GetCellTypeDom.test.ts
Original file line number Diff line number Diff line change
@@ -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'),
])
})
38 changes: 38 additions & 0 deletions packages/chat-debug-view/test/GetTableCellDom.test.ts
Original file line number Diff line number Diff line change
@@ -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([])
})
2 changes: 1 addition & 1 deletion packages/chat-debug-view/test/Refresh.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion packages/chat-debug-view/test/SetSessionId.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading
Loading