/** * 对话区 —— 「对话流是**一切回环输入的唯一入口**」(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 } from './chat-bubbles.tsx'; export function TaskChat({ detail, onLocate }: { detail: TaskDetail; onLocate: (orderId: string) => void }) { const queryClient = useQueryClient(); const taskId = detail.card.id; /** 回传后一律重取任务投影 —— 卡与工单的状态由服务端说,前端不自行推演 */ 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 盯着这单推进