合并工作目录新建入口
This commit is contained in:
parent
03ee37e201
commit
ff3bc75fed
|
|
@ -5,6 +5,10 @@
|
||||||
> 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。
|
> 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。
|
||||||
> 工程口径的完整记录见 `PROGRESS.md` / git log。
|
> 工程口径的完整记录见 `PROGRESS.md` / git log。
|
||||||
|
|
||||||
|
## 0.60.7 — 2026-07-29
|
||||||
|
|
||||||
|
- 新对话的目录选择框现在也能直接创建工作目录:输入新名称后选择创建即可,不再需要旁边的额外按钮和第二个输入框。
|
||||||
|
|
||||||
## 0.60.6 — 2026-07-29
|
## 0.60.6 — 2026-07-29
|
||||||
|
|
||||||
- 新对话的工作目录选择改为可直接输入检索的下拉框,不再额外占用一行搜索框;同时支持键盘快速选择。
|
- 新对话的工作目录选择改为可直接输入检索的下拉框,不再额外占用一行搜索框;同时支持键盘快速选择。
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。
|
> 配合 `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
|
### 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.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.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项按环境跳过)。
|
- **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项按环境跳过)。
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
|
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
|
||||||
# 改版本只动这一行。
|
# 改版本只动这一行。
|
||||||
__version__ = "0.60.6"
|
__version__ = "0.60.7"
|
||||||
|
|
|
||||||
|
|
@ -807,9 +807,11 @@
|
||||||
background:var(--accent-soft); color:var(--accent); font-size:10px;
|
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-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-dir-create { margin-top:4px; border-top:1px solid var(--border); border-radius:0 0 6px 6px; color:var(--accent); }
|
||||||
.new-chat-new-dir.show { display:block; }
|
.new-chat-dir-create-hint {
|
||||||
.new-chat-new-dir input { width:100%; }
|
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; }
|
.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; }
|
.msg-top-sentinel, .msg-bot-sentinel { align-self: center; font-size: 12px; padding: 4px 0; opacity: 0.6; }
|
||||||
|
|
|
||||||
|
|
@ -407,7 +407,9 @@ function syncDraftAction() {
|
||||||
const note = $("new-chat-dir-note");
|
const note = $("new-chat-dir-note");
|
||||||
if (note) {
|
if (note) {
|
||||||
note.textContent = ready
|
note.textContent = ready
|
||||||
? `文件将保存到「${draftWorkingDir()}」`
|
? state.draftNewDirMode
|
||||||
|
? `将新建工作目录「${draftWorkingDir()}」`
|
||||||
|
: `文件将保存到「${draftWorkingDir()}」`
|
||||||
: "可选择已有目录,也可以新建目录。";
|
: "可选择已有目录,也可以新建目录。";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -428,9 +430,11 @@ function renderDraftFolderOptions() {
|
||||||
const visible = query
|
const visible = query
|
||||||
? folders.filter(f => f.name.toLocaleLowerCase().includes(query))
|
? folders.filter(f => f.name.toLocaleLowerCase().includes(query))
|
||||||
: folders;
|
: folders;
|
||||||
|
const exact = query
|
||||||
|
? folders.some(f => f.name.toLocaleLowerCase() === query)
|
||||||
|
: false;
|
||||||
const selected = state.draftNewDirMode ? "" : draftWorkingDir();
|
const selected = state.draftNewDirMode ? "" : draftWorkingDir();
|
||||||
list.innerHTML = visible.length
|
const existingHtml = visible.map((f, i) => {
|
||||||
? visible.map((f, i) => {
|
|
||||||
const tag = f.n_tasks ? `${f.n_tasks} 个任务` : "空目录";
|
const tag = f.n_tasks ? `${f.n_tasks} 个任务` : "空目录";
|
||||||
const recentTag = f.name === recent
|
const recentTag = f.name === recent
|
||||||
? `<span class="new-chat-dir-recent">最近使用</span>`
|
? `<span class="new-chat-dir-recent">最近使用</span>`
|
||||||
|
|
@ -443,14 +447,30 @@ function renderDraftFolderOptions() {
|
||||||
<span>${escapeHtml(f.name)}${recentTag}</span>
|
<span>${escapeHtml(f.name)}${recentTag}</span>
|
||||||
<small>${tag}</small>
|
<small>${tag}</small>
|
||||||
</button>`;
|
</button>`;
|
||||||
}).join("")
|
}).join("");
|
||||||
: `<div class="new-chat-dir-empty">${query ? "没有匹配的目录" : "暂无已有目录"}</div>`;
|
const createHtml = query && !exact
|
||||||
if (input.dataset.searching !== "1") input.value = selected;
|
? `<button type="button" id="new-chat-dir-create"
|
||||||
|
class="new-chat-dir-option new-chat-dir-create" role="option"
|
||||||
|
aria-selected="false" data-create="${escapeHtml(input.value.trim())}">
|
||||||
|
<span>创建工作目录「${escapeHtml(input.value.trim())}」</span>
|
||||||
|
</button>`
|
||||||
|
: `<div class="new-chat-dir-create-hint">输入新名称可创建工作目录</div>`;
|
||||||
|
const emptyHtml = !visible.length && !query
|
||||||
|
? `<div class="new-chat-dir-empty">暂无已有目录</div>`
|
||||||
|
: "";
|
||||||
|
list.innerHTML = existingHtml + emptyHtml + createHtml;
|
||||||
|
if (input.dataset.searching !== "1") input.value = draftWorkingDir();
|
||||||
_draftFolderActiveIndex = -1;
|
_draftFolderActiveIndex = -1;
|
||||||
input.setAttribute("aria-activedescendant", "");
|
input.setAttribute("aria-activedescendant", "");
|
||||||
list.querySelectorAll(".new-chat-dir-option").forEach(option => {
|
list.querySelectorAll(".new-chat-dir-option").forEach(option => {
|
||||||
option.onmousedown = e => e.preventDefault();
|
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));
|
input.setAttribute("aria-expanded", String(open));
|
||||||
if (!open) {
|
if (!open) {
|
||||||
input.dataset.searching = "";
|
input.dataset.searching = "";
|
||||||
input.value = state.draftNewDirMode ? "" : draftWorkingDir();
|
input.value = draftWorkingDir();
|
||||||
_draftFolderActiveIndex = -1;
|
_draftFolderActiveIndex = -1;
|
||||||
input.setAttribute("aria-activedescendant", "");
|
input.setAttribute("aria-activedescendant", "");
|
||||||
}
|
}
|
||||||
|
|
@ -472,8 +492,6 @@ function selectDraftFolder(name) {
|
||||||
if (!name) return;
|
if (!name) return;
|
||||||
state.draftNewDirMode = false;
|
state.draftNewDirMode = false;
|
||||||
state.draftWorkingDir = name;
|
state.draftWorkingDir = name;
|
||||||
const box = $("new-chat-new-dir");
|
|
||||||
if (box) box.classList.remove("show");
|
|
||||||
setDraftFolderOpen(false);
|
setDraftFolderOpen(false);
|
||||||
state.filesPath = name;
|
state.filesPath = name;
|
||||||
loadFiles();
|
loadFiles();
|
||||||
|
|
@ -481,6 +499,18 @@ function selectDraftFolder(name) {
|
||||||
renderDraftFolderOptions();
|
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) {
|
function moveDraftFolderActive(delta) {
|
||||||
const input = $("new-chat-dir-picker");
|
const input = $("new-chat-dir-picker");
|
||||||
const options = [...document.querySelectorAll("#new-chat-dir-options .new-chat-dir-option")];
|
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 combo = $("new-chat-dir-combobox");
|
||||||
const picker = $("new-chat-dir-picker");
|
const picker = $("new-chat-dir-picker");
|
||||||
const toggle = $("new-chat-dir-toggle");
|
const toggle = $("new-chat-dir-toggle");
|
||||||
const add = $("new-chat-dir-add");
|
if (!combo || !picker || !toggle) return;
|
||||||
const box = $("new-chat-new-dir");
|
|
||||||
const input = $("new-chat-dir-input");
|
|
||||||
if (!combo || !picker || !toggle || !add || !box || !input) return;
|
|
||||||
renderDraftFolderOptions();
|
renderDraftFolderOptions();
|
||||||
box.classList.toggle("show", state.draftNewDirMode);
|
|
||||||
if (state.draftNewDirMode) input.value = draftWorkingDir();
|
|
||||||
picker.onfocus = () => {
|
picker.onfocus = () => {
|
||||||
picker.dataset.searching = "";
|
picker.dataset.searching = "";
|
||||||
setDraftFolderOpen(true);
|
setDraftFolderOpen(true);
|
||||||
|
|
@ -524,11 +549,16 @@ function bindDraftControls() {
|
||||||
} else if (e.key === "Enter" && combo.classList.contains("open")) {
|
} else if (e.key === "Enter" && combo.classList.contains("open")) {
|
||||||
const options = [...document.querySelectorAll("#new-chat-dir-options .new-chat-dir-option")];
|
const options = [...document.querySelectorAll("#new-chat-dir-options .new-chat-dir-option")];
|
||||||
const active = options[_draftFolderActiveIndex];
|
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);
|
const target = active || exact || (options.length === 1 ? options[0] : null);
|
||||||
if (target) {
|
if (target) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
selectDraftFolder(target.dataset.name || "");
|
if (target.dataset.create) {
|
||||||
|
selectDraftNewFolder(target.dataset.create);
|
||||||
|
} else {
|
||||||
|
selectDraftFolder(target.dataset.name || "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (e.key === "Escape") {
|
} else if (e.key === "Escape") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -547,19 +577,6 @@ function bindDraftControls() {
|
||||||
renderDraftFolderOptions();
|
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() {
|
export async function showNewConversationDraft() {
|
||||||
|
|
@ -589,17 +606,14 @@ export async function showNewConversationDraft() {
|
||||||
<input type="text" id="new-chat-dir-picker" role="combobox"
|
<input type="text" id="new-chat-dir-picker" role="combobox"
|
||||||
aria-autocomplete="list" aria-expanded="false"
|
aria-autocomplete="list" aria-expanded="false"
|
||||||
aria-controls="new-chat-dir-options"
|
aria-controls="new-chat-dir-options"
|
||||||
placeholder="选择或搜索已有目录" autocomplete="off" />
|
placeholder="选择已有目录或输入新名称" autocomplete="off"
|
||||||
|
maxlength="255" />
|
||||||
<button type="button" id="new-chat-dir-toggle" tabindex="-1"
|
<button type="button" id="new-chat-dir-toggle" tabindex="-1"
|
||||||
aria-label="展开工作目录"></button>
|
aria-label="展开工作目录"></button>
|
||||||
</div>
|
</div>
|
||||||
<div id="new-chat-dir-options" class="new-chat-dir-options"
|
<div id="new-chat-dir-options" class="new-chat-dir-options"
|
||||||
role="listbox"></div>
|
role="listbox"></div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" id="new-chat-dir-add">+ 新建工作目录</button>
|
|
||||||
</div>
|
|
||||||
<div id="new-chat-new-dir" class="new-chat-new-dir">
|
|
||||||
<input id="new-chat-dir-input" placeholder="输入新目录名" maxlength="255" />
|
|
||||||
</div>
|
</div>
|
||||||
<div id="new-chat-dir-note" class="dir-note"></div>
|
<div id="new-chat-dir-note" class="dir-note"></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue