106 lines
3.1 KiB
TypeScript
106 lines
3.1 KiB
TypeScript
/** Root/subcall Tool composition with one keyed atomic dispatch path. */
|
|
import { memo, useMemo, type ReactNode } from 'react'
|
|
import type { ToolCallBlock } from '@deepseek-ai/dsh-client-runtime/client'
|
|
import type { ToolCallOwnerProps, ToolTreeProps } from '../contract/slots.ts'
|
|
import { GenericToolCard } from './toolviews/GenericToolCard.tsx'
|
|
import css from './ToolCallTree.module.css'
|
|
|
|
/** Resolve a Tool call's wire name from either lifecycle form. */
|
|
function callName(node: ToolCallBlock): string {
|
|
return 'kind' in node ? node.call?.name ?? '' : node.name
|
|
}
|
|
|
|
/** One atomic call dispatched through the Tool-owned keyed slot. */
|
|
const ToolCall = memo(function ToolCall({
|
|
renderSlot, callId, toolName, block, openFile, selected, cwd, inspectCall, t, children,
|
|
}: Pick<ToolTreeProps, 'renderSlot' | 'openFile' | 'cwd' | 'inspectCall' | 't'> & {
|
|
callId: string
|
|
toolName: string
|
|
block: ToolCallBlock
|
|
selected: boolean
|
|
children?: ReactNode
|
|
}) {
|
|
const owner: ToolCallOwnerProps = useMemo(() => ({
|
|
callId,
|
|
toolName,
|
|
block,
|
|
openFile,
|
|
cwd,
|
|
inspect: () => { inspectCall(callId) },
|
|
}), [callId, toolName, block, openFile, cwd, inspectCall])
|
|
return (
|
|
<div
|
|
className={css.callRow}
|
|
data-chat-anchor-key={`call:${callId}`}
|
|
data-chat-call-id={callId}
|
|
data-selected={selected || undefined}
|
|
>
|
|
{renderSlot('tool.call.toolview', owner, {
|
|
entryKey: toolName,
|
|
fallback: <GenericToolCard {...owner} t={t} />,
|
|
})}
|
|
{children}
|
|
</div>
|
|
)
|
|
})
|
|
|
|
const ToolCallBranch = memo(function ToolCallBranch({
|
|
renderSlot, block, selectedCallId, cwd, openFile, inspectCall, t,
|
|
}: Pick<ToolTreeProps, 'renderSlot' | 'selectedCallId' | 'cwd' | 'openFile' | 'inspectCall' | 't'> & {
|
|
block: ToolCallBlock
|
|
}) {
|
|
return (
|
|
<ToolCall
|
|
renderSlot={renderSlot}
|
|
callId={block.callId}
|
|
toolName={callName(block)}
|
|
block={block}
|
|
openFile={openFile}
|
|
selected={block.callId === selectedCallId}
|
|
cwd={cwd}
|
|
inspectCall={inspectCall}
|
|
t={t}
|
|
>
|
|
{block.subCalls.length > 0 ? (
|
|
<div className={css.subCalls} data-subcalls>
|
|
{block.subCalls.map(child => (
|
|
<ToolCallBranch
|
|
key={child.callId}
|
|
renderSlot={renderSlot}
|
|
block={child}
|
|
selectedCallId={selectedCallId}
|
|
cwd={cwd}
|
|
openFile={openFile}
|
|
inspectCall={inspectCall}
|
|
t={t}
|
|
/>
|
|
))}
|
|
</div>
|
|
) : null}
|
|
</ToolCall>
|
|
)
|
|
})
|
|
|
|
/**
|
|
* Render one root Tool call and its recursive children through the same
|
|
* atomic keyed dispatch.
|
|
* @param props - whole-Tool owner data and the Tool-owned child-slot share.
|
|
* @returns the Tool call tree.
|
|
*/
|
|
export function ToolCallTree({
|
|
renderSlot, node, selectedCallId, cwd, openFile, inspectCall, t,
|
|
}: ToolTreeProps) {
|
|
const block = node.data.root
|
|
return (
|
|
<ToolCallBranch
|
|
renderSlot={renderSlot}
|
|
block={block}
|
|
selectedCallId={selectedCallId}
|
|
cwd={cwd}
|
|
openFile={openFile}
|
|
inspectCall={inspectCall}
|
|
t={t}
|
|
/>
|
|
)
|
|
}
|