fix(gui): todo row keeps running/stopped execution states visible
The row rendered a status only for error, so a call cancelled before tool/result read as a completed plan update even though no todo/write occurred. Non-ok states now ride the generic row's StateDot semantics (ongoing dot while running, warning dot + 已中断 marker when interrupted); the ok badge stays for settled successful updates.
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
// row stays one line.
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import { StateDot } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { ToolRowProps } from '../contract/slots.ts'
|
||||
import { toolRowModel } from '../contract/tool-call-model.ts'
|
||||
import css from './todo-row.module.css'
|
||||
@@ -38,17 +39,22 @@ function summarize(argsRaw: string): string | null {
|
||||
: head
|
||||
}
|
||||
|
||||
/** One-line plan update row (click opens the raw args in details). */
|
||||
/** One-line plan update row (click opens the raw args in details). Non-ok
|
||||
* execution states keep the generic row's dot semantics — a cancelled call
|
||||
* wrote no todo/write, so it must not read as a completed update. */
|
||||
export function TodoRow({ toolName, block, openDetails }: ToolRowProps) {
|
||||
const model = toolRowModel(toolName, block)
|
||||
const argsRaw = ('kind' in block ? block.call?.argsRaw : block.argsRaw) ?? ''
|
||||
const summary = summarize(argsRaw) ?? model.summary
|
||||
return (
|
||||
<div className={css.row} data-sample="todo-row" onClick={openDetails}>
|
||||
<span className={css.badge} aria-hidden>☰</span>
|
||||
<div className={css.row} data-sample="todo-row" data-state={model.state} onClick={openDetails}>
|
||||
{model.state === 'ok'
|
||||
? <span className={css.badge} aria-hidden>☰</span>
|
||||
: <StateDot state={model.state === 'running' ? 'ongoing' : model.state === 'stopped' ? 'warning' : 'error'} />}
|
||||
<span className={css.title}>更新任务清单</span>
|
||||
<span className={css.summary}>{summary}</span>
|
||||
{model.state === 'error' && <span className={css.err}>failed</span>}
|
||||
{model.state === 'stopped' && <span className={css.err}>已中断</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -94,10 +94,23 @@ describe('TodoRow', () => {
|
||||
|
||||
it('omits the active clause when no item is in progress and reads running-call args', () => {
|
||||
const args = JSON.stringify({ todos: [{ content: 'x', status: 'completed' }] })
|
||||
render(<TodoRow {...rowProps({ callId: 'c1', name: 'todo_write', argsRaw: args, turn: 1, step: 1, callView: null })} />)
|
||||
render(<TodoRow {...rowProps({ callId: 'c1', name: 'todo_write', argsRaw: args, turn: 1, step: 1, time: 1_000, callView: null })} />)
|
||||
expect(screen.getByText('1/1 已完成')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('keeps the non-ok execution states visible: running dot, interrupted marker', () => {
|
||||
// A running call (no result yet) shows the ongoing dot, never the ok badge.
|
||||
const args = JSON.stringify({ todos: LIST })
|
||||
const running = render(<TodoRow {...rowProps({ callId: 'c1', name: 'todo_write', argsRaw: args, turn: 1, step: 1, time: 1_000, callView: null })} />)
|
||||
expect(running.container.querySelector('[data-state="running"]')).not.toBeNull()
|
||||
expect(running.container.querySelector('[data-state="running"] svg')).not.toBeNull()
|
||||
running.unmount()
|
||||
// A cancelled call wrote no todo/write: the row must not read as a completed update.
|
||||
const stopped = render(<TodoRow {...rowProps(resultNode(args, { isError: true, error: { name: 'Interrupted', code: 'interrupted' } }))} />)
|
||||
expect(stopped.container.querySelector('[data-state="stopped"]')).not.toBeNull()
|
||||
expect(stopped.getByText('已中断')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('falls back to the generic summary on malformed args and flags errors', () => {
|
||||
render(<TodoRow {...rowProps(resultNode('not json', { isError: true }))} />)
|
||||
expect(screen.getByText('failed')).toBeTruthy()
|
||||
|
||||
Reference in New Issue
Block a user