diff --git a/CHANGELOG.md b/CHANGELOG.md index 791d2a9..7fb8a3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ > 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。 > 工程口径的完整记录见 `PROGRESS.md` / git log。 +## 0.62.4 — 2026-08-06 + +- 开启新对话时可以在顶部直接选择对话模型,第一条消息会使用所选模型;工作目录候选改为浮层展示,展开时不再挤出对话区滚动条。 + ## 0.62.3 — 2026-08-06 - 生成 PPT 时不再自动切换到指定模型,会继续使用当前对话已选择的模型。 diff --git a/PROGRESS.md b/PROGRESS.md index e902dc5..729aaee 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -2,7 +2,7 @@ > 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。 -最后更新:2026-08-06(PPT 取消定向模型,bump 0.62.3) +最后更新:2026-08-06(新对话创建前选择模型,bump 0.62.4) --- @@ -23,6 +23,7 @@ ### 2026-08-06 +- **08-06 / 0.62.4 / 新对话创建前选择模型 + 目录浮层优化**:快速新对话草稿在顶部复用既有模型选择位置,只展示当前用户可用模型并在首次发送或上传附件懒创建 task 时持久化所选档位;清单失败继续由后端默认模型兜底。工作目录候选改为视口级浮层,按可用空间自动向上或向下展开,不再扩大 `chat-stream` 滚动范围;窄屏保持标题与模型同排。相关静态前端、无 DB 路由及更新日志共 34 项 unittest、JavaScript 语法与 diff 检查通过;当前环境无可用浏览器实例,未连接生产 DB。无 schema、migration、HTTP API、依赖或运行方式变化。 - **08-06 / 0.62.3 / PPT 取消定向模型**:删除 ppt skill frontmatter 的 `glm.pro52` 定向配置,load 后继续沿用 task 当前模型,显式选模型与通用 skill 定向机制保持不变。同步更新 Skill 清单与设计记录;定向模型 7 项 unittest、PPT frontmatter 解析及 diff 检查通过;无 schema、migration、HTTP API、依赖或运行方式变化,未连接生产 DB。 - **08-06 / 0.62.2 / 外部结果持久导出 + task 点目录查看开关**:外部大响应缓存统一迁入 user/task 隔离的 `.zcbot_cache`,兼容读取 0.62.1 旧目录;用户明确要求保存时新增 `external_system_result_export`,重验当前连接授权后把完整响应及 operation/参数/查询时间溯源导出到 `data/external/`,不受 24h TTL 影响。媒体 prompt/model/cost sidecar 改存 `figures|videos/.meta/`;文件面板默认隐藏所有 dotdir,当前 task 工作目录子树可用 `.*` 开关查看 `.meta/.build/.preview` 等目录,user_root 平台目录和点文件始终不展示。完整 483 项 unittest 全绿(17 skip),Node 语法、Ruff 致命规则与 diff 检查通过;当前环境无可用浏览器实例,真实点击留部署后冒烟。无 schema、migration 或依赖变化,未连接生产 DB。 - **08-06 / 0.62.1 / OpenAPI 请求体契约 + 大响应无损读取**:生产 task `506c6ad0` 暴露 Swagger 2 body `$ref` 未进入搜索结果,模型无法得知 BI dataset exec 需要 `body.query`,并在 400 只剩状态码时连续盲猜;现统一解析 Swagger 2/OpenAPI 3 请求体 schema,返回限长脱敏的上游错误详情。外部响应不再按字节切成半截 JSON,超出模型内联额度时完整写入 user/task 隔离的 24h 缓存并返回 `result_ref`,新增 JSON Pointer、分页和字段投影 reader,单响应/task/user 分别以 10/50/200 MiB 守门;完整 479 项 unittest 全绿(17 skip),外部系统专项 26 项、Ruff、隔离 mypy、Python 编译与 diff 检查通过。无 schema、migration、依赖或 HTTP API 变化,未写生产 DB。 diff --git a/core/__init__.py b/core/__init__.py index b51edc9..05df632 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,3 @@ # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # 改版本只动这一行。 -__version__ = "0.62.3" +__version__ = "0.62.4" diff --git a/tests/test_static_vendor.py b/tests/test_static_vendor.py index bc53524..4b7ec32 100644 --- a/tests/test_static_vendor.py +++ b/tests/test_static_vendor.py @@ -106,6 +106,20 @@ class StaticVendorTests(unittest.TestCase): self.assertIn("if (name) body.name = name;", newtask_js) self.assertNotIn("任务名为必填项", newtask_js) + def test_quick_new_conversation_can_choose_model(self) -> None: + chat_js = (JS_DIR / "chat.js").read_text(encoding="utf-8") + state_js = (JS_DIR / "state.js").read_text(encoding="utf-8") + html = DEV_HTML.read_text(encoding="utf-8") + + self.assertIn('id="new-chat-model-sel"', chat_js) + self.assertIn("draftModelProfile", state_js) + self.assertIn("state.draftModelProfile = defaultDraftModelProfile();", chat_js) + self.assertIn('model_profile: state.draftModelProfile || ""', chat_js) + self.assertIn('$("chat-meta").classList.add("draft-meta");', chat_js) + self.assertNotIn("new-chat-model-row", chat_js) + self.assertIn("function positionDraftFolderOptions()", chat_js) + self.assertIn("#new-chat-dir-options { position:fixed;", html) + def test_embed_initial_task_skips_root_directory_bootstrap(self) -> None: main_js = (JS_DIR / "main.js").read_text(encoding="utf-8") embed_js = (JS_DIR / "embed.js").read_text(encoding="utf-8") diff --git a/web/static/dev.html b/web/static/dev.html index 386790f..1b82ecc 100644 --- a/web/static/dev.html +++ b/web/static/dev.html @@ -787,6 +787,9 @@ max-height:260px; overflow:auto; padding:5px; border:1px solid var(--border); border-radius:8px; background:var(--panel); box-shadow:0 8px 24px rgba(0,0,0,.16); } + /* 快速新对话位于可滚动的 chat-stream 内;候选层改用视口定位,避免它扩大 + scroll overflow 或被编辑区边界裁切。自定义任务弹框仍沿用上面的局部定位。 */ + #new-chat-dir-options { position:fixed; z-index:120; top:auto; right:auto; } .new-chat-dir-combobox.open .new-chat-dir-options { display:block; } .new-chat-dir-option { display:flex; align-items:center; justify-content:space-between; gap:12px; @@ -1495,6 +1498,8 @@ .mdl-text { display: none; } .mdl-icon { display: inline; } #chat-meta select { max-width: 48vw; } + #chat-meta.draft-meta > .spacer { flex:1 1 auto; height:auto; } + #chat-meta.draft-meta .mdl-wrap { flex:0 1 auto; } /* 对话面板顶栏:藏 "对话" label / spacer,5 个按钮自然换行,文字 nowrap 防内部断行 */ #pane-mid > .pane-head { flex-wrap: wrap; gap: 6px; padding: 6px 8px; } #pane-mid > .pane-head > .label, diff --git a/web/static/js/chat.js b/web/static/js/chat.js index 18d4259..491690e 100644 --- a/web/static/js/chat.js +++ b/web/static/js/chat.js @@ -38,6 +38,7 @@ export async function loadModels() { } catch (e) { state.models = []; // 静默兜底:无模型清单时下拉不显示,不挡正常流程 } + syncDraftModelSelect(); try { const data = await api("GET", "/v1/image_models"); state.imageModels = data.models || []; @@ -410,7 +411,34 @@ function draftWorkingDir() { return (state.draftWorkingDir || "").trim(); } +function defaultDraftModelProfile() { + const models = state.models || []; + const preferred = models.find(m => m.is_default) || models[0]; + return preferred ? preferred.profile : ""; +} + +function syncDraftModelSelect() { + const sel = $("new-chat-model-sel"); + if (!sel) return; + const models = state.models || []; + if (!models.length) { + state.draftModelProfile = ""; + sel.innerHTML = ``; + sel.disabled = true; + return; + } + if (!models.some(m => m.profile === state.draftModelProfile)) { + state.draftModelProfile = defaultDraftModelProfile(); + } + sel.innerHTML = models.map(m => + `` + ).join(""); + sel.disabled = false; + sel.onchange = () => { state.draftModelProfile = sel.value || ""; }; +} + let _draftFolderActiveIndex = -1; +let _draftFolderResizeObserver = null; function syncDraftAction() { const ready = !!draftWorkingDir(); @@ -487,12 +515,37 @@ function renderDraftFolderOptions() { }); } +function positionDraftFolderOptions() { + const combo = $("new-chat-dir-combobox"); + const input = $("new-chat-dir-picker"); + const list = $("new-chat-dir-options"); + if (!combo || !input || !list || !combo.classList.contains("open")) return; + const rect = input.getBoundingClientRect(); + const gap = 5; + const viewportMargin = 8; + const below = Math.max(0, window.innerHeight - rect.bottom - gap - viewportMargin); + const above = Math.max(0, rect.top - gap - viewportMargin); + const desiredHeight = Math.min(260, list.scrollHeight || 260); + const openBelow = below >= desiredHeight || below >= above; + const available = openBelow ? below : above; + const height = Math.min(desiredHeight, available); + const top = openBelow + ? rect.bottom + gap + : Math.max(viewportMargin, rect.top - gap - height); + const left = Math.max(viewportMargin, rect.left); + list.style.left = `${left}px`; + list.style.top = `${top}px`; + list.style.width = `${Math.max(0, Math.min(rect.width, window.innerWidth - left - viewportMargin))}px`; + list.style.maxHeight = `${Math.min(260, available)}px`; +} + function setDraftFolderOpen(open) { const combo = $("new-chat-dir-combobox"); const input = $("new-chat-dir-picker"); if (!combo || !input) return; combo.classList.toggle("open", open); input.setAttribute("aria-expanded", String(open)); + if (open) positionDraftFolderOptions(); if (!open) { input.dataset.searching = ""; input.value = draftWorkingDir(); @@ -542,6 +595,11 @@ function bindDraftControls() { const picker = $("new-chat-dir-picker"); const toggle = $("new-chat-dir-toggle"); if (!combo || !picker || !toggle) return; + if (_draftFolderResizeObserver) _draftFolderResizeObserver.disconnect(); + if (typeof ResizeObserver !== "undefined") { + _draftFolderResizeObserver = new ResizeObserver(positionDraftFolderOptions); + _draftFolderResizeObserver.observe(picker); + } renderDraftFolderOptions(); picker.onfocus = () => { picker.dataset.searching = ""; @@ -592,6 +650,9 @@ function bindDraftControls() { }; } +window.addEventListener("resize", positionDraftFolderOptions); +$("chat-stream").addEventListener("scroll", positionDraftFolderOptions, { passive: true }); + export async function showNewConversationDraft() { if (state.evtSrc) { state.evtSrc.close(); state.evtSrc = null; } if (state.taskId) { _flushMediaArtifactCache(); clearAttachTray(); } @@ -601,13 +662,23 @@ export async function showNewConversationDraft() { state.loadedMessages = []; state.outline = []; state.draftNewDirMode = false; + state.draftModelProfile = defaultDraftModelProfile(); state.filesPath = ""; document.querySelectorAll(".task-row").forEach(el => el.classList.remove("active")); syncChannelCardActive(null); renderConcurrentWarning(); renderTaskProgressDock([]); renderOutlineRail(); - $("chat-meta").innerHTML = `新对话`; + $("chat-meta").classList.add("draft-meta"); + $("chat-meta").innerHTML = ` + 新对话 + + + 模型 + + `; $("chat-stream").innerHTML = `