From ff3bc75fed21f9e9b94cf813f631fa9bc70d88fa Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 29 Jul 2026 14:00:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=B7=A5=E4=BD=9C=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E6=96=B0=E5=BB=BA=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++ PROGRESS.md | 3 +- core/__init__.py | 2 +- web/static/dev.html | 8 ++-- web/static/js/chat.js | 86 +++++++++++++++++++++++++------------------ 5 files changed, 62 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1269726..897480c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ > 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。 > 工程口径的完整记录见 `PROGRESS.md` / git log。 +## 0.60.7 — 2026-07-29 + +- 新对话的目录选择框现在也能直接创建工作目录:输入新名称后选择创建即可,不再需要旁边的额外按钮和第二个输入框。 + ## 0.60.6 — 2026-07-29 - 新对话的工作目录选择改为可直接输入检索的下拉框,不再额外占用一行搜索框;同时支持键盘快速选择。 diff --git a/PROGRESS.md b/PROGRESS.md index 849ebf4..fdbaef7 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -2,7 +2,7 @@ > 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。 -最后更新:2026-07-29(新对话可搜索组合框,bump 0.60.6) +最后更新:2026-07-29(组合框内直接新建目录,bump 0.60.7) --- @@ -23,6 +23,7 @@ ### 2026-07-29 +- **07-29 / 0.60.7 / 组合框内直接新建工作目录**:新对话目录 combobox 增加 allow-create 语义:输入时继续过滤已有目录,无完全同名项时在同一下拉内提供“创建工作目录「名称」”,明确选择后才进入新建状态,失焦搜索词不会误提交;删除外部新建按钮与二级输入框。纯前端改动,无 API、schema、migration 或目录绑定语义变化;全量 387 测试通过(17 项按环境跳过)。 - **07-29 / 0.60.6 / 新对话目录选择改可搜索组合框**:将上一版独立搜索框 + 原生 select 合并为单一 combobox,同一输入区完成已选目录展示、即时过滤和下拉选择,并补方向键、Enter、Esc、失焦恢复与 ARIA 状态;最近目录置顶、明确选择 working_dir 和新建目录入口均保持不变。纯前端改动,无依赖、API、schema 或 migration;全量 387 测试通过(17 项按环境跳过)。 - **07-29 / 0.60.5 / 新对话目录选择提速**:保留首发前明确选择 working_dir 的归档约束;本机最近实际使用目录在候选中自动选中、置顶并标记,目录达到 6 个时按需显示搜索框,目录较少时维持原有简洁界面;同步精简“文件保存位置”文案。纯前端改动,无 API、schema、migration 或目录绑定语义变化;相关静态资源与无 DB 路由 18 项测试通过。 - **07-29 / 0.60.4 / 工具失败信号分层 + run_python 同错保护 + wire 健康观测**:管理端保留近7天低阈值全量事实,默认把近24h活跃数据拆成跨任务系统性故障、单任务反复失败、按设计非零退出的质量门,已归零记录沉入历史区;空输出 shell 关联最近 assistant tool_call 补 `search/no match` / `dependency probe` 等稳定类别。RepeatGuard 仅将 `run_python` traceback+非零退出纳入同错 streak,普通 shell exit 1 保持原语义。新增只读 `/v1/admin/tool-wire-health`,按 model_profile+tool 对既有 `tool_salvaged/tool_malformed` 计算24h/7d抢救率,无新表、migration、索引或执行策略变化。全量387测试通过(17项按环境跳过)。 diff --git a/core/__init__.py b/core/__init__.py index 6281410..a7b730a 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,3 @@ # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # 改版本只动这一行。 -__version__ = "0.60.6" +__version__ = "0.60.7" diff --git a/web/static/dev.html b/web/static/dev.html index ebd9cea..6aae7cd 100644 --- a/web/static/dev.html +++ b/web/static/dev.html @@ -807,9 +807,11 @@ background:var(--accent-soft); color:var(--accent); font-size:10px; } .new-chat-dir-empty { padding:12px 10px; color:var(--muted); font-size:12px; text-align:center; } - .new-chat-new-dir { display:none; margin-top:10px; } - .new-chat-new-dir.show { display:block; } - .new-chat-new-dir input { width:100%; } + .new-chat-dir-create { margin-top:4px; border-top:1px solid var(--border); border-radius:0 0 6px 6px; color:var(--accent); } + .new-chat-dir-create-hint { + margin-top:4px; padding:8px 10px 4px; border-top:1px solid var(--border); + color:var(--muted); font-size:11px; + } .new-chat-start .dir-note { margin-top:10px; min-height:18px; color:var(--muted); font-size:12px; } /* 顶/底"上滑加载更早 / 下滑加载更新"占位:居中淡色小字,自身不占气泡样式 */ .msg-top-sentinel, .msg-bot-sentinel { align-self: center; font-size: 12px; padding: 4px 0; opacity: 0.6; } diff --git a/web/static/js/chat.js b/web/static/js/chat.js index 0912de8..1a3b366 100644 --- a/web/static/js/chat.js +++ b/web/static/js/chat.js @@ -407,7 +407,9 @@ function syncDraftAction() { const note = $("new-chat-dir-note"); if (note) { note.textContent = ready - ? `文件将保存到「${draftWorkingDir()}」` + ? state.draftNewDirMode + ? `将新建工作目录「${draftWorkingDir()}」` + : `文件将保存到「${draftWorkingDir()}」` : "可选择已有目录,也可以新建目录。"; } } @@ -428,9 +430,11 @@ function renderDraftFolderOptions() { const visible = query ? folders.filter(f => f.name.toLocaleLowerCase().includes(query)) : folders; + const exact = query + ? folders.some(f => f.name.toLocaleLowerCase() === query) + : false; const selected = state.draftNewDirMode ? "" : draftWorkingDir(); - list.innerHTML = visible.length - ? visible.map((f, i) => { + const existingHtml = visible.map((f, i) => { const tag = f.n_tasks ? `${f.n_tasks} 个任务` : "空目录"; const recentTag = f.name === recent ? `最近使用` @@ -443,14 +447,30 @@ function renderDraftFolderOptions() { ${escapeHtml(f.name)}${recentTag} ${tag} `; - }).join("") - : `
${query ? "没有匹配的目录" : "暂无已有目录"}
`; - if (input.dataset.searching !== "1") input.value = selected; + }).join(""); + const createHtml = query && !exact + ? `` + : `
输入新名称可创建工作目录
`; + const emptyHtml = !visible.length && !query + ? `
暂无已有目录
` + : ""; + list.innerHTML = existingHtml + emptyHtml + createHtml; + if (input.dataset.searching !== "1") input.value = draftWorkingDir(); _draftFolderActiveIndex = -1; input.setAttribute("aria-activedescendant", ""); list.querySelectorAll(".new-chat-dir-option").forEach(option => { option.onmousedown = e => e.preventDefault(); - option.onclick = () => selectDraftFolder(option.dataset.name || ""); + option.onclick = () => { + if (option.dataset.create) { + selectDraftNewFolder(option.dataset.create); + } else { + selectDraftFolder(option.dataset.name || ""); + } + }; }); } @@ -462,7 +482,7 @@ function setDraftFolderOpen(open) { input.setAttribute("aria-expanded", String(open)); if (!open) { input.dataset.searching = ""; - input.value = state.draftNewDirMode ? "" : draftWorkingDir(); + input.value = draftWorkingDir(); _draftFolderActiveIndex = -1; input.setAttribute("aria-activedescendant", ""); } @@ -472,8 +492,6 @@ function selectDraftFolder(name) { if (!name) return; state.draftNewDirMode = false; state.draftWorkingDir = name; - const box = $("new-chat-new-dir"); - if (box) box.classList.remove("show"); setDraftFolderOpen(false); state.filesPath = name; loadFiles(); @@ -481,6 +499,18 @@ function selectDraftFolder(name) { renderDraftFolderOptions(); } +function selectDraftNewFolder(name) { + const trimmed = name.trim(); + if (!trimmed) return; + state.draftNewDirMode = true; + state.draftWorkingDir = trimmed; + state.filesPath = ""; + loadFiles(); + setDraftFolderOpen(false); + syncDraftAction(); + renderDraftFolderOptions(); +} + function moveDraftFolderActive(delta) { const input = $("new-chat-dir-picker"); const options = [...document.querySelectorAll("#new-chat-dir-options .new-chat-dir-option")]; @@ -498,13 +528,8 @@ function bindDraftControls() { const combo = $("new-chat-dir-combobox"); const picker = $("new-chat-dir-picker"); const toggle = $("new-chat-dir-toggle"); - const add = $("new-chat-dir-add"); - const box = $("new-chat-new-dir"); - const input = $("new-chat-dir-input"); - if (!combo || !picker || !toggle || !add || !box || !input) return; + if (!combo || !picker || !toggle) return; renderDraftFolderOptions(); - box.classList.toggle("show", state.draftNewDirMode); - if (state.draftNewDirMode) input.value = draftWorkingDir(); picker.onfocus = () => { picker.dataset.searching = ""; setDraftFolderOpen(true); @@ -524,11 +549,16 @@ function bindDraftControls() { } else if (e.key === "Enter" && combo.classList.contains("open")) { const options = [...document.querySelectorAll("#new-chat-dir-options .new-chat-dir-option")]; const active = options[_draftFolderActiveIndex]; - const exact = options.find(o => o.dataset.name === picker.value.trim()); + const typed = picker.value.trim().toLocaleLowerCase(); + const exact = options.find(o => (o.dataset.name || "").toLocaleLowerCase() === typed); const target = active || exact || (options.length === 1 ? options[0] : null); if (target) { e.preventDefault(); - selectDraftFolder(target.dataset.name || ""); + if (target.dataset.create) { + selectDraftNewFolder(target.dataset.create); + } else { + selectDraftFolder(target.dataset.name || ""); + } } } else if (e.key === "Escape") { e.preventDefault(); @@ -547,19 +577,6 @@ function bindDraftControls() { renderDraftFolderOptions(); } }; - add.onclick = () => { - state.draftNewDirMode = true; - state.draftWorkingDir = ""; - setDraftFolderOpen(false); - box.classList.add("show"); - input.value = ""; - input.focus(); - syncDraftAction(); - }; - input.oninput = () => { - state.draftWorkingDir = input.value.trim(); - syncDraftAction(); - }; } export async function showNewConversationDraft() { @@ -589,17 +606,14 @@ export async function showNewConversationDraft() { + placeholder="选择已有目录或输入新名称" autocomplete="off" + maxlength="255" />
- - -
-
`;