fix(chat): keep new conversation model controls inline

This commit is contained in:
caoqianming 2026-08-06 14:14:38 +08:00
parent 689059eb8e
commit 4873dd0ae5
7 changed files with 49 additions and 22 deletions

View File

@ -5,6 +5,10 @@
> 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。 > 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。
> 工程口径的完整记录见 `PROGRESS.md` / git log。 > 工程口径的完整记录见 `PROGRESS.md` / git log。
## 0.62.5 — 2026-08-06
- 修复新对话顶部模型控件换行以及生图、生视频模型入口缺失的问题;对话模型与媒体模型现在保持在同一行。
## 0.62.4 — 2026-08-06 ## 0.62.4 — 2026-08-06
- 开启新对话时可以在顶部直接选择对话模型,第一条消息会使用所选模型;工作目录候选改为浮层展示,展开时不再挤出对话区滚动条。 - 开启新对话时可以在顶部直接选择对话模型,第一条消息会使用所选模型;工作目录候选改为浮层展示,展开时不再挤出对话区滚动条。

View File

@ -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-08-06(新对话创建前选择模型,bump 0.62.4) 最后更新:2026-08-06(新对话模型控件单行修复,bump 0.62.5)
--- ---
@ -23,6 +23,7 @@
### 2026-08-06 ### 2026-08-06
- **08-06 / 0.62.5 / 新对话模型控件单行 + 媒体入口恢复**:新对话顶部改用专用的 `draft-controls` 单行布局,不再复用历史 task meta 的强制换行 spacer模型清单异步加载完成后统一重绘草稿 meta同时恢复既有“⚙ 媒体”入口及生图/生视频选择弹层。相关静态前端、无 DB 路由及更新日志共 34 项 unittest、JavaScript 语法与 diff 检查通过;当前环境无可用浏览器实例,未连接生产 DB。无 schema、migration、HTTP API、依赖或运行方式变化。
- **08-06 / 0.62.4 / 新对话创建前选择模型 + 目录浮层优化**:快速新对话草稿在顶部复用既有模型选择位置,只展示当前用户可用模型并在首次发送或上传附件懒创建 task 时持久化所选档位;清单失败继续由后端默认模型兜底。工作目录候选改为视口级浮层,按可用空间自动向上或向下展开,不再扩大 `chat-stream` 滚动范围;窄屏保持标题与模型同排。相关静态前端、无 DB 路由及更新日志共 34 项 unittest、JavaScript 语法与 diff 检查通过;当前环境无可用浏览器实例,未连接生产 DB。无 schema、migration、HTTP API、依赖或运行方式变化。 - **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.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.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。

View File

@ -1,3 +1,3 @@
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
# 改版本只动这一行。 # 改版本只动这一行。
__version__ = "0.62.4" __version__ = "0.62.5"

View File

@ -115,7 +115,10 @@ class StaticVendorTests(unittest.TestCase):
self.assertIn("draftModelProfile", state_js) self.assertIn("draftModelProfile", state_js)
self.assertIn("state.draftModelProfile = defaultDraftModelProfile();", chat_js) self.assertIn("state.draftModelProfile = defaultDraftModelProfile();", chat_js)
self.assertIn('model_profile: state.draftModelProfile || ""', chat_js) self.assertIn('model_profile: state.draftModelProfile || ""', chat_js)
self.assertIn('$("chat-meta").classList.add("draft-meta");', chat_js) self.assertIn("function renderDraftChatMeta()", chat_js)
self.assertIn("${renderMediaModelTrigger()}", chat_js)
self.assertIn("bindMediaModelTrigger();", chat_js)
self.assertIn("#chat-meta.draft-meta { flex-wrap:nowrap; }", html)
self.assertNotIn("new-chat-model-row", chat_js) self.assertNotIn("new-chat-model-row", chat_js)
self.assertIn("function positionDraftFolderOptions()", chat_js) self.assertIn("function positionDraftFolderOptions()", chat_js)
self.assertIn("#new-chat-dir-options { position:fixed;", html) self.assertIn("#new-chat-dir-options { position:fixed;", html)

View File

