feat(web): redesign task rail — Plan/Event log polish, entity graph resize fix
- Plan/Activity panels: humanize step titles, richer icons, empty states matching Scope's illustration style, pretty-printed expandable detail - Activity log renamed to Event log; every step now expandable - Chat: middle-truncate header title, remove redundant task-list rail and header stat cluster (duplicated in the sidebar), simplify markdown styling - Fix --font-mono actually being a monospace font (was aliased to DM Sans) - Replace rotating loader-circle spinner with a smoother fading-blade Spinner - SessionGraph entity detail panel: resizable and self-clamping against its live container size (was overflowing into sibling sections), close button - Dev launch config: fetch bearer token from the running api container so `npm run dev` works against the local compose stack without a hardcoded secret in a tracked file Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ export interface ActivityEntry {
|
||||
'approval'
|
||||
description: string
|
||||
detail?: string
|
||||
args?: string
|
||||
timestamp: number
|
||||
toolName?: string
|
||||
stepSeq?: number
|
||||
@@ -19,6 +20,26 @@ export interface ActivityEntry {
|
||||
status: 'running' | 'done' | 'failed'
|
||||
}
|
||||
|
||||
// Detail text is kept full-length (not hard-truncated to a preview snippet)
|
||||
// so the expanded view has something worth pretty-printing — capped only as
|
||||
// a safety net against pathological payloads (a full fleet dump, etc).
|
||||
const DETAIL_MAX = 8000
|
||||
|
||||
function summarizeArgs(args: unknown): string | undefined {
|
||||
if (!args || typeof args !== 'object' || Array.isArray(args)) return undefined
|
||||
if (Object.keys(args).length === 0) return undefined
|
||||
try {
|
||||
return JSON.stringify(args)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function stringifyResult(result: unknown): string {
|
||||
const s = typeof result === 'string' ? result : JSON.stringify(result ?? '')
|
||||
return s.length > DETAIL_MAX ? `${s.slice(0, DETAIL_MAX)}\n… truncated` : s
|
||||
}
|
||||
|
||||
export const activityLog = derived([messages, planSteps, currentTask], ([$msgs, $steps, $task]) => {
|
||||
const entries: ActivityEntry[] = []
|
||||
const now = Date.now()
|
||||
@@ -64,6 +85,7 @@ export const activityLog = derived([messages, planSteps, currentTask], ([$msgs,
|
||||
id: t.id ?? `tool_${mi}_${entryIdx++}`,
|
||||
type: 'tool_running',
|
||||
description: label,
|
||||
args: summarizeArgs(t.args),
|
||||
timestamp: now - ($msgs.length - mi) * 1000,
|
||||
toolName: t.name,
|
||||
stepSeq: stepTag,
|
||||
@@ -77,19 +99,25 @@ export const activityLog = derived([messages, planSteps, currentTask], ([$msgs,
|
||||
if (running && t.error) {
|
||||
running.type = 'tool_error'
|
||||
running.status = 'failed'
|
||||
running.description = `${t.name}: ${t.error.slice(0, 80)}`
|
||||
running.description = `${label}: ${t.error.slice(0, 80)}`
|
||||
running.detail = t.error
|
||||
} else if (running) {
|
||||
running.type = 'tool_done'
|
||||
running.status = 'done'
|
||||
running.detail = typeof t.result === 'string'
|
||||
? t.result.slice(0, 200)
|
||||
: JSON.stringify(t.result ?? '').slice(0, 200)
|
||||
running.detail = stringifyResult(t.result)
|
||||
} else {
|
||||
// Historical/persisted tool calls arrive as one merged record (args
|
||||
// + result on the same object, see mergeToolCalls in chat.ts) rather
|
||||
// than a separate tool_use/tool_result pair — there's never a
|
||||
// "running" entry to attach to, so this branch has to build the
|
||||
// full entry itself. It used to fall back to the raw tool name
|
||||
// (e.g. "get_entity") instead of the humanized label here.
|
||||
entries.push({
|
||||
id: t.id ?? `tool_${mi}_${entryIdx++}`,
|
||||
type: t.error ? 'tool_error' : 'tool_done',
|
||||
description: t.error ? `${t.name}: ${t.error.slice(0, 80)}` : t.name,
|
||||
detail: !t.error ? (typeof t.result === 'string' ? t.result.slice(0, 200) : '') : undefined,
|
||||
description: t.error ? `${label}: ${t.error.slice(0, 80)}` : label,
|
||||
detail: t.error ? t.error : stringifyResult(t.result),
|
||||
args: summarizeArgs(t.args),
|
||||
timestamp: now - ($msgs.length - mi) * 1000,
|
||||
toolName: t.name,
|
||||
stepSeq: stepTag,
|
||||
@@ -181,6 +209,8 @@ function toolActivityLabel(t: ToolCallResult): string {
|
||||
case 'complete_task': return 'Complete task'
|
||||
case 'ping_service': return 'Check service'
|
||||
case 'ask_operator': return 'Ask operator'
|
||||
default: return t.name
|
||||
// Unmapped tool (new/uncommon) — humanize the raw name rather than
|
||||
// showing it verbatim, e.g. "revoke_execution" -> "Revoke execution".
|
||||
default: return t.name.replace(/_/g, ' ').replace(/^./, (c) => c.toUpperCase())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user