// 新建任务弹框:任务名 / 工作目录(新建 sentinel 或复用已有,二级 input 联动)/ // 描述 / 智能体(skill)/ 模型 select,提交 POST /v1/tasks。 // 顶层自绑 hd-new 打开、nt-go 提交、各 input 联动;唯一对外导出 loadFolderSuggestions //(供 main enterApp 初始化顶部 filter-wd、files 复制/移动后刷新目录列表)。 import { state } from "./state.js"; import { api } from "./api.js"; import { escapeHtml } from "./format.js"; import { logout } from "./auth.js"; import { loadModels, loadTaskList, selectTask } from "./main.js"; // ───── new task ───── // wd 二级 input 默认跟随 name;用户一旦手改二级 input → 脱钩;清空二级 input 重置 flag let wdManuallyEdited = false; $("hd-new").onclick = async () => { $("nt-name").value = ""; $("nt-wd-sel").value = "__new__"; // 默认选 sentinel $("nt-wd-new").value = ""; $("nt-wd-new").style.display = ""; // sentinel 选中态 → 二级 input 可见 $("nt-desc").value = ""; $("nt-skill").value = ""; $("nt-err").textContent = ""; $("nt-wd-hint").textContent = ""; wdManuallyEdited = false; $("new-task-modal").classList.add("show"); await Promise.all([loadFolderSuggestions(), loadSkillOptions(), loadModels()]); $("nt-wd-sel").value = "__new__"; // populateFolderSelects 重渲后再保险一次 populateModelSelect(); $("nt-name").focus(); }; function populateModelSelect() { const sel = $("nt-model"); const models = state.models || []; if (models.length === 0) { sel.innerHTML = ``; return; } sel.innerHTML = models.map(m => `` ).join(""); } $("nt-cancel").onclick = () => $("new-task-modal").classList.remove("show"); $("nt-go").onclick = async () => { const name = $("nt-name").value.trim(); const sel = $("nt-wd-sel").value; // sentinel:用二级 input 值,空则 fallback name;选已有目录:直接用 value const working_dir = sel === "__new__" ? ($("nt-wd-new").value.trim() || name) : sel; const desc = $("nt-desc").value.trim(); const skill = $("nt-skill").value; const model_profile = $("nt-model").value; $("nt-err").textContent = ""; if (!name) { $("nt-err").textContent = "任务名为必填项"; return; } try { const t = await api("POST", "/v1/tasks", { name, working_dir, description: desc, skill, model_profile }); $("new-task-modal").classList.remove("show"); await loadTaskList(); selectTask(t.task_id); } catch (e) { if (e.status === 401) { logout(); return; } $("nt-err").textContent = e.message; } }; // 工作目录:拉数据 + 灌两个 select(顶部 filter-wd 和 modal nt-wd-sel) export async function loadFolderSuggestions() { try { const data = await api("GET", "/v1/folders"); state.folders = data.folders || []; } catch (e) { state.folders = state.folders || []; } populateFolderSelects(); } // 灌 filter-wd + nt-wd-sel options;保留当前选中值 function populateFolderSelects() { const folders = state.folders || []; // 顶部 filter:第一项 "(全部目录)" sentinel const filterSel = $("filter-wd"); const filterCur = filterSel.value; const filterOpts = ['']; for (const f of folders) { const tag = f.n_tasks ? `${f.n_tasks} 个任务` : `空目录`; filterOpts.push(``); } filterSel.innerHTML = filterOpts.join(""); filterSel.value = filterCur; // 重渲后恢复选中 // modal wd:第一项 "+ 新建(跟随任务名)" sentinel(label 由 updateSentinelLabel 实时刷) const wdSel = $("nt-wd-sel"); const wdCur = wdSel.value || "__new__"; const wdOpts = [``]; for (const f of folders) { const tag = f.n_tasks ? `${f.n_tasks} 个任务` : `空目录`; wdOpts.push(``); } wdSel.innerHTML = wdOpts.join(""); wdSel.value = wdCur; updateSentinelLabel(); // 用最新的 name 刷 sentinel } // 智能体类型下拉:skill registry 服务器端静态,首次加载后缓存到 state.skills async function loadSkillOptions() { const sel = $("nt-skill"); if (!state.skills) { try { const data = await api("GET", "/v1/skills"); state.skills = data.skills || []; } catch (e) { state.skills = []; // 静默兜底,select 仍保留"(默认)"项 } } // 渲染:第一项固定为"默认"(空 value),其后逐 skill 一项 const opts = ['']; for (const s of state.skills) { const label = `${s.name}${s.description ? " — " + s.description : ""}`; opts.push(``); } sel.innerHTML = opts.join(""); sel.value = ""; // hd-new 已清空,这里幂等再保一次 } // === modal wd select + 二级 input 联动 === // select 选 "__new__" sentinel → 显示二级 input(默认值跟随 name);选已有目录 → 隐藏二级 input // wdManuallyEdited:用户改过二级 input 后置 true,name 不再覆盖;清空二级 input 重置 false function updateSentinelLabel() { // sentinel 永远是 select 第一项,labels 实时含 name 让用户一眼知会建什么 const sel = $("nt-wd-sel"); const opt = sel.options[0]; if (!opt || opt.value !== "__new__") return; const name = $("nt-name").value.trim(); opt.textContent = name ? `+ 新建「${name}」` : `+ 新建(跟随任务名)`; } function updateWdHint() { const hint = $("nt-wd-hint"); const sel = $("nt-wd-sel").value; if (sel === "__new__") { const v = $("nt-wd-new").value.trim(); const name = $("nt-name").value.trim(); const target = v || name; if (!target) { hint.textContent = ""; return; } // 用户手输的新名恰好命中已有目录 → 提示会复用而非新建 const collision = (state.folders || []).find(f => f.name === target); if (collision) { const n = collision.n_tasks || 0; hint.innerHTML = `! 已有同名目录,将复用 · ${n} 个任务`; } else { hint.innerHTML = `→ 新建目录 ${escapeHtml(target)}`; } } else { const f = (state.folders || []).find(x => x.name === sel); const n = f ? (f.n_tasks || 0) : 0; hint.innerHTML = `→ 复用已有目录 · ${n} 个任务`; } } // name 改变 → 更新 sentinel label;若未脱钩且当前是 sentinel,二级 input 跟随 name $("nt-name").addEventListener("input", () => { updateSentinelLabel(); if (!wdManuallyEdited && $("nt-wd-sel").value === "__new__") { $("nt-wd-new").value = $("nt-name").value; } updateWdHint(); }); // wd select 切换 → 切显示二级 input + 刷 hint $("nt-wd-sel").addEventListener("change", () => { const v = $("nt-wd-sel").value; if (v === "__new__") { $("nt-wd-new").style.display = ""; if (!wdManuallyEdited) $("nt-wd-new").value = $("nt-name").value; } else { $("nt-wd-new").style.display = "none"; } updateWdHint(); }); // 二级 input 改变 → 非空视为手动修改;清空重置 flag 但保持空(避免 backspace 想换名时被打断) $("nt-wd-new").addEventListener("input", () => { wdManuallyEdited = $("nt-wd-new").value.trim() !== ""; updateWdHint(); });