fix(client): address trajectory review findings
This commit is contained in:
@@ -1188,6 +1188,20 @@ function RecordPayload({
|
||||
: 'No result captured'
|
||||
if (!value) return <p className={css.noPayload}>{missing}</p>
|
||||
|
||||
const json = parseJsonContainer(value)
|
||||
const singleTextResult = direction === 'output'
|
||||
&& record.cell.outputBlocks?.length === 1
|
||||
&& record.cell.outputBlocks[0]?.type === 'text'
|
||||
if (singleTextResult && json !== undefined) {
|
||||
return (
|
||||
<JsonTree
|
||||
data={json}
|
||||
label="Result JSON"
|
||||
className={preview ? css.jsonPreview : css.jsonPayload}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
direction === 'output'
|
||||
&& record.cell.outputBlocks?.some(block =>
|
||||
@@ -1214,7 +1228,6 @@ function RecordPayload({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const json = parseJsonContainer(value)
|
||||
if (json !== undefined) {
|
||||
return (
|
||||
<JsonTree
|
||||
|
||||
@@ -5,7 +5,9 @@ import type { ConvViewProps } from '@deepseek-ai/dsh-client-ui-conversation/clie
|
||||
import type {
|
||||
AssistantMessageNode, ConversationContext, RequestView,
|
||||
} from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { deriveTrajectoryContextBranches } from './context-branches.ts'
|
||||
import {
|
||||
deriveTrajectoryContextBranches, trajectoryBranchContainsSeq,
|
||||
} from './context-branches.ts'
|
||||
import {
|
||||
TrajectoryTable,
|
||||
type TrajectoryRequestNumber,
|
||||
@@ -97,9 +99,13 @@ export function TrajectoryView({ useSession, loadAllHistory }: ConvViewProps & T
|
||||
)
|
||||
const currentBranch = branches.at(-1)
|
||||
if (currentBranch === undefined) throw new Error('trajectory branch projection must not be empty')
|
||||
const selectedNodes = inspection === undefined || inspection.eventNodes.length === 0
|
||||
? nodes
|
||||
: inspection.eventNodes
|
||||
const selectedNodes = currentBranch.nodes
|
||||
const selectedRequests = useMemo(
|
||||
() => requests.filter(request =>
|
||||
trajectoryBranchContainsSeq(currentBranch, request.startSeq),
|
||||
),
|
||||
[currentBranch, requests],
|
||||
)
|
||||
const globalRequestNumbers = useMemo<readonly TrajectoryRequestNumber[]>(() => {
|
||||
const assistantsByStep = new Map<string, AssistantMessageNode>()
|
||||
for (const context of contexts) {
|
||||
@@ -235,12 +241,12 @@ export function TrajectoryView({ useSession, loadAllHistory }: ConvViewProps & T
|
||||
nodes: selectedNodes,
|
||||
partial,
|
||||
runningCalls,
|
||||
requests,
|
||||
requests: selectedRequests,
|
||||
...(callSchemas === undefined ? {} : { callSchemas }),
|
||||
codeDispatches,
|
||||
}),
|
||||
[
|
||||
selectedNodes, partial, runningCalls, requests, callSchemas, codeDispatches,
|
||||
selectedNodes, partial, runningCalls, selectedRequests, callSchemas, codeDispatches,
|
||||
],
|
||||
)
|
||||
const collapsibleTurnIds = useMemo(
|
||||
|
||||
@@ -96,6 +96,33 @@ describe('TrajectoryTable', () => {
|
||||
expect(screen.getByText('ToolError: non_zero_exit')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('renders a single-text JSON tool result as a JSON tree', () => {
|
||||
const turns: readonly TrajectoryTurnModel[] = [{
|
||||
turn: 1,
|
||||
groups: [{
|
||||
title: 'Step 1',
|
||||
cells: [{
|
||||
index: 1,
|
||||
kind: 'tool',
|
||||
text: 'read {"path":"result.json"}',
|
||||
outputDetail: '{"value":1,"nested":{"ok":true}}',
|
||||
outputBlocks: [{
|
||||
type: 'text',
|
||||
content: '{"value":1,"nested":{"ok":true}}',
|
||||
}],
|
||||
timeSeconds: 0.1,
|
||||
}],
|
||||
}],
|
||||
}]
|
||||
|
||||
render(<TrajectoryTable turns={turns} {...FOLD_PROPS} />)
|
||||
fireEvent.click(screen.getByRole('row', { name: /TOOL/ }))
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'Result' }))
|
||||
|
||||
expect(screen.getByRole('tree', { name: 'Result JSON' })).toBeTruthy()
|
||||
expect(screen.getByText('value:')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('keeps the first row and a compact summary when a turn is collapsed', () => {
|
||||
render(
|
||||
<TrajectoryTable
|
||||
|
||||
@@ -16,7 +16,7 @@ import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { UseSession } from '@deepseek-ai/dsh-client-web-react'
|
||||
import { SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type {
|
||||
ConversationSnapshot, SessionId, SessionListState, WorkspaceListState,
|
||||
ConversationSnapshot, RequestView, SessionId, SessionListState, WorkspaceListState,
|
||||
} from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { ConvViewProps, ViewTab } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
// Export discipline: packages/client/AGENTS.md.
|
||||
@@ -270,6 +270,79 @@ describe('span derivation', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('TrajectoryView branches', () => {
|
||||
it('renders only the selected rewind branch while retaining session-global requests', () => {
|
||||
const retained = {
|
||||
kind: 'user',
|
||||
seq: 1,
|
||||
time: 1_000,
|
||||
content: [{ type: 'text', text: 'retained user' }],
|
||||
source: null,
|
||||
} as unknown as ConversationSnapshot['nodes'][number]
|
||||
const abandoned = {
|
||||
kind: 'assistant',
|
||||
seq: 3,
|
||||
time: 3_000,
|
||||
turn: 1,
|
||||
step: 1,
|
||||
blocks: [{ kind: 'text', text: 'abandoned response' }],
|
||||
} as unknown as ConversationSnapshot['nodes'][number]
|
||||
const current = {
|
||||
kind: 'assistant',
|
||||
seq: 5,
|
||||
time: 5_000,
|
||||
turn: 2,
|
||||
step: 1,
|
||||
blocks: [{ kind: 'text', text: 'current response' }],
|
||||
} as unknown as ConversationSnapshot['nodes'][number]
|
||||
const request = (startSeq: number, turn: number): RequestView => ({
|
||||
purpose: 'assistant',
|
||||
startSeq,
|
||||
turn,
|
||||
step: 1,
|
||||
startedAt: startSeq * 1_000,
|
||||
completedAt: startSeq * 1_000 + 100,
|
||||
status: 'complete',
|
||||
})
|
||||
const store = createSnapshotStore({
|
||||
nodes: [retained, current],
|
||||
inspection: {
|
||||
eventNodes: [retained, abandoned, current],
|
||||
contexts: [
|
||||
{ id: 0, nodes: [retained, abandoned] },
|
||||
{
|
||||
id: 1,
|
||||
parentId: 0,
|
||||
origin: 'rewind' as const,
|
||||
originSeq: 4,
|
||||
nodes: [retained, current],
|
||||
},
|
||||
],
|
||||
requests: [request(2, 1), request(4, 2)],
|
||||
callSchemas: new Map(),
|
||||
},
|
||||
openState: 'open' as const,
|
||||
hasMore: false,
|
||||
partial: null,
|
||||
runningCalls: [] as ConversationSnapshot['runningCalls'],
|
||||
codeDispatches: new Map(),
|
||||
})
|
||||
|
||||
const view = render(
|
||||
<TrajectoryView
|
||||
{...standaloneProps([])}
|
||||
useSession={bindSnapshotSelector(store) as unknown as UseSession<ConversationSnapshot>}
|
||||
loadAllHistory={vi.fn(() => Promise.resolve())}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByText('abandoned response')).toBeNull()
|
||||
expect(screen.getByText('current response')).toBeTruthy()
|
||||
expect(screen.getByRole('row', { name: /Request 2, ASSISTANT/ })).toBeTruthy()
|
||||
expect(view.container.querySelectorAll('[data-request-only="true"]')).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('WaterfallView standalone branches', () => {
|
||||
it('empty window renders the placeholder copy', () => {
|
||||
render(createElement(WaterfallView as FC<ConvViewProps>,
|
||||
|
||||
Reference in New Issue
Block a user