feat(ui): refine trajectory inspection details
This commit is contained in:
@@ -29,7 +29,8 @@ export type {
|
|||||||
} from './contract/store.ts'
|
} from './contract/store.ts'
|
||||||
export type {
|
export type {
|
||||||
AssistantBlock, AssistantMessageNode, AssistantTiming, CodeSubCall, ComposerPhase, ContextMessageNode,
|
AssistantBlock, AssistantMessageNode, AssistantTiming, CodeSubCall, ComposerPhase, ContextMessageNode,
|
||||||
ConversationContext, ConversationContextOriginKind, ConversationNode, ConversationSnapshot, QueuedMessage, RunningToolCall,
|
ConversationContext, ConversationContextOriginKind, ConversationNode, ConversationPromptSnapshot,
|
||||||
|
ConversationSnapshot, QueuedMessage, RunningToolCall,
|
||||||
SteeringMessageNode, ToolResultNode, UnknownSurfaceNode, UserMessageNode,
|
SteeringMessageNode, ToolResultNode, UnknownSurfaceNode, UserMessageNode,
|
||||||
} from './sessions/conversation.ts'
|
} from './sessions/conversation.ts'
|
||||||
export { PendingWait } from './sessions/pending.ts'
|
export { PendingWait } from './sessions/pending.ts'
|
||||||
|
|||||||
@@ -209,6 +209,14 @@ export type ComposerPhase = 'blank' | 'engaging' | 'active'
|
|||||||
/** Operation that started a new append-only model context. */
|
/** Operation that started a new append-only model context. */
|
||||||
export type ConversationContextOriginKind = 'compaction' | 'rewind' | 'rewrite'
|
export type ConversationContextOriginKind = 'compaction' | 'rewind' | 'rewrite'
|
||||||
|
|
||||||
|
/** Latest complete model request header in force within one context generation. */
|
||||||
|
export interface ConversationPromptSnapshot {
|
||||||
|
/** Rendered system prompt text; empty when the request had no system prompt. */
|
||||||
|
system: string
|
||||||
|
/** Complete tool catalog sent with the request, including tools that were never called. */
|
||||||
|
tools: readonly ToolSchema[]
|
||||||
|
}
|
||||||
|
|
||||||
/** One immutable model-context generation reconstructed from surface replacements. */
|
/** One immutable model-context generation reconstructed from surface replacements. */
|
||||||
export interface ConversationContext {
|
export interface ConversationContext {
|
||||||
/** Zero-based generation within the session; stable across later appends. */
|
/** Zero-based generation within the session; stable across later appends. */
|
||||||
@@ -221,6 +229,8 @@ export interface ConversationContext {
|
|||||||
originSeq?: number
|
originSeq?: number
|
||||||
/** Unix epoch ms of the replacement that created this generation. */
|
/** Unix epoch ms of the replacement that created this generation. */
|
||||||
createdAt?: number
|
createdAt?: number
|
||||||
|
/** Latest request header observed in this generation, inherited until a later header replaces it. */
|
||||||
|
prompt?: ConversationPromptSnapshot
|
||||||
/** Final frozen nodes for historical generations, or current folded nodes for the tail. */
|
/** Final frozen nodes for historical generations, or current folded nodes for the tail. */
|
||||||
nodes: readonly ConversationNode[]
|
nodes: readonly ConversationNode[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import type { ToolCallView, ToolEventView, ToolResultView } from '@deepseek-ai/dsh-client-connection/client'
|
import type { ToolCallView, ToolEventView, ToolResultView } from '@deepseek-ai/dsh-client-connection/client'
|
||||||
import type {
|
import type {
|
||||||
AssistantTiming, ConversationContext, ConversationContextOriginKind, ConversationNode,
|
AssistantTiming, ConversationContext, ConversationContextOriginKind, ConversationNode,
|
||||||
|
ConversationPromptSnapshot,
|
||||||
} from './conversation.ts'
|
} from './conversation.ts'
|
||||||
import { toAssistantBlocks } from './conversation.ts'
|
import { toAssistantBlocks } from './conversation.ts'
|
||||||
|
|
||||||
@@ -111,9 +112,12 @@ export class FoldAdapter {
|
|||||||
* reference-stability contract (§A.9.4) starts here. */
|
* reference-stability contract (§A.9.4) starts here. */
|
||||||
private rev = 0
|
private rev = 0
|
||||||
private nodesResult: { rev: number; value: { nodes: ConversationNode[]; degraded: boolean } } | null = null
|
private nodesResult: { rev: number; value: { nodes: ConversationNode[]; degraded: boolean } } | null = null
|
||||||
/** Revision of the model-visible surface only; log-only chunks do not rebuild context generations. */
|
/** Revision of context structure or its request header; unrelated log-only events do not rebuild contexts. */
|
||||||
private surfaceRev = 0
|
private contextRev = 0
|
||||||
private contextsResult: { rev: number; value: readonly ConversationContext[] } | null = null
|
private contextsResult: { rev: number; value: readonly ConversationContext[] } | null = null
|
||||||
|
private contextGeneration = 0
|
||||||
|
private activePrompt: ConversationPromptSnapshot | undefined
|
||||||
|
private promptsByContext = new Map<number, ConversationPromptSnapshot>()
|
||||||
|
|
||||||
/** In-window tool/call index (Session uses it for runningCalls and result-card backfill). */
|
/** In-window tool/call index (Session uses it for runningCalls and result-card backfill). */
|
||||||
get callIndex(): ReadonlyMap<string, CallIndexEntry> {
|
get callIndex(): ReadonlyMap<string, CallIndexEntry> {
|
||||||
@@ -129,7 +133,7 @@ export class FoldAdapter {
|
|||||||
*/
|
*/
|
||||||
reset(events: readonly SessionEvent[], baseSeq: number, views?: readonly (ToolEventView | undefined)[]): void {
|
reset(events: readonly SessionEvent[], baseSeq: number, views?: readonly (ToolEventView | undefined)[]): void {
|
||||||
this.rev++
|
this.rev++
|
||||||
this.surfaceRev++
|
this.contextRev++
|
||||||
this.baseSeq = baseSeq
|
this.baseSeq = baseSeq
|
||||||
this.padded = []
|
this.padded = []
|
||||||
for (let i = 0; i < baseSeq; i++) this.padded.push(paddingEvent(i))
|
for (let i = 0; i < baseSeq; i++) this.padded.push(paddingEvent(i))
|
||||||
@@ -139,10 +143,16 @@ export class FoldAdapter {
|
|||||||
this.degraded = false
|
this.degraded = false
|
||||||
this.callIdx = new Map()
|
this.callIdx = new Map()
|
||||||
this.resultViews.clear()
|
this.resultViews.clear()
|
||||||
|
this.contextGeneration = 0
|
||||||
|
this.activePrompt = undefined
|
||||||
|
this.promptsByContext = new Map()
|
||||||
for (let i = 0; i < events.length; i++) {
|
for (let i = 0; i < events.length; i++) {
|
||||||
const event = events[i]
|
const event = events[i]
|
||||||
/* v8 ignore next -- dense-array guard: i stays within events.length, so the undefined arm needs a sparse array no caller builds. */
|
/* v8 ignore next -- dense-array guard: i stays within events.length, so the undefined arm needs a sparse array no caller builds. */
|
||||||
if (event !== undefined) this.indexCall(event, views?.[i])
|
if (event !== undefined) {
|
||||||
|
this.indexCall(event, views?.[i])
|
||||||
|
this.indexContextPrompt(event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,9 +164,10 @@ export class FoldAdapter {
|
|||||||
*/
|
*/
|
||||||
append(event: SessionEvent, view?: ToolEventView): void {
|
append(event: SessionEvent, view?: ToolEventView): void {
|
||||||
this.rev++
|
this.rev++
|
||||||
if (isSurfaceEvent(event)) this.surfaceRev++
|
if (isSurfaceEvent(event) || event.type === 'request/header') this.contextRev++
|
||||||
this.padded.push(event)
|
this.padded.push(event)
|
||||||
this.indexCall(event, view)
|
this.indexCall(event, view)
|
||||||
|
this.indexContextPrompt(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -207,13 +218,17 @@ export class FoldAdapter {
|
|||||||
* @returns Frozen historical contexts followed by the current context.
|
* @returns Frozen historical contexts followed by the current context.
|
||||||
*/
|
*/
|
||||||
contexts(): readonly ConversationContext[] {
|
contexts(): readonly ConversationContext[] {
|
||||||
if (this.contextsResult !== null && this.contextsResult.rev === this.surfaceRev) {
|
if (this.contextsResult !== null && this.contextsResult.rev === this.contextRev) {
|
||||||
return this.contextsResult.value
|
return this.contextsResult.value
|
||||||
}
|
}
|
||||||
const current = this.nodes()
|
const current = this.nodes()
|
||||||
if (current.degraded) {
|
if (current.degraded) {
|
||||||
const value: readonly ConversationContext[] = [{ id: 0, nodes: current.nodes }]
|
const value: readonly ConversationContext[] = [{
|
||||||
this.contextsResult = { rev: this.surfaceRev, value }
|
id: 0,
|
||||||
|
...(this.activePrompt === undefined ? {} : { prompt: this.activePrompt }),
|
||||||
|
nodes: current.nodes,
|
||||||
|
}]
|
||||||
|
this.contextsResult = { rev: this.contextRev, value }
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
const value = this.surface.contexts.map((context): ConversationContext => {
|
const value = this.surface.contexts.map((context): ConversationContext => {
|
||||||
@@ -222,7 +237,14 @@ export class FoldAdapter {
|
|||||||
const node = this.materialize(seq)
|
const node = this.materialize(seq)
|
||||||
if (node !== undefined) nodes.push(node)
|
if (node !== undefined) nodes.push(node)
|
||||||
}
|
}
|
||||||
if (context.origin === undefined) return { id: context.generation, nodes }
|
const prompt = this.promptsByContext.get(context.generation)
|
||||||
|
if (context.origin === undefined) {
|
||||||
|
return {
|
||||||
|
id: context.generation,
|
||||||
|
...(prompt === undefined ? {} : { prompt }),
|
||||||
|
nodes,
|
||||||
|
}
|
||||||
|
}
|
||||||
const originEvent = this.padded[context.origin.seq]
|
const originEvent = this.padded[context.origin.seq]
|
||||||
return {
|
return {
|
||||||
id: context.generation,
|
id: context.generation,
|
||||||
@@ -230,10 +252,11 @@ export class FoldAdapter {
|
|||||||
origin: contextOriginKind(originEvent),
|
origin: contextOriginKind(originEvent),
|
||||||
originSeq: context.origin.seq,
|
originSeq: context.origin.seq,
|
||||||
...(originEvent === undefined ? {} : { createdAt: originEvent.time }),
|
...(originEvent === undefined ? {} : { createdAt: originEvent.time }),
|
||||||
|
...(prompt === undefined ? {} : { prompt }),
|
||||||
nodes,
|
nodes,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.contextsResult = { rev: this.surfaceRev, value }
|
this.contextsResult = { rev: this.contextRev, value }
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,6 +326,21 @@ export class FoldAdapter {
|
|||||||
// No backfill into already-materialized tool-result nodes for this callId
|
// No backfill into already-materialized tool-result nodes for this callId
|
||||||
// (window order puts the call before its result; cannot happen on the normal path).
|
// (window order puts the call before its result; cannot happen on the normal path).
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private indexContextPrompt(event: SessionEvent): void {
|
||||||
|
if (isSurfaceEvent(event) && event.surfaceOp !== 'append') {
|
||||||
|
this.contextGeneration++
|
||||||
|
if (this.activePrompt !== undefined) {
|
||||||
|
this.promptsByContext.set(this.contextGeneration, this.activePrompt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event.type !== 'request/header') return
|
||||||
|
this.activePrompt = {
|
||||||
|
system: event.data.header.system ?? '',
|
||||||
|
tools: event.data.header.tools ?? [],
|
||||||
|
}
|
||||||
|
this.promptsByContext.set(this.contextGeneration, this.activePrompt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function contextOriginKind(event: SessionEvent | undefined): ConversationContextOriginKind {
|
function contextOriginKind(event: SessionEvent | undefined): ConversationContextOriginKind {
|
||||||
@@ -310,10 +348,8 @@ function contextOriginKind(event: SessionEvent | undefined): ConversationContext
|
|||||||
const source = event.data.source
|
const source = event.data.source
|
||||||
if (
|
if (
|
||||||
typeof source === 'object'
|
typeof source === 'object'
|
||||||
&& source !== null
|
|
||||||
&& 'kind' in source
|
&& 'kind' in source
|
||||||
&& 'plugin' in source
|
&& 'plugin' in source
|
||||||
&& source.kind === 'plugin'
|
|
||||||
) {
|
) {
|
||||||
if (source.plugin === 'compact') return 'compaction'
|
if (source.plugin === 'compact') return 'compaction'
|
||||||
if (source.plugin === 'rewind') return 'rewind'
|
if (source.plugin === 'rewind') return 'rewind'
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
color: var(--dsw-alias-label-primary);
|
color: var(--dsw-alias-label-primary);
|
||||||
background: var(--dsw-alias-bg-layer-1);
|
background: var(--dsw-alias-bg-layer-1);
|
||||||
font: 12px/16px var(--ds-font-family-code);
|
font: 12px/16px var(--ds-font-family-code);
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior-x: contain;
|
||||||
|
overscroll-behavior-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(body[data-ds-dark-theme]) .root {
|
:global(body[data-ds-dark-theme]) .root {
|
||||||
@@ -78,8 +79,8 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: -100vw;
|
right: 0;
|
||||||
left: -100vw;
|
left: 0;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
background: var(--json-tree-hover);
|
background: var(--json-tree-hover);
|
||||||
content: '';
|
content: '';
|
||||||
@@ -134,7 +135,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.copyAnchor {
|
.copyAnchor {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,14 +235,29 @@ export function JsonTree({
|
|||||||
const rootRect = root.getBoundingClientRect()
|
const rootRect = root.getBoundingClientRect()
|
||||||
const rowRect = row.getBoundingClientRect()
|
const rowRect = row.getBoundingClientRect()
|
||||||
setCopyTarget({
|
setCopyTarget({
|
||||||
left: root.scrollLeft + root.clientWidth - 26,
|
left: rootRect.left + root.clientWidth - 26,
|
||||||
path: target.path,
|
path: target.path,
|
||||||
side: rowRect.top - rootRect.top > root.clientHeight / 2 ? 'top' : 'bottom',
|
side: rowRect.top - rootRect.top > root.clientHeight / 2 ? 'top' : 'bottom',
|
||||||
top: root.scrollTop + rowRect.top - rootRect.top,
|
top: rowRect.top,
|
||||||
value: target.value,
|
value: target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const reposition = () => {
|
||||||
|
const row = activeRowRef.current
|
||||||
|
if (row === undefined) return
|
||||||
|
const resolved = resolveRow(data, row, expandTopLevel)
|
||||||
|
if (resolved !== undefined) positionCopyButton(row, resolved)
|
||||||
|
}
|
||||||
|
window.addEventListener('scroll', reposition, true)
|
||||||
|
window.addEventListener('resize', reposition)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scroll', reposition, true)
|
||||||
|
window.removeEventListener('resize', reposition)
|
||||||
|
}
|
||||||
|
}, [data, expandTopLevel])
|
||||||
|
|
||||||
const clearCopyTarget = () => {
|
const clearCopyTarget = () => {
|
||||||
setActiveRow(undefined)
|
setActiveRow(undefined)
|
||||||
setCopyTarget(undefined)
|
setCopyTarget(undefined)
|
||||||
|
|||||||
@@ -52,13 +52,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tagContext {
|
.tagContext {
|
||||||
color: var(--dsw-alias-label-secondary);
|
color: color-mix(
|
||||||
background: var(--dsw-alias-bg-layer-3);
|
in srgb,
|
||||||
|
var(--dsw-alias-state-success-primary) 68%,
|
||||||
|
var(--dsw-alias-label-secondary)
|
||||||
|
);
|
||||||
|
background: var(--dsw-alias-state-success-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagMessage {
|
.tagMessage {
|
||||||
color: var(--dsw-alias-brand-primary-new-colorprimary-new-color);
|
color: color-mix(
|
||||||
background: var(--dsw-specific-bubble);
|
in srgb,
|
||||||
|
var(--dsw-alias-brand-primary-new-colorprimary-new-color) 60%,
|
||||||
|
var(--dsw-alias-state-error-secondary)
|
||||||
|
);
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--dsw-alias-brand-primary-new-colorprimary-new-color) 55%,
|
||||||
|
var(--dsw-alias-state-error-secondary)
|
||||||
|
) 15%,
|
||||||
|
var(--dsw-alias-bg-layer-1)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagTool {
|
.tagTool {
|
||||||
@@ -69,8 +85,16 @@
|
|||||||
/* run_code sub-dispatch cells: the business tint plus an indent so the
|
/* run_code sub-dispatch cells: the business tint plus an indent so the
|
||||||
nesting under the parent Tool cell reads at a glance. */
|
nesting under the parent Tool cell reads at a glance. */
|
||||||
.tagSubtool {
|
.tagSubtool {
|
||||||
color: var(--dsw-alias-state-business-primary);
|
color: color-mix(
|
||||||
background: var(--dsw-alias-state-business-tertiary);
|
in srgb,
|
||||||
|
var(--dsw-alias-state-warn-label) 62%,
|
||||||
|
var(--dsw-alias-label-tertiary)
|
||||||
|
);
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--dsw-alias-state-warn-tertiary) 58%,
|
||||||
|
var(--dsw-alias-bg-layer-1)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.root[data-kind='subtool'] {
|
.root[data-kind='subtool'] {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
transition: background-color 120ms var(--ds-ease-in-out);
|
transition: background-color 120ms var(--ds-ease-in-out);
|
||||||
}
|
}
|
||||||
|
|
||||||
.table tbody tr:not([data-collapsed-summary]):hover {
|
.table tbody tr:not([data-collapsed-summary]):not([data-selected='true']):hover {
|
||||||
background: var(--dsw-alias-interactive-bg-hover);
|
background: var(--dsw-alias-interactive-bg-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,28 +230,53 @@
|
|||||||
background: var(--dsw-alias-state-business-tertiary);
|
background: var(--dsw-alias-state-business-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.context {
|
.systemNeutral {
|
||||||
border-color: var(--dsw-alias-border-l2);
|
|
||||||
color: var(--dsw-alias-label-secondary);
|
|
||||||
background: var(--dsw-alias-bg-layer-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
border-color: var(--dsw-alias-border-l2);
|
|
||||||
color: var(--dsw-alias-label-secondary);
|
color: var(--dsw-alias-label-secondary);
|
||||||
background: var(--dsw-alias-bg-module-platform);
|
background: var(--dsw-alias-bg-module-platform);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool {
|
.contextGreen {
|
||||||
border-color: var(--dsw-alias-border-l2);
|
color: color-mix(
|
||||||
color: var(--dsw-alias-label-primary);
|
in srgb,
|
||||||
background: var(--dsw-alias-bg-layer-1);
|
var(--dsw-alias-state-success-primary) 68%,
|
||||||
|
var(--dsw-alias-label-secondary)
|
||||||
|
);
|
||||||
|
background: var(--dsw-alias-state-success-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtool {
|
.assistantVioletBright {
|
||||||
border-color: var(--dsw-alias-border-l2);
|
color: color-mix(
|
||||||
color: var(--dsw-alias-state-business-primary);
|
in srgb,
|
||||||
background: transparent;
|
var(--dsw-alias-brand-primary-new-colorprimary-new-color) 60%,
|
||||||
|
var(--dsw-alias-state-error-secondary)
|
||||||
|
);
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--dsw-alias-brand-primary-new-colorprimary-new-color) 55%,
|
||||||
|
var(--dsw-alias-state-error-secondary)
|
||||||
|
) 15%,
|
||||||
|
var(--dsw-alias-bg-layer-1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolAmber {
|
||||||
|
color: var(--dsw-alias-state-warn-label);
|
||||||
|
background: var(--dsw-alias-state-warn-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtoolAmber {
|
||||||
|
color: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--dsw-alias-state-warn-label) 62%,
|
||||||
|
var(--dsw-alias-label-tertiary)
|
||||||
|
);
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--dsw-alias-state-warn-tertiary) 58%,
|
||||||
|
var(--dsw-alias-bg-layer-1)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.contentText {
|
.contentText {
|
||||||
@@ -326,6 +351,18 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toolCallNameTypeface {
|
||||||
|
color: var(--dsw-alias-label-primary);
|
||||||
|
font: 400 13px/18px Menlo, Consolas, 'Liberation Mono', 'PingFang SC',
|
||||||
|
'Microsoft YaHei';
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolCallPayload {
|
||||||
|
margin-left: 7px;
|
||||||
|
color: var(--dsw-alias-label-secondary);
|
||||||
|
font: 400 12px/18px var(--ds-font-family-code);
|
||||||
|
}
|
||||||
|
|
||||||
.table tbody tr[data-kind='tool'] .contentText,
|
.table tbody tr[data-kind='tool'] .contentText,
|
||||||
.table tbody tr[data-kind='subtool'] .contentText,
|
.table tbody tr[data-kind='subtool'] .contentText,
|
||||||
.table tbody tr[data-kind='tool'] .resultPreview,
|
.table tbody tr[data-kind='tool'] .resultPreview,
|
||||||
@@ -475,7 +512,7 @@
|
|||||||
height: 34px;
|
height: 34px;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
gap: 2px;
|
gap: 1px;
|
||||||
border-bottom: 1px solid var(--dsw-alias-border-l2);
|
border-bottom: 1px solid var(--dsw-alias-border-l2);
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
@@ -534,27 +571,32 @@
|
|||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overviewHierarchyLink {
|
.overviewHierarchyNavLink {
|
||||||
all: unset;
|
all: unset;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--dsw-alias-label-primary);
|
color: var(--dsw-alias-label-secondary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
|
gap: 1px;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
text-decoration-line: underline;
|
|
||||||
text-decoration-color: currentColor;
|
|
||||||
text-decoration-style: solid;
|
|
||||||
text-decoration-thickness: 1px;
|
|
||||||
text-underline-offset: 2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.overviewHierarchyLink:hover {
|
.overviewHierarchyNavLink:hover {
|
||||||
color: var(--dsw-alias-label-primary);
|
color: var(--dsw-alias-label-primary);
|
||||||
text-decoration-thickness: 2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.overviewHierarchyLink:focus-visible {
|
.overviewHierarchyJumpIconTight {
|
||||||
|
flex: none;
|
||||||
|
color: var(--dsw-alias-label-caption);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overviewHierarchyNavLink:hover .overviewHierarchyJumpIconTight,
|
||||||
|
.overviewHierarchyNavLink:focus-visible .overviewHierarchyJumpIconTight {
|
||||||
|
color: var(--dsw-alias-label-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overviewHierarchyNavLink:focus-visible {
|
||||||
color: var(--dsw-alias-label-primary);
|
color: var(--dsw-alias-label-primary);
|
||||||
outline: 1px solid var(--dsw-alias-state-business-primary);
|
outline: 1px solid var(--dsw-alias-state-business-primary);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
@@ -582,6 +624,16 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tokenEquation {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tokenOperator {
|
||||||
|
margin: 0 2px;
|
||||||
|
color: var(--dsw-alias-label-caption);
|
||||||
|
}
|
||||||
|
|
||||||
.overviewSections {
|
.overviewSections {
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
}
|
}
|
||||||
@@ -651,8 +703,8 @@
|
|||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overviewPreview .jsonPreview > :last-child,
|
.overviewPreview .jsonPreview > :first-child,
|
||||||
.overviewPreview .schemaTree > :last-child {
|
.overviewPreview .schemaTree > :first-child {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1022,6 +1074,91 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.systemPrompt {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolCatalog {
|
||||||
|
min-height: 100%;
|
||||||
|
padding: 8px 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolCatalogItem {
|
||||||
|
border-bottom: 1px solid var(--dsw-alias-border-l1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolCatalogSummary {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 12px 12px max-content minmax(0, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 30px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
gap: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolCatalogSummary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolCatalogSummary:hover {
|
||||||
|
background: var(--dsw-alias-interactive-bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolCatalogSummary:focus-visible {
|
||||||
|
background: var(--dsw-alias-interactive-bg-hover);
|
||||||
|
outline: 1px solid var(--dsw-alias-state-business-primary);
|
||||||
|
outline-offset: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolCatalogChevron,
|
||||||
|
.toolCatalogIcon {
|
||||||
|
color: var(--dsw-alias-label-caption);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolCatalogChevron {
|
||||||
|
transition: transform 100ms var(--ds-ease-in-out);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolCatalogItem[open] .toolCatalogChevron {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolCatalogName {
|
||||||
|
color: var(--dsw-alias-label-primary);
|
||||||
|
font: 500 12px/18px var(--ds-font-family-code);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolCatalogDescription {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--dsw-alias-label-tertiary);
|
||||||
|
font: var(--dsw-font-xs-13);
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolCatalogDefinition {
|
||||||
|
padding: 0 0 8px 29px;
|
||||||
|
background: var(--dsw-alias-bg-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolCatalogFullDescription {
|
||||||
|
margin: 0;
|
||||||
|
padding: 7px 14px 4px 0;
|
||||||
|
color: var(--dsw-alias-label-secondary);
|
||||||
|
font: var(--dsw-font-xs-13);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolCatalogTree {
|
||||||
|
margin-left: -14px;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.resultBlocks {
|
.resultBlocks {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { CSSProperties, ReactNode } from 'react'
|
|||||||
import {
|
import {
|
||||||
extractMarkdownPlainText, IconChevronRightOutline14, JsonTree, MarkdownText,
|
extractMarkdownPlainText, IconChevronRightOutline14, JsonTree, MarkdownText,
|
||||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||||
|
import type { ConversationPromptSnapshot } from '@deepseek-ai/dsh-client-runtime/client'
|
||||||
import type {
|
import type {
|
||||||
AssistantMetricDetail, TrajectoryCellKind, TrajectoryCellProps, TrajectorySourceBlock,
|
AssistantMetricDetail, TrajectoryCellKind, TrajectoryCellProps, TrajectorySourceBlock,
|
||||||
} from './trajectory-record.ts'
|
} from './trajectory-record.ts'
|
||||||
@@ -31,7 +32,16 @@ interface TableRecord {
|
|||||||
collapsedSummaryKind?: 'turn' | 'assistant'
|
collapsedSummaryKind?: 'turn' | 'assistant'
|
||||||
}
|
}
|
||||||
|
|
||||||
type DetailTab = 'overview' | 'rendered' | 'source' | 'input' | 'output' | 'schema' | 'timing'
|
type DetailTab =
|
||||||
|
| 'system-prompt'
|
||||||
|
| 'tools'
|
||||||
|
| 'overview'
|
||||||
|
| 'rendered'
|
||||||
|
| 'source'
|
||||||
|
| 'input'
|
||||||
|
| 'output'
|
||||||
|
| 'schema'
|
||||||
|
| 'timing'
|
||||||
type RecordState = 'complete' | 'running' | 'error'
|
type RecordState = 'complete' | 'running' | 'error'
|
||||||
|
|
||||||
interface DetailTabItem {
|
interface DetailTabItem {
|
||||||
@@ -44,6 +54,11 @@ interface ParentRecords {
|
|||||||
tool?: TableRecord
|
tool?: TableRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ToolCallTextParts {
|
||||||
|
name: string
|
||||||
|
args?: string
|
||||||
|
}
|
||||||
|
|
||||||
interface DetailsResizeDrag {
|
interface DetailsResizeDrag {
|
||||||
pointerId: number
|
pointerId: number
|
||||||
startX: number
|
startX: number
|
||||||
@@ -61,6 +76,11 @@ const TOOL_REQUEST_MIN_WIDTH = 180
|
|||||||
const TOOL_REQUEST_MAX_WIDTH = 480
|
const TOOL_REQUEST_MAX_WIDTH = 480
|
||||||
const DEFAULT_TOOL_REQUEST_SHARE = 0.36
|
const DEFAULT_TOOL_REQUEST_SHARE = 0.36
|
||||||
const DEFAULT_TOOL_REQUEST_OFFSET = 56
|
const DEFAULT_TOOL_REQUEST_OFFSET = 56
|
||||||
|
const SYSTEM_PROMPT_INDEX = 0
|
||||||
|
const SYSTEM_PROMPT_TABS: readonly DetailTabItem[] = [
|
||||||
|
{ id: 'system-prompt', label: 'System Prompt' },
|
||||||
|
{ id: 'tools', label: 'Tools' },
|
||||||
|
]
|
||||||
|
|
||||||
type TrajectorySplitStyle = CSSProperties & {
|
type TrajectorySplitStyle = CSSProperties & {
|
||||||
'--trajectory-tool-request-width': string
|
'--trajectory-tool-request-width': string
|
||||||
@@ -156,6 +176,8 @@ function AssistantTimingPanel({ metrics }: { metrics: AssistantMetricDetail }) {
|
|||||||
|
|
||||||
/** Props for the trajectory ledger. */
|
/** Props for the trajectory ledger. */
|
||||||
export interface TrajectoryTableProps {
|
export interface TrajectoryTableProps {
|
||||||
|
/** Latest model request header in force for the selected context. */
|
||||||
|
prompt?: ConversationPromptSnapshot
|
||||||
/** Grouped records in display order. */
|
/** Grouped records in display order. */
|
||||||
turns: readonly TrajectoryTurnModel[]
|
turns: readonly TrajectoryTurnModel[]
|
||||||
/** Turn ids whose rows after the first are folded into a summary. */
|
/** Turn ids whose rows after the first are folded into a summary. */
|
||||||
@@ -326,9 +348,21 @@ function statusLabel(state: RecordState): string {
|
|||||||
return 'Completed'
|
return 'Completed'
|
||||||
}
|
}
|
||||||
|
|
||||||
function tokenSummary(cell: TrajectoryCellProps): string {
|
function tokenSummary(cell: TrajectoryCellProps): ReactNode {
|
||||||
if (cell.kind !== 'message') return '—'
|
if (cell.kind !== 'message') return '—'
|
||||||
return `${cell.input ?? '—'} / ${cell.output ?? '—'} / ${cell.think ?? '—'}`
|
if (cell.output === undefined) return '—'
|
||||||
|
if (cell.think === undefined) return String(cell.output)
|
||||||
|
return (
|
||||||
|
<span className={css.tokenEquation}>
|
||||||
|
<span title="Total output tokens">{cell.output}</span>
|
||||||
|
<span className={css.tokenOperator}>=</span>
|
||||||
|
<span title="Non-reasoning output tokens">
|
||||||
|
{Math.max(0, cell.output - cell.think)}
|
||||||
|
</span>
|
||||||
|
<span className={css.tokenOperator}>+</span>
|
||||||
|
<span title="Reasoning tokens">{cell.think}</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMarkdownRecord(record: TableRecord): boolean {
|
function isMarkdownRecord(record: TableRecord): boolean {
|
||||||
@@ -409,6 +443,19 @@ function recordDisplayText(cell: TrajectoryCellProps): string {
|
|||||||
return plainText.replace(/\s+/g, ' ').trim()
|
return plainText.replace(/\s+/g, ' ').trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toolCallTextParts(
|
||||||
|
kind: TrajectoryCellKind,
|
||||||
|
text: string,
|
||||||
|
): ToolCallTextParts | undefined {
|
||||||
|
if (kind !== 'tool' && kind !== 'subtool') return undefined
|
||||||
|
const separator = text.indexOf(' · ')
|
||||||
|
if (separator === -1) return { name: text }
|
||||||
|
return {
|
||||||
|
name: text.slice(0, separator),
|
||||||
|
args: text.slice(separator + 3),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isToolCallOnly(cell: TrajectoryCellProps): boolean {
|
function isToolCallOnly(cell: TrajectoryCellProps): boolean {
|
||||||
return cell.kind === 'message'
|
return cell.kind === 'message'
|
||||||
&& !cell.outputDetail
|
&& !cell.outputDetail
|
||||||
@@ -581,6 +628,55 @@ function AssistantToolCalls({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ToolGlyph() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={css.toolCatalogIcon}
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.8"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToolCatalog({ tools }: { tools: ConversationPromptSnapshot['tools'] }) {
|
||||||
|
if (tools.length === 0) return <p className={css.noPayload}>No tools in this request</p>
|
||||||
|
return (
|
||||||
|
<div className={css.toolCatalog}>
|
||||||
|
{tools.map((tool, index) => (
|
||||||
|
<details className={css.toolCatalogItem} key={`${tool.name}:${index}`}>
|
||||||
|
<summary className={css.toolCatalogSummary}>
|
||||||
|
<IconChevronRightOutline14 className={css.toolCatalogChevron} size={12} />
|
||||||
|
<ToolGlyph />
|
||||||
|
<span className={css.toolCatalogName}>{tool.name}</span>
|
||||||
|
<span className={css.toolCatalogDescription}>{tool.description}</span>
|
||||||
|
</summary>
|
||||||
|
<div className={css.toolCatalogDefinition}>
|
||||||
|
{tool.description !== '' && (
|
||||||
|
<p className={css.toolCatalogFullDescription}>{tool.description}</p>
|
||||||
|
)}
|
||||||
|
<JsonTree
|
||||||
|
data={tool.parameters}
|
||||||
|
label={`${tool.name} parameters JSON`}
|
||||||
|
className={css.toolCatalogTree}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function ToolOutputBlocks({
|
function ToolOutputBlocks({
|
||||||
blocks,
|
blocks,
|
||||||
preview,
|
preview,
|
||||||
@@ -879,6 +975,7 @@ function OverviewSection({
|
|||||||
* @returns The ledger and an optional local record inspector.
|
* @returns The ledger and an optional local record inspector.
|
||||||
*/
|
*/
|
||||||
export function TrajectoryTable({
|
export function TrajectoryTable({
|
||||||
|
prompt,
|
||||||
turns,
|
turns,
|
||||||
collapsedTurns,
|
collapsedTurns,
|
||||||
onToggleTurn,
|
onToggleTurn,
|
||||||
@@ -895,9 +992,17 @@ export function TrajectoryTable({
|
|||||||
const allRecords = flattenRecords(turns)
|
const allRecords = flattenRecords(turns)
|
||||||
const turnRecords = collapseTurnRecords(allRecords, collapsedTurns)
|
const turnRecords = collapseTurnRecords(allRecords, collapsedTurns)
|
||||||
const records = collapseAssistantRecords(turnRecords, collapsedAssistants)
|
const records = collapseAssistantRecords(turnRecords, collapsedAssistants)
|
||||||
|
const systemPromptPreview = prompt === undefined
|
||||||
|
? 'Request header not recorded'
|
||||||
|
: prompt.system === ''
|
||||||
|
? 'No system prompt'
|
||||||
|
: extractMarkdownPlainText(prompt.system).replace(/\s+/g, ' ').trim()
|
||||||
|
const promptSelected = selectedIndex === SYSTEM_PROMPT_INDEX
|
||||||
const selected = allRecords.find(record => record.cell.index === selectedIndex)
|
const selected = allRecords.find(record => record.cell.index === selectedIndex)
|
||||||
const selectedState = selected === undefined ? undefined : stateOf(selected)
|
const selectedState = selected === undefined ? undefined : stateOf(selected)
|
||||||
const selectedTabs = selected === undefined ? [] : detailTabs(selected)
|
const selectedTabs = promptSelected
|
||||||
|
? SYSTEM_PROMPT_TABS
|
||||||
|
: selected === undefined ? [] : detailTabs(selected)
|
||||||
const selectedParents: ParentRecords = selected === undefined
|
const selectedParents: ParentRecords = selected === undefined
|
||||||
? {}
|
? {}
|
||||||
: parentRecords(allRecords, selected)
|
: parentRecords(allRecords, selected)
|
||||||
@@ -924,6 +1029,11 @@ export function TrajectoryTable({
|
|||||||
setActiveTab(recent ?? 'overview')
|
setActiveTab(recent ?? 'overview')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectSystemPrompt = () => {
|
||||||
|
setSelectedIndex(SYSTEM_PROMPT_INDEX)
|
||||||
|
activateTab('system-prompt')
|
||||||
|
}
|
||||||
|
|
||||||
const openRecordSummary = (target: TableRecord) => {
|
const openRecordSummary = (target: TableRecord) => {
|
||||||
const targetAt = allRecords.findIndex(record => record.cell.index === target.cell.index)
|
const targetAt = allRecords.findIndex(record => record.cell.index === target.cell.index)
|
||||||
if (collapsedTurns.has(target.turn)) onToggleTurn(target.turn)
|
if (collapsedTurns.has(target.turn)) onToggleTurn(target.turn)
|
||||||
@@ -954,8 +1064,42 @@ export function TrajectoryTable({
|
|||||||
<col className={css.contentColumn} />
|
<col className={css.contentColumn} />
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<tr
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label="System prompt and tool catalog"
|
||||||
|
aria-selected={promptSelected}
|
||||||
|
data-kind="system"
|
||||||
|
data-selected={promptSelected || undefined}
|
||||||
|
onClick={selectSystemPrompt}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key !== 'Enter' && event.key !== ' ') return
|
||||||
|
event.preventDefault()
|
||||||
|
selectSystemPrompt()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<td className={css.event}>
|
||||||
|
{promptSelected && <span className={css.selectionRail} aria-hidden="true" />}
|
||||||
|
<div className={css.eventInner}>
|
||||||
|
<span className={`${css.kindSlot} ${css.kindSlotLeft}`}>
|
||||||
|
<span className={`${css.kindTag} ${css.systemNeutral}`}>SYSTEM</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className={css.content}>
|
||||||
|
<span
|
||||||
|
className={css.contentText}
|
||||||
|
title={prompt?.system || systemPromptPreview}
|
||||||
|
>
|
||||||
|
{systemPromptPreview}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{records.map((record) => {
|
{records.map((record) => {
|
||||||
const displayText = recordDisplayText(record.cell)
|
const displayText = recordDisplayText(record.cell)
|
||||||
|
const toolCallText = toolCallTextParts(record.cell.kind, displayText)
|
||||||
|
const listDisplayText = toolCallText === undefined
|
||||||
|
? displayText
|
||||||
|
: [toolCallText.name, toolCallText.args].filter(Boolean).join(' ')
|
||||||
const isCollapsedSummary = record.collapsedSummary !== undefined
|
const isCollapsedSummary = record.collapsedSummary !== undefined
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
@@ -963,7 +1107,7 @@ export function TrajectoryTable({
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label={isCollapsedSummary
|
aria-label={isCollapsedSummary
|
||||||
? `Collapsed ${record.collapsedSummaryKind} summary, ${record.collapsedSummary}`
|
? `Collapsed ${record.collapsedSummaryKind} summary, ${record.collapsedSummary}`
|
||||||
: `${KIND_LABEL[record.cell.kind]}, ${displayText || 'no content'}`}
|
: `${KIND_LABEL[record.cell.kind]}, ${listDisplayText || 'no content'}`}
|
||||||
aria-selected={!isCollapsedSummary && selectedIndex === record.cell.index}
|
aria-selected={!isCollapsedSummary && selectedIndex === record.cell.index}
|
||||||
data-kind={record.cell.kind}
|
data-kind={record.cell.kind}
|
||||||
data-group-start={record.groupStart || undefined}
|
data-group-start={record.groupStart || undefined}
|
||||||
@@ -1028,7 +1172,18 @@ export function TrajectoryTable({
|
|||||||
: `${css.kindSlot} ${css.kindSlotRight}`
|
: `${css.kindSlot} ${css.kindSlotRight}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span className={`${css.kindTag} ${css[record.cell.kind]}`}>
|
<span className={`${css.kindTag} ${
|
||||||
|
record.cell.kind === 'context'
|
||||||
|
? css.contextGreen
|
||||||
|
: record.cell.kind === 'tool'
|
||||||
|
? css.toolAmber
|
||||||
|
: record.cell.kind === 'message'
|
||||||
|
? css.assistantVioletBright
|
||||||
|
: record.cell.kind === 'subtool'
|
||||||
|
? css.subtoolAmber
|
||||||
|
: css[record.cell.kind]
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{KIND_LABEL[record.cell.kind]}
|
{KIND_LABEL[record.cell.kind]}
|
||||||
</span>
|
</span>
|
||||||
{record.turnStart && record.cell.opensTurn && (
|
{record.turnStart && record.cell.opensTurn && (
|
||||||
@@ -1056,11 +1211,26 @@ export function TrajectoryTable({
|
|||||||
<span
|
<span
|
||||||
className={record.cell.result === undefined ? css.contentText : css.resultPreview}
|
className={record.cell.result === undefined ? css.contentText : css.resultPreview}
|
||||||
title={record.cell.result === undefined
|
title={record.cell.result === undefined
|
||||||
? displayText
|
? listDisplayText
|
||||||
: `${displayText} → ${record.cell.result}`}
|
: `${listDisplayText} → ${record.cell.result}`}
|
||||||
>
|
>
|
||||||
<span className={record.cell.result === undefined ? undefined : css.resultRequest}>
|
<span className={record.cell.result === undefined ? undefined : css.resultRequest}>
|
||||||
{isToolCallOnly(record.cell) ? null : displayText || '—'}
|
{isToolCallOnly(record.cell)
|
||||||
|
? null
|
||||||
|
: toolCallText === undefined
|
||||||
|
? listDisplayText || '—'
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
<span className={css.toolCallNameTypeface}>
|
||||||
|
{toolCallText.name || '—'}
|
||||||
|
</span>
|
||||||
|
{toolCallText.args !== undefined && (
|
||||||
|
<span className={css.toolCallPayload}>
|
||||||
|
{toolCallText.args}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
{record.cell.result !== undefined && (
|
{record.cell.result !== undefined && (
|
||||||
<span className={record.cell.isError ? `${css.inlineResult} ${css.error}` : css.inlineResult}>
|
<span className={record.cell.isError ? `${css.inlineResult} ${css.error}` : css.inlineResult}>
|
||||||
@@ -1077,7 +1247,7 @@ export function TrajectoryTable({
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{selected !== undefined && selectedState !== undefined && (
|
{(promptSelected || (selected !== undefined && selectedState !== undefined)) && (
|
||||||
<aside
|
<aside
|
||||||
className={css.details}
|
className={css.details}
|
||||||
aria-label="Event details"
|
aria-label="Event details"
|
||||||
@@ -1159,10 +1329,29 @@ export function TrajectoryTable({
|
|||||||
/>
|
/>
|
||||||
<div className={css.detailsHeader}>
|
<div className={css.detailsHeader}>
|
||||||
<div className={css.detailsTitle}>
|
<div className={css.detailsTitle}>
|
||||||
<span className={`${css.kindTag} ${css[selected.cell.kind]}`}>
|
{promptSelected
|
||||||
{KIND_LABEL[selected.cell.kind]}
|
? (
|
||||||
</span>
|
<span className={`${css.kindTag} ${css.systemNeutral}`}>SYSTEM</span>
|
||||||
<span className={css.detailsLocation}>{`Turn ${selected.turn} · ${selected.group}`}</span>
|
)
|
||||||
|
: selected !== undefined && (
|
||||||
|
<>
|
||||||
|
<span className={`${css.kindTag} ${
|
||||||
|
selected.cell.kind === 'context'
|
||||||
|
? css.contextGreen
|
||||||
|
: selected.cell.kind === 'tool'
|
||||||
|
? css.toolAmber
|
||||||
|
: selected.cell.kind === 'message'
|
||||||
|
? css.assistantVioletBright
|
||||||
|
: selected.cell.kind === 'subtool'
|
||||||
|
? css.subtoolAmber
|
||||||
|
: css[selected.cell.kind]
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{KIND_LABEL[selected.cell.kind]}
|
||||||
|
</span>
|
||||||
|
<span className={css.detailsLocation}>{`Turn ${selected.turn} · ${selected.group}`}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1195,7 +1384,23 @@ export function TrajectoryTable({
|
|||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
aria-labelledby={`trajectory-detail-${activeTab}`}
|
aria-labelledby={`trajectory-detail-${activeTab}`}
|
||||||
>
|
>
|
||||||
{activeTab === 'overview' && (
|
{promptSelected && activeTab === 'system-prompt' && (
|
||||||
|
prompt === undefined
|
||||||
|
? <p className={css.noPayload}>Request header not recorded</p>
|
||||||
|
: prompt.system === ''
|
||||||
|
? <p className={css.noPayload}>No system prompt in this request</p>
|
||||||
|
: (
|
||||||
|
<div className={`${css.markdownPayload} ${css.systemPrompt}`}>
|
||||||
|
<MarkdownText text={prompt.system} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
{promptSelected && activeTab === 'tools' && (
|
||||||
|
prompt === undefined
|
||||||
|
? <p className={css.noPayload}>Request header not recorded</p>
|
||||||
|
: <ToolCatalog tools={prompt.tools} />
|
||||||
|
)}
|
||||||
|
{!promptSelected && selected !== undefined && selectedState !== undefined && activeTab === 'overview' && (
|
||||||
<>
|
<>
|
||||||
<dl className={css.overview}>
|
<dl className={css.overview}>
|
||||||
{hasSelectedParents && (
|
{hasSelectedParents && (
|
||||||
@@ -1205,19 +1410,27 @@ export function TrajectoryTable({
|
|||||||
{selectedParents.message !== undefined && (
|
{selectedParents.message !== undefined && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={css.overviewHierarchyLink}
|
className={css.overviewHierarchyNavLink}
|
||||||
onClick={() => { openRecordSummary(selectedParents.message!) }}
|
onClick={() => { openRecordSummary(selectedParents.message!) }}
|
||||||
>
|
>
|
||||||
Parent Message
|
<span>Parent Message</span>
|
||||||
|
<IconChevronRightOutline14
|
||||||
|
className={css.overviewHierarchyJumpIconTight}
|
||||||
|
size={11}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{selectedParents.tool !== undefined && (
|
{selectedParents.tool !== undefined && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={css.overviewHierarchyLink}
|
className={css.overviewHierarchyNavLink}
|
||||||
onClick={() => { openRecordSummary(selectedParents.tool!) }}
|
onClick={() => { openRecordSummary(selectedParents.tool!) }}
|
||||||
>
|
>
|
||||||
Parent Tool Call
|
<span>Parent Tool Call</span>
|
||||||
|
<IconChevronRightOutline14
|
||||||
|
className={css.overviewHierarchyJumpIconTight}
|
||||||
|
size={11}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</dd>
|
</dd>
|
||||||
@@ -1271,7 +1484,7 @@ export function TrajectoryTable({
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{activeTab === 'rendered' && (
|
{!promptSelected && selected !== undefined && activeTab === 'rendered' && (
|
||||||
<MarkdownRecordContent
|
<MarkdownRecordContent
|
||||||
record={selected}
|
record={selected}
|
||||||
rendered
|
rendered
|
||||||
@@ -1280,7 +1493,7 @@ export function TrajectoryTable({
|
|||||||
onOpenCall={openCallSummary}
|
onOpenCall={openCallSummary}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeTab === 'source' && (
|
{!promptSelected && selected !== undefined && activeTab === 'source' && (
|
||||||
<MarkdownRecordContent
|
<MarkdownRecordContent
|
||||||
record={selected}
|
record={selected}
|
||||||
rendered={false}
|
rendered={false}
|
||||||
@@ -1289,16 +1502,16 @@ export function TrajectoryTable({
|
|||||||
onOpenCall={openCallSummary}
|
onOpenCall={openCallSummary}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeTab === 'input' && (
|
{!promptSelected && selected !== undefined && activeTab === 'input' && (
|
||||||
<RecordPayload record={selected} direction="input" />
|
<RecordPayload record={selected} direction="input" />
|
||||||
)}
|
)}
|
||||||
{activeTab === 'output' && (
|
{!promptSelected && selected !== undefined && activeTab === 'output' && (
|
||||||
<RecordPayload record={selected} direction="output" />
|
<RecordPayload record={selected} direction="output" />
|
||||||
)}
|
)}
|
||||||
{activeTab === 'schema' && (
|
{!promptSelected && selected !== undefined && activeTab === 'schema' && (
|
||||||
<RecordSchema record={selected} />
|
<RecordSchema record={selected} />
|
||||||
)}
|
)}
|
||||||
{activeTab === 'timing' && (
|
{!promptSelected && selected !== undefined && activeTab === 'timing' && (
|
||||||
<RecordTiming record={selected} />
|
<RecordTiming record={selected} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -161,17 +161,15 @@ export function TrajectoryView({ useSession }: ConvViewProps) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className={css.ledger}>
|
<div className={css.ledger}>
|
||||||
{turns.length === 0 && <p className={css.empty}>No trajectory events</p>}
|
<TrajectoryTable
|
||||||
{turns.length > 0 && (
|
key={selectedContext.id}
|
||||||
<TrajectoryTable
|
{...selectedContext.prompt === undefined ? {} : { prompt: selectedContext.prompt }}
|
||||||
key={selectedContext.id}
|
turns={turns}
|
||||||
turns={turns}
|
collapsedTurns={collapsedTurns}
|
||||||
collapsedTurns={collapsedTurns}
|
onToggleTurn={toggleTurn}
|
||||||
onToggleTurn={toggleTurn}
|
collapsedAssistants={collapsedAssistants}
|
||||||
collapsedAssistants={collapsedAssistants}
|
onToggleAssistant={toggleAssistant}
|
||||||
onToggleAssistant={toggleAssistant}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user