@ -721,6 +721,12 @@
font-size: 12px; color: var(--muted); display: flex; gap: 12px; align-items: center; flex-wrap: wrap; } font-size: 12px; color: var(--muted); display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
#chat-meta .tid { font-family: var(--mono); color: var(--text); } #chat-meta .tid { font-family: var(--mono); color: var(--text); }
#chat-meta .spacer { flex: 1; } #chat-meta .spacer { flex: 1; }
#chat-meta.draft-meta { flex-wrap:nowrap; }
#chat-meta.draft-meta .draft-title { flex:none; font-weight:600; }
#chat-meta.draft-meta .draft-controls {
display:flex; align-items:center; justify-content:flex-end; gap:8px;
min-width:0; margin-left:auto;
}
/* 模型下拉标签:桌面文字 / 手机 emoji 二选一(swap 在 @media 640px 内) */ /* 模型下拉标签:桌面文字 / 手机 emoji 二选一(swap 在 @media 640px 内) */
.mdl-icon { display: none; } .mdl-icon { display: none; }
/* 同 wd 并发软警告 banner — 非阻塞,只提示中间产物互覆风险 */ /* 同 wd 并发软警告 banner — 非阻塞,只提示中间产物互覆风险 */
@ -1498,8 +1504,8 @@
.mdl-text { display: none; } .mdl-text { display: none; }
.mdl-icon { display: inline; } .mdl-icon { display: inline; }
#chat-meta select { max-width: 48vw; } #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; } #chat-meta.draft-meta .mdl-wrap { flex:0 1 auto; }
#chat-meta.draft-meta select { width:auto !important; max-width:48vw; }
/* 对话面板顶栏:藏 "对话" label / spacer,5 个按钮自然换行,文字 nowrap 防内部断行 */ /* 对话面板顶栏:藏 "对话" label / spacer,5 个按钮自然换行,文字 nowrap 防内部断行 */
#pane-mid > .pane-head { flex-wrap: wrap; gap: 6px; padding: 6px 8px; } #pane-mid > .pane-head { flex-wrap: wrap; gap: 6px; padding: 6px 8px; }
#pane-mid > .pane-head > .label, #pane-mid > .pane-head > .label,

View File

