Files
deepseek-harness/packages/client/ui-conversation/src/client/chat/ChatNodeSeat.tsx
T
Yichen Jiang c06f9041fc Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input
# Conflicts:
#	.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml
#	docs/config-catalog.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.md
#	docs/core-data-structures/core.zh.md
#	docs/core-data-structures/llm-streaming.i18n.yaml
#	docs/core-data-structures/llm-streaming.md
#	docs/core-data-structures/llm-streaming.zh.md
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	docs/subsystems/attachment.i18n.yaml
#	docs/subsystems/attachment.md
#	docs/subsystems/attachment.zh.md
#	docs/subsystems/core.i18n.yaml
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl
#	packages/README.i18n.yaml
#	packages/README.md
#	packages/README.zh.md
#	packages/client/runtime/package.json
#	packages/client/ui-conversation/package.json
#	packages/client/ui-conversation/src/client/chat/AssistantMarkdown.tsx
#	packages/client/ui-conversation/src/client/chat/ChatView.tsx
#	packages/client/ui-conversation/src/client/chat/MessageItem.tsx
#	packages/client/ui-conversation/src/client/index.ts
#	packages/client/ui-conversation/tests/input-bar.spec.tsx
#	packages/compact/compact-basic/README.i18n.yaml
#	packages/compact/compact-basic/README.md
#	packages/compact/compact-basic/README.zh.md
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/src/api/index.ts
#	packages/host/apiproxy/src/api/sessions.ts
#	packages/host/apiproxy/src/index.ts
#	packages/host/apiproxy/tests/api-proxy-models.spec.ts
#	packages/self-modification/tool-cordis/src/api-catalog.ts
#	pnpm-lock.yaml
#	scripts/type-equiv.manifest.json
2026-08-09 23:33:35 +08:00

62 lines
2.3 KiB
TypeScript

import { memo, useMemo } from 'react'
import { JsonBlock } from '@deepseek-ai/dsh-client-ui-primitives'
import type { ChatNodeOwnerProps, ChatViewSlotProps } from '../contract/slots.ts'
import type { ChatNode } from '../contract/chat-nodes.ts'
import css from './ChatView.module.css'
interface ChatNodeSeatProps extends ChatNodeOwnerProps {
readonly nodeKey: string
readonly useSession: ChatViewSlotProps['useSession']
readonly renderSlot: ChatViewSlotProps['renderSlot']
readonly t: ChatViewSlotProps['t']
}
type RoutedChatNodeOwner = {
[Kind in ChatNode['kind']]: ChatNodeOwnerProps & { readonly node: ChatNode<Kind> }
}[ChatNode['kind']]
/** Subscribe and dispatch one stable Context key without observing sibling Nodes. */
export const ChatNodeSeat = memo(function ChatNodeSeat({
nodeKey, selectedCallId, cwd, openFile, inspectCall, forkAt,
loadImage, fileMentions, useSession, renderSlot, t,
}: ChatNodeSeatProps) {
const node = useSession(snapshot => snapshot.chat.nodes.get(nodeKey))
const routedNode = node as ChatNode | undefined
const owner = useMemo<ChatNodeOwnerProps | null>(() => node === undefined
? null
: {
selectedCallId,
cwd,
openFile,
inspectCall,
forkAt,
loadImage,
fileMentions,
}, [node, selectedCallId, cwd, openFile, inspectCall, forkAt, loadImage, fileMentions])
if (routedNode === undefined || owner === null) return null
// Runtime dispatch owns the correlation: every Node's discriminant is the
// keyed-slot entry passed alongside that same Node. TypeScript does not
// distribute an object containing a union into a union of objects itself.
const routedOwner = { ...owner, node: routedNode } as RoutedChatNodeOwner
return (
<div
className={css.flowItem}
data-chat-anchor-key={routedNode.key}
data-chat-flow-key={routedNode.key}
data-chat-flow-kind={routedNode.kind}
>
{renderSlot('conversation.chat.node', routedOwner, {
entryKey: routedNode.kind,
hookContext: nodeKey,
fallback: (
<JsonBlock
label={t('message.unknownSurface', { type: routedNode.kind })}
payload={routedNode.data}
truncatedLabel={total => t('json.truncated', { total })}
/>
),
})}
</div>
)
})