// 工作时间线:思考、对用户的解说、同类工具合并成一行。运行中展开;
// 终轮结束后由调用方套进「已工作 X」折叠。
import type { ReactNode } from 'react'
import { Collapsible, ToolBlock } from '@ui'
import type { Part } from '../contracts'
import { formatDuration, humanSize } from '../messageparts'
import { timelineEntries, toolGroupLabel, toolTail, type ToolGroupItem } from '../toolkind'
export function ProcessTimeline({ parts, live, idKey, renderPart }: {
parts: Part[]
live: boolean
idKey: string
renderPart: (part: Part, liveText: boolean) => ReactNode
}) {
const rows = timelineEntries(parts, live)
return (
{rows.map((row, i) => {
if (row.kind === 'thinking') {
const dur = formatDuration(row.durationMs)
const label = row.active ? '思考中' : (dur ? `思考 · 持续了 ${dur}` : '思考')
return (
)
}
if (row.kind === 'text') {
return
{renderPart({ type: 'text', text: row.text }, false)}
}
if (row.kind === 'check') {
const text = row.passed ? '终轮检查通过' : `终轮检查未通过:${row.reason}`
return
{text}
}
if (row.kind === 'toolblock') {
return
}
if (row.kind === 'tools') {
const running = row.items.some(it => it.running)
return (
{toolGroupLabel(row.toolKind, row.items.length, running)}
{row.items.map((it, j) => (
-
{it.headline}
{it.status === 'error' && 失败}
{it.summary && {it.summary}}
))}
)
}
return
{renderPart(row.part, false)}
})}
)
}
function sizeLabel(n: number): string {
if (n < 1024) return `${n} 字符`
return humanSize(n)
}
function ToolCallBlock({ item }: { item: ToolGroupItem }) {
const composing = item.phase === 'composing'
const tail = item.running ? toolTail(item.arguments, item.output) : ''
const dur = !item.running ? formatDuration(item.durationMs) : ''
const detail = composing ? undefined : (dur ? `${item.headline} · ${dur}` : item.headline)
return (
{item.arguments && {item.arguments}}
{item.output && {item.output}}
{tail &&
{tail}}
)
}