/** * 对话区 —— 「对话流是**一切回环输入的唯一入口**」(OS 4.1)。 * * 壳(`jc-tch` 题头 / 滚动区 / composer)由 `ChatStream` 自己带;这里只做取数与回传: * 说一句、答卡、答工单,回传后按键失效,画面由服务端重投影(前端零过程状态)。 * * **有内容就渲染内容,没内容才落空态** —— 空对话是这一单刚开始时的常态,不是缺陷; * 但一旦真有话,占位就变成了谎:角标说有、正文说没有,人会以为坏了。 */ import { useMutation, useQueryClient } from '@tanstack/react-query'; import type { ChatCardReply, TaskDetail, WorkOrderReply } from '@contracts'; import { replyChatCardRoute, replyWorkOrderRoute, sayRoute } from '@contracts'; import { call } from '@web-shared/typed-fetch.ts'; import { Ico } from '@web-shared/Ico.tsx'; import { EmptyState } from '@web-shared/desk/module-kit.tsx'; import { ChatStream, WorkingStrip } from './chat-bubbles.tsx'; export function TaskChat({ detail, onLocate, onOpenComms, orderValues, onField, }: { detail: TaskDetail; onLocate: (orderId: string) => void; /** 起单开场白上的「查看邮件」—— 跳到沟通记录那一屏 */ onOpenComms: () => void; /** * 人在工作区报告上改过的填报值 —— **裁决时一并带走**。 * * 验收工单「AI 已填好、可编辑」而裁决在这儿:两半分在两个区里做, * 不把缓冲带过来,回传的 `values` 就恒空 —— 人改了报告,落地时用的还是 AI 的原值。 */ orderValues?: Readonly>; /** 卡上填的值进同一个缓冲 —— 执行单的表单如今就长在对话区的卡上 */ onField: (key: string, value: string) => void; }) { const queryClient = useQueryClient(); const taskId = detail.card.id; /* * AI 正在办这单 = Loop 在自己转(runtime running)。 * 等人(awaiting-human)、等外部(awaiting-event)、已收尾都不算 —— * 那时亮「工作中」就是撒谎:环没在转,是在等。 */ const working = detail.card.terminal === undefined && detail.card.runtime === 'running'; /** 回传后一律重取任务投影 —— 卡与工单的状态由服务端说,前端不自行推演 */ const refresh = () => { void queryClient.invalidateQueries({ queryKey: ['desk', 'task', taskId] }); void queryClient.invalidateQueries({ queryKey: ['desk', 'tasks'] }); }; const say = useMutation({ mutationFn: (text: string) => call(sayRoute, { params: { taskId }, body: { text } }), onSuccess: refresh, }); const replyCard = useMutation({ mutationFn: (reply: ChatCardReply) => call(replyChatCardRoute, { body: { reply } }), onSuccess: refresh, }); const replyOrder = useMutation({ mutationFn: (reply: WorkOrderReply) => call(replyWorkOrderRoute, { body: { reply } }), onSuccess: refresh, }); if (detail.chat.length === 0) { return (
任务助理 Agent 盯着这单推进
{/* * **起手就是头像 + 气泡,不是空占位**。 * * 从前这里在 working 时同时摆两样:一张「AI 已经开工」的空态插画,底下再挂一条办事条。 * 于是这一单的开头是「一幅画 + 一条」,而第一句真消息到达时那幅画忽然消失、 * 版式整个跳一下 —— 人看到的是界面在换布景,不是 AI 在办事。 * * 现在:一开工就渲染与真消息**同形**的气泡(头像 + 署名行 + 内容), * 内容是办事条。第一句话到了,它接在同一条流里,位置不动、形状不变。 * 真正的空态只留给「还没开工也没消息」那一种。 */} {working ? (
) : ( )}