import { useRef, useState } from "react"; import { useConsole } from "../context"; import { Card, Bar } from "../components/ui"; import { gatherAttachments } from "../api/client"; import { chvAttachmentBlocked, chvAvailability, chvAvailabilityText, isChvExecutor } from "../api/chv"; import { EFFORTS } from "../api/types"; export default function DispatchSection() { const ctx = useConsole(); const [prompt, setPrompt] = useState(""); const [coordParts, setCoordParts] = useState(""); const [goalMode, setGoalMode] = useState(false); const [goalInvincible, setGoalInvincible] = useState(false); const [planMode, setPlanMode] = useState(false); const [executor, setExecutor] = useState("docker"); 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 (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 || goalInvincible) { setMsg("计划模式不能与 goal 模式同用"); return; } body.plan = 1; } // 无敌版 = goal 模式 + 任务不限时:勾了它就同时发 goal_mode 与 // no_time_limit(服务端已支持该字段);与普通 goal 勾选不冲突, // 无敌版优先,单独勾普通 goal 时行为不变。 if (goalInvincible) { body.goal_mode = 1; body.no_time_limit = 1; } else if (goalMode) body.goal_mode = 1; const coordRaw = coordParts.trim(); if (coordRaw) { try { body.coordinator_parts = JSON.parse(coordRaw); } catch { setMsg("协调员子任务 JSON 解析失败"); return; } if (!Array.isArray(body.coordinator_parts)) { setMsg("协调员子任务必须是数组"); return; } if (goalMode || goalInvincible || planMode) { setMsg("协调员任务不能与 goal/计划模式同用:协调员任务本身不跑模型回合,请取消 goal/计划勾选,或清空协调员 JSON"); return; } } const atts = 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); 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 ( {isChv ?
实验执行器,默认关闭:需后端已开启 CHV worker 且宿主有 KVM,否则任务无人认领、一直排队;带附件时建任务即被后端拒绝(错误文案原样显示在下方)。前端只传 executor 值,不绕过后端白名单;默认仍是 docker。
: null} {isChv ? (
{chvAvailabilityText(chvAvail)} {" "}
) : null}