// 新建任务弹框:任务名 / 工作目录(新建 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 "./chat.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();
});