@ -65,6 +65,7 @@ export async function loadModels() {
// embed + task_id 场景下 selectTask 可能在 loadModels 完成前就跑完 renderChatMeta, // embed + task_id 场景下 selectTask 可能在 loadModels 完成前就跑完 renderChatMeta,
// 此时 models 为空 → 模型下拉不渲染。loadModels 收尾时如果已选中 task,补一次 chat-meta 重渲。 // 此时 models 为空 → 模型下拉不渲染。loadModels 收尾时如果已选中 task,补一次 chat-meta 重渲。
if (state.taskMeta) renderChatMeta(); if (state.taskMeta) renderChatMeta();
else if (!state.taskId && $("chat-meta").classList.contains("draft-meta")) renderDraftChatMeta();
} }
// loadTaskList:默认 reset(filters/refresh/写操作后),append=true 由 sentinel observer 触发 // loadTaskList:默认 reset(filters/refresh/写操作后),append=true 由 sentinel observer 触发
@ -437,6 +438,24 @@ function syncDraftModelSelect() {
sel.onchange = () => { state.draftModelProfile = sel.value || ""; }; sel.onchange = () => { state.draftModelProfile = sel.value || ""; };
} }
function renderDraftChatMeta() {
const meta = $("chat-meta");
meta.classList.add("draft-meta");
meta.innerHTML = `
<span class="draft-title">新对话</span>
<span class="draft-controls">
<span class="muted small mdl-wrap" style="display:inline-flex;align-items:center;gap:4px;">
<span class="mdl-text">模型</span><span class="mdl-icon" aria-hidden="true">💬</span>
<select id="new-chat-model-sel" class="small" title="选择新对话模型">
<option value="">默认模型</option>
</select>
</span>
${renderMediaModelTrigger()}
</span>`;
syncDraftModelSelect();
bindMediaModelTrigger();
}
let _draftFolderActiveIndex = -1; let _draftFolderActiveIndex = -1;
let _draftFolderResizeObserver = null; let _draftFolderResizeObserver = null;
@ -669,16 +688,7 @@ export async function showNewConversationDraft() {
renderConcurrentWarning(); renderConcurrentWarning();
renderTaskProgressDock([]); renderTaskProgressDock([]);
renderOutlineRail(); renderOutlineRail();
$("chat-meta").classList.add("draft-meta"); renderDraftChatMeta();
$("chat-meta").innerHTML = `
<span style="font-weight:600;">新对话</span>
<span class="spacer"></span>
<span class="muted small mdl-wrap" style="display:inline-flex;align-items:center;gap:4px;">
<span class="mdl-text">模型</span><span class="mdl-icon" aria-hidden="true">💬</span>
<select id="new-chat-model-sel" class="small" title="选择新对话模型">
<option value="">默认模型</option>
</select>
</span>`;
$("chat-stream").innerHTML = ` $("chat-stream").innerHTML = `
<div class="new-chat-start"> <div class="new-chat-start">
<h2>开始一段新对话</h2> <h2>开始一段新对话</h2>
@ -711,7 +721,6 @@ export async function showNewConversationDraft() {
$("btn-task-menu").disabled = true; $("btn-task-menu").disabled = true;
syncOptimizeBtn(); syncOptimizeBtn();
bindDraftControls(); bindDraftControls();
syncDraftModelSelect();
syncDraftAction(); syncDraftAction();
loadFiles(); loadFiles();
if (mqPhone.matches) setMobileView("mv-mid"); if (mqPhone.matches) setMobileView("mv-mid");
@ -861,8 +870,7 @@ function renderChatMeta() {
const sel = $("chat-model-sel"); const sel = $("chat-model-sel");
if (sel) sel.onchange = onChangeModel; if (sel) sel.onchange = onChangeModel;
// 生图/生视频 收进 ⚙ 弹层(低频),点开时再渲染 select 并接 onChange // 生图/生视频 收进 ⚙ 弹层(低频),点开时再渲染 select 并接 onChange
const mmBtn = $("media-model-btn"); bindMediaModelTrigger();
if (mmBtn) mmBtn.onclick = (e) => { e.stopPropagation(); openMediaModelPop(mmBtn); };
const active = t.status === "active"; const active = t.status === "active";
$("chat-form").style.display = active ? "flex" : "none"; $("chat-form").style.display = active ? "flex" : "none";
syncOptimizeBtn(); syncOptimizeBtn();
@ -898,6 +906,11 @@ function renderMediaModelTrigger() {
return `<button id="media-model-btn" class="small" title="生图 / 生视频 模型选择">⚙ 媒体</button>`; return `<button id="media-model-btn" class="small" title="生图 / 生视频 模型选择">⚙ 媒体</button>`;
} }
function bindMediaModelTrigger() {
const btn = $("media-model-btn");
if (btn) btn.onclick = (e) => { e.stopPropagation(); openMediaModelPop(btn); };
}
// 弹层里一行 = 标签(icon + 文字) + select。沿用原 onChange,选中值仍只进 state.* 随下条消息发。 // 弹层里一行 = 标签(icon + 文字) + select。沿用原 onChange,选中值仍只进 state.* 随下条消息发。
function mediaSelectRow(icon, label, id, list, cur, title) { function mediaSelectRow(icon, label, id, list, cur, title) {
const opts = list.map(m => const opts = list.map(m =>

View File

@ -762,11 +762,6 @@ header .who {
height: 0; height: 0;
} }
#chat-meta.draft-meta > .spacer {
flex: 1 1 auto;
height: auto;
}
#chat-meta .mdl-wrap { #chat-meta .mdl-wrap {
flex: 1 1 auto; flex: 1 1 auto;
min-width: 0; min-width: 0;
@ -776,6 +771,11 @@ header .who {
flex: 0 1 auto; flex: 0 1 auto;
} }
#chat-meta.draft-meta select {
width: auto !important;
max-width: 48vw;
}
#chat-meta select { #chat-meta select {
width: 100% !important; width: 100% !important;
max-width: none; max-width: none;