import { useRef, useState } from "react"; import { useConsole } from "../context"; import { Card, Bar } from "../components/ui"; import { gatherAttachments, shortId } from "../api/client"; import { chvAttachmentBlocked, chvAvailability, chvAvailabilityText, isChvExecutor } from "../api/chv"; import { EFFORTS } from "../api/types"; export default function DispatchSection() { const ctx = useConsole(); // 旁聊模式(Muse 角色对齐 P2):列表“开旁聊”点选主聊后, // sideParentId 即父任务 id;此时隐藏环境/协调员/附件, // 执行器默认不指定(服务端旁聊默认 local),提交带 // kind="side" + parent_task_id。 const isSide = !!ctx.sideParentId; const [prompt, setPrompt] = useState(""); const [coordParts, setCoordParts] = useState(""); const [goalMode, setGoalMode] = useState(false); const [planMode, setPlanMode] = useState(false); const [executor, setExecutor] = useState("docker"); const [sideExecutor, setSideExecutor] = useState(""); const [model, setModel] = useState(""); const [modelCustom, setModelCustom] = useState(""); const [effort, setEffort] = useState(""); const [approval, setApproval] = useState(""); const [childModel, setChildModel] = useState(""); const [childModelCustom, setChildModelCustom] = useState(""); const [childEffort, setChildEffort] = useState(""); const filesRef = useRef(null); const dirRef = useRef(null); const imgRef = useRef(null); const [hasAttachments, setHasAttachments] = useState(false); const [msg, setMsg] = useState(""); const chvAvail = chvAvailability(ctx.workers, ctx.workersSummary, ctx.workersError); const isChv = isChvExecutor(executor); function refreshAttachments() { setHasAttachments( !!(filesRef.current?.files?.length || dirRef.current?.files?.length || imgRef.current?.files?.length), ); } async function submit() { setMsg(""); if (!ctx.projectId) { setMsg("先选项目"); return; } if (!prompt.trim()) { setMsg("prompt 必填"); return; } const body: any = { project_id: ctx.projectId, prompt: prompt.trim() }; if (isSide) { body.kind = "side"; body.parent_task_id = ctx.sideParentId; // 旁聊默认不指定执行器(服务端填 local);显式选了才送。 if (sideExecutor) body.executor = sideExecutor; } else { if (ctx.envSelect) body.environment_id = ctx.envSelect; if (executor) body.executor = executor; } const m = modelCustom.trim() || model; if (m) body.model = m; if (effort) body.effort = effort; const cm = childModelCustom.trim() || childModel; if (cm) body.child_model = cm; if (childEffort) body.child_effort = childEffort; if (approval) { // 选了完全访问也不再弹确认框:下拉里选 full 本身就是 // 显式选择,提交时直接送 approval;计划模式与 full // 互斥仍在下面拦截。 body.approval = approval; } if (planMode) { if (approval === "full") { setMsg("计划模式不能与完全访问同用"); return; } if (goalMode) { setMsg("计划模式不能与 goal 模式同用"); return; } body.plan = 1; } // 唯一 goal 模式 = 原无敌版:勾选即同时发 goal_mode 与 // no_time_limit(任务不限时);普通 goal 入口已取消,不再 // 单独发不带 no_time_limit 的 goal_mode。 if (goalMode) { body.goal_mode = 1; body.no_time_limit = 1; } const coordRaw = coordParts.trim(); if (coordRaw && !isSide) { try { body.coordinator_parts = JSON.parse(coordRaw); } catch { setMsg("协调员子任务 JSON 解析失败"); return; } if (!Array.isArray(body.coordinator_parts)) { setMsg("协调员子任务必须是数组"); return; } if (goalMode || planMode) { setMsg("协调员任务不能与 goal/计划模式同用:协调员任务本身不跑模型回合,请取消 goal/计划勾选,或清空协调员 JSON"); return; } } const atts = isSide ? [] : gatherAttachments(filesRef.current?.files || null, dirRef.current?.files || null, imgRef.current?.files || null); if (chvAttachmentBlocked(executor, atts.length > 0)) { setMsg("cloud-hypervisor 任务不支持附件:请先清空附件再提交(后端对带附件的 CHV 任务会 400)"); return; } try { const result = await ctx.dispatch(body, atts); if (isSide) { setMsg("旁聊已创建(无 worktree)"); ctx.setSideParentId(null); } else { setMsg(result); } setPrompt(""); setCoordParts(""); if (filesRef.current) filesRef.current.value = ""; if (dirRef.current) dirRef.current.value = ""; if (imgRef.current) imgRef.current.value = ""; setHasAttachments(false); } catch (e: any) { setMsg("提交失败:" + (e?.message || String(e))); } } return ( {isSide ? (
旁聊模式:挂在父任务 {shortId(ctx.sideParentId)} 下,不建 worktree/分支/容器;终态后结论回写父级。 {" "}
) : null} {isSide ? null : ( )} {isSide ? ( ) : ( )} {isChv ?
实验执行器,默认关闭:需后端已开启 CHV worker 且宿主有 KVM,否则任务无人认领、一直排队;带附件时建任务即被后端拒绝(错误文案原样显示在下方)。前端只传 executor 值,不绕过后端白名单;默认仍是 docker。
: null} {isChv ? (
{chvAvailabilityText(chvAvail)} {" "}
) : null}