/** * 任务深链(rwfx / 任务FX)的「任务工作区」:一个任务挂多个对话(1:N)。 * * 左侧 = 该任务下的对话列表(按 metadata.taskId 过滤)+「新建对话」; * 右下角 = 常驻浮层红色按钮(编辑 / 下一步 / 清除任务记录),absolute 定位,时刻可见、不被内容挤走; * 右侧 = 渲染选中的对话(AgentChatPage,新建对话顶部带目的树闸门面板)。 * * - 进入 chats 索引:有历史 → 跳最新;无历史 → 跳 new(=新建,走闸门)。 * - 新建对话 = 直接跳入:navigate 到 chats/new;切换/新建时 AgentChatPage 会停掉老对话的服务端 run。 * - 清除任务记录:仅调 delete 接口(聊天对话保留)。 * - 编辑 / 下一步:window.open 配置里的 B 系统地址(?id=taskId)。 * 见 [[task-purpose]] / [[task-deeplink-config]]。 */ import { useEffect, useMemo, useState } from "react"; import { Outlet, useLocation, useNavigate, useParams } from "react-router-dom"; import { ArrowRight, ExternalLink, Loader2, MessageSquare, PlusSquare, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { buildTreemapEditUrl, buildTreemapNextUrl, deleteTaskPurpose, readTaskContext, } from "@/core/auth/task-purpose"; import { fetchTaskDeeplinkConfig } from "@/core/auth/task-deeplink-config"; import { useTaskThreads } from "@/core/threads/use-task-threads"; import { titleOfThread } from "@/core/threads/utils"; import { desensitize } from "@/lib/desensitize"; import { cn } from "@/lib/utils"; import AgentChatPage from "@/pages/AgentChatPage"; import { ChatRuntime } from "@/pages/WorkspaceRoutes"; function useChatsBase(taskBase: string): { taskId: string; chatsBase: string } { const { agent_id } = useParams<{ agent_id: string }>(); const taskId = readTaskContext()?.taskId ?? ""; return { taskId, chatsBase: `${taskBase}/agent/${agent_id}/chats` }; } /** 从路由路径里解析当前打开的会话 id(布局组件读不到子路由的 :thread_id,故从 path 取)。 */ function parseThreadId(pathname: string, chatsBase: string): string | null { if (!pathname.startsWith(chatsBase)) return null; const seg = pathname.slice(chatsBase.length).replace(/^\/+/, "").split("/")[0]; return seg && seg !== "new" ? seg : null; } /** 左侧任务对话列表 +「新建对话」。 */ function TaskConversationsList({ taskId, chatsBase }: { taskId: string; chatsBase: string }) { const { pathname } = useLocation(); const navigate = useNavigate(); const { threads, isLoading } = useTaskThreads(taskId); const currentThreadId = useMemo(() => parseThreadId(pathname, chatsBase), [pathname, chatsBase]); return (
{isLoading ? (
加载中…
) : threads.length === 0 ? (
暂无对话
) : ( threads.map((thread) => ( )) )}
); } /** * 右下角常驻红色操作浮层:编辑 / 下一步 / 清除任务记录。 * absolute 定位在工作区右侧面板内,z-40,时刻可见、不被聊天内容挤走。 */ function TaskFloatingActions({ taskId }: { taskId: string }) { const [clearOpen, setClearOpen] = useState(false); const [clearing, setClearing] = useState(false); const openExternal = (url: string, label: string) => { if (url) window.open(url, "_blank", "noopener,noreferrer"); else toast.error(`未配置${label}地址`); }; const handleClear = async () => { setClearing(true); try { const r = await deleteTaskPurpose(taskId); if (r.success) toast.success("已清除任务记录"); else toast.error(r.message || "清除失败"); } catch { toast.error("清除失败,请稍后再试"); } finally { setClearing(false); setClearOpen(false); } }; return ( <>
!clearing && setClearOpen(o)}> 清除任务记录 将清除该任务在 B 系统中的目的策划数据(聊天对话保留),是否确认? ); } /** 任务工作区布局:左列表 + 右 Outlet + 右下角常驻红色操作浮层。 */ export function TaskFxWorkspace({ taskBase }: { taskBase: string }) { const { taskId, chatsBase } = useChatsBase(taskBase); // 预拉配置,保证「编辑/下一步」的同步 URL 构造能读到缓存。 useEffect(() => { void fetchTaskDeeplinkConfig(); }, []); return (
); } /** chats 索引:有历史对话 → 跳最新;否则 → 跳 new(走闸门)。 */ export function TaskFxIndexResolver({ taskBase }: { taskBase: string }) { const { taskId, chatsBase } = useChatsBase(taskBase); const navigate = useNavigate(); const { isLoading, hasThreads, latestThreadId } = useTaskThreads(taskId); useEffect(() => { if (isLoading) return; if (hasThreads && latestThreadId) { navigate(`${chatsBase}/${latestThreadId}`, { replace: true }); } else { navigate(`${chatsBase}/new`, { replace: true }); } }, [isLoading, hasThreads, latestThreadId, chatsBase, navigate]); return (
); } /** 右侧对话宿主:AgentChatPage(taskGate 开启 → 新建对话顶部展示目的树闸门面板 + 老对话停止)。 */ export function TaskFxChat({ taskBase }: { taskBase: string }) { const { agent_id } = useParams<{ agent_id: string }>(); return ( ); }