Просмотр исходного кода

round 3: disambiguate standalone compaction sections

Hypatia May 1 месяц назад
Родитель
Сommit
7a6d64981c

+ 10 - 1
packages/client/ui-trajectory/src/client/TrajectoryTable.tsx

@@ -71,6 +71,7 @@ interface ToolCallTextParts {
 
 interface SelectedRequest {
   turn: number | null
+  section: number
   number: number
   group: string
 }
@@ -1453,6 +1454,7 @@ export function TrajectoryTable({
     ? []
     : allRecords.filter(record =>
       record.turn === selectedRequest.turn
+        && record.section === selectedRequest.section
         && record.group === selectedRequest.group,
     )
   const selectedRequestAssistant = selectedRequestRecords.find(
@@ -1505,6 +1507,7 @@ export function TrajectoryTable({
     selectedRequestInfo?.cumulativeUsage ?? selectedRequestUsage
   const selectedRequestOptions = selectedRequestInfo?.requestConfig
   const activeTurn = selectedRequest === null ? selected?.turn : selectedRequest.turn
+  const activeSection = selectedRequest === null ? selected?.section : selectedRequest.section
   const selectedTabs = selectedRequest !== null
     ? REQUEST_TABS.filter(tab => tab.id !== 'options' || selectedRequestOptions !== undefined)
     : selected === undefined ? [] : detailTabs(selected)
@@ -1520,6 +1523,7 @@ export function TrajectoryTable({
     selected !== undefined && selectedAssistantRequest !== undefined
       ? {
         turn: selected.turn,
+        section: selected.section,
         number: selectedAssistantRequest,
         group: selected.group,
       }
@@ -1629,7 +1633,11 @@ export function TrajectoryTable({
                 : `Request #${request}${requestInfo?.purpose === 'compaction' ? ' · Compaction' : ''}`
               const requestSelected = request !== undefined
                 && selectedRequest?.turn === record.turn
+                && selectedRequest.section === record.section
                 && selectedRequest.number === request
+              const sectionActive = record.turn === null
+                ? activeSection === record.section
+                : activeTurn === record.turn
               return (
                 <tr
                   key={`${record.cell.index}:${record.collapsedSummaryKind ?? 'record'}`}
@@ -1715,6 +1723,7 @@ export function TrajectoryTable({
                           event.stopPropagation()
                           selectRequest({
                             turn: record.turn,
+                            section: record.section,
                             number: request,
                             group: record.group,
                           })
@@ -1734,7 +1743,7 @@ export function TrajectoryTable({
                     && !isRequestOnly
                     && record.turnStart && (
                       <span
-                        className={activeTurn === record.turn
+                        className={sectionActive
                           ? `${css.turnLabel} ${css.turnLabelActive}`
                           : css.turnLabel}
                       >

+ 64 - 0
packages/client/ui-trajectory/tests/views.spec.tsx

@@ -312,6 +312,70 @@ describe('tab switching in ConversationRoot', () => {
     expect(view.container.textContent).not.toContain('Turn null')
   })
 
+  it('activates only the selected standalone compaction section', async () => {
+    const nodes = [
+      { kind: 'user', seq: 1, time: 1_000, content: [], source: null },
+      {
+        kind: 'assistant', seq: 2, time: 2_000, turn: 1, step: 1,
+        blocks: [{ kind: 'text', text: 'before first compaction' }],
+      },
+      { kind: 'user', seq: 5, time: 5_000, content: [], source: null },
+      {
+        kind: 'assistant', seq: 6, time: 6_000, turn: 2, step: 1,
+        blocks: [{ kind: 'text', text: 'between compactions' }],
+      },
+      { kind: 'user', seq: 9, time: 9_000, content: [], source: null },
+      {
+        kind: 'assistant', seq: 10, time: 10_000, turn: 3, step: 1,
+        blocks: [{ kind: 'text', text: 'after second compaction' }],
+      },
+    ] as unknown as ConversationSnapshot['nodes']
+    const compactions: RequestView[] = [
+      {
+        purpose: 'compaction',
+        startSeq: 3,
+        turn: null,
+        step: 0,
+        startedAt: 3_000,
+        completedAt: 4_000,
+        status: 'complete',
+        summary: [{ type: 'text', text: 'first standalone summary' }],
+      },
+      {
+        purpose: 'compaction',
+        startSeq: 7,
+        turn: null,
+        step: 0,
+        startedAt: 7_000,
+        completedAt: 8_000,
+        status: 'complete',
+        summary: [{ type: 'text', text: 'second standalone summary' }],
+      },
+    ]
+    const b = await bench(historySnapshot(nodes, { requests: compactions }))
+    mount(b.slots, nodes)
+    fireEvent.click(screen.getByRole('tab', { name: 'Trajectory' }))
+
+    const firstRequest = screen.getByRole('button', { name: 'Request #2 · Compaction' })
+    const secondRequest = screen.getByRole('button', { name: 'Request #4 · Compaction' })
+    const firstSection = firstRequest.closest('tr')?.querySelector('span')
+    const secondSection = secondRequest.closest('tr')?.querySelector('span')
+    expect(firstSection?.textContent).toBe('Between turns')
+    expect(secondSection?.textContent).toBe('Between turns')
+
+    fireEvent.click(firstRequest)
+    expect(firstSection?.className).toMatch(/turnLabelActive/)
+    expect(secondSection?.className).not.toMatch(/turnLabelActive/)
+    expect(screen.getByText('Request #2')).toBeTruthy()
+    expect(screen.getByText('Compaction · Between turns')).toBeTruthy()
+
+    fireEvent.click(secondRequest)
+    expect(firstSection?.className).not.toMatch(/turnLabelActive/)
+    expect(secondSection?.className).toMatch(/turnLabelActive/)
+    expect(screen.getByText('Request #4')).toBeTruthy()
+    expect(screen.getByText('Compaction · Between turns')).toBeTruthy()
+  })
+
   it('dragging the overview focuses overlapping records without filtering the ledger', async () => {
     const b = await bench()
     mount(b.slots)