Refine mobile conversation layout

This commit is contained in:
caoqianming 2026-07-29 15:13:51 +08:00
parent 369c74483c
commit 02b278b175
6 changed files with 94 additions and 65 deletions

View File

@ -5,6 +5,10 @@
> 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。 > 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。
> 工程口径的完整记录见 `PROGRESS.md` / git log。 > 工程口径的完整记录见 `PROGRESS.md` / git log。
## 0.60.10 — 2026-07-29
- 网页工作台恢复清爽的中性白色配色,助手回复回到左侧阅读轴;手机端进一步缩小顶部页签、操作按钮和输入区,并移除默认的文件保存位置提示,为对话正文留出更多空间。
## 0.60.9 — 2026-07-29 ## 0.60.9 — 2026-07-29
- 网页工作台改用更温和的暖白配色,任务与文件列表的分隔更加清楚;助手回复按完整轮次形成统一阅读块,手机端输入区也改为更紧凑的图标操作布局。 - 网页工作台改用更温和的暖白配色,任务与文件列表的分隔更加清楚;助手回复按完整轮次形成统一阅读块,手机端输入区也改为更紧凑的图标操作布局。

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-07-29(Web 工作台视觉复核修正,bump 0.60.9) 最后更新:2026-07-29(Web 工作台移动端空间优化,bump 0.60.10)
--- ---
@ -23,6 +23,7 @@
### 2026-07-29 ### 2026-07-29
- **07-29 / 0.60.10 / Web 工作台移动端空间优化**:根据第二轮手机实图反馈把暖白/浅米灰改回中性白色桌面助手回答、工具卡与任务进度恢复左侧阅读轴手机端压缩顶栏、Tab、任务操作区、模型区、消息间距和 composer高频操作键由 44px 收至 40px、输入框最低高度由 64px 收至 52px并隐藏空闲态“请选择文件保存位置/就绪”占位行,运行、上传与错误等有效状态提示继续按需显示。相关 Python 21 项 + Node 5 项及 JS 语法、diff 格式检查通过;无 API、schema、migration 或依赖变化。
- **07-29 / 0.60.9 / Web 工作台视觉复核修正(桌面 + 手机实图)**:据上线截图把冷蓝灰改为暖白/浅米灰;任务与文件行恢复明确分隔,助手消息收进居中 52rem 文档型回答块,工具与进度对齐同一阅读轨道;同时修掉 `task_progress`-only assistant 被过滤工具后仍留下空“助手”壳。手机端 composer 改为独立状态行 + 3 个 44px SVG 图标键 + 发送键,缩短 placeholder、加入安全区并压缩操作/模型区;录音与润色状态改只更新 label/ARIA不再覆盖 SVG DOM。相关 Python 21 项 + Node 5 项及 JS 语法、HTML/SVG 结构、CSS 括号检查通过;无 API、schema、migration 或依赖变化。 - **07-29 / 0.60.9 / Web 工作台视觉复核修正(桌面 + 手机实图)**:据上线截图把冷蓝灰改为暖白/浅米灰;任务与文件行恢复明确分隔,助手消息收进居中 52rem 文档型回答块,工具与进度对齐同一阅读轨道;同时修掉 `task_progress`-only assistant 被过滤工具后仍留下空“助手”壳。手机端 composer 改为独立状态行 + 3 个 44px SVG 图标键 + 发送键,缩短 placeholder、加入安全区并压缩操作/模型区;录音与润色状态改只更新 label/ARIA不再覆盖 SVG DOM。相关 Python 21 项 + Node 5 项及 JS 语法、HTML/SVG 结构、CSS 括号检查通过;无 API、schema、migration 或依赖变化。
- **07-29 / 0.60.8 / Web 工作台 Visual Polish v1**:新增零构建 `web/static/ui.css` 作为内部轻量视觉基础层,集中颜色、间距、圆角、阴影与 focus/disabled 等控件状态;收敛三栏层级、任务/资源导航、对话正文与工具过程块、输入区、文件栏、modal/toast 及 ≤640px 触控尺寸,不改 DOM 契约、API 或业务交互。新增 CSS 文件接入、MIME 与 `no-cache` 回归测试;相关 Python 20 项 + Node 5 项通过,浏览器连接不可用故截图级视觉验收留真实页面复核。 - **07-29 / 0.60.8 / Web 工作台 Visual Polish v1**:新增零构建 `web/static/ui.css` 作为内部轻量视觉基础层,集中颜色、间距、圆角、阴影与 focus/disabled 等控件状态;收敛三栏层级、任务/资源导航、对话正文与工具过程块、输入区、文件栏、modal/toast 及 ≤640px 触控尺寸,不改 DOM 契约、API 或业务交互。新增 CSS 文件接入、MIME 与 `no-cache` 回归测试;相关 Python 20 项 + Node 5 项通过,浏览器连接不可用故截图级视觉验收留真实页面复核。
- **07-29 / 0.60.7 / 组合框内直接新建工作目录**:新对话目录 combobox 增加 allow-create 语义:输入时继续过滤已有目录,无完全同名项时在同一下拉内提供“创建工作目录「名称」”,明确选择后才进入新建状态,失焦搜索词不会误提交;删除外部新建按钮与二级输入框。纯前端改动,无 API、schema、migration 或目录绑定语义变化;全量 387 测试通过17 项按环境跳过)。 - **07-29 / 0.60.7 / 组合框内直接新建工作目录**:新对话目录 combobox 增加 allow-create 语义:输入时继续过滤已有目录,无完全同名项时在同一下拉内提供“创建工作目录「名称」”,明确选择后才进入新建状态,失焦搜索词不会误提交;删除外部新建按钮与二级输入框。纯前端改动,无 API、schema、migration 或目录绑定语义变化;全量 387 测试通过17 项按环境跳过)。

View File

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

View File

@ -66,6 +66,8 @@ class StaticVendorTests(unittest.TestCase):
self.assertIn('aria-label="润色提示词"', html) self.assertIn('aria-label="润色提示词"', html)
self.assertIn("hasVisibleAssistantContent", frontend) self.assertIn("hasVisibleAssistantContent", frontend)
self.assertIn('input.placeholder = mqPhone.matches', frontend) self.assertIn('input.placeholder = mqPhone.matches', frontend)
self.assertIn("#chat-hint:empty", css)
self.assertNotIn("请选择文件保存位置", frontend)
def test_frontend_surfaces_context_and_cache_stats(self) -> None: def test_frontend_surfaces_context_and_cache_stats(self) -> None:
src = _frontend_source() src = _frontend_source()

View File

@ -403,7 +403,7 @@ let _draftFolderActiveIndex = -1;
function syncDraftAction() { function syncDraftAction() {
const ready = !!draftWorkingDir(); const ready = !!draftWorkingDir();
setActionMode(ready ? "idle" : "loading"); setActionMode(ready ? "idle" : "loading");
$("chat-hint").textContent = ready ? "输入消息开始对话" : "请选择文件保存位置"; $("chat-hint").textContent = "";
const note = $("new-chat-dir-note"); const note = $("new-chat-dir-note");
if (note) { if (note) {
note.textContent = ready note.textContent = ready
@ -685,6 +685,7 @@ export async function selectTask(tid) {
renderMessages(state.loadedMessages); // 内部含 renderPersistedRunTerminal(须在 taskMeta 赋值后) renderMessages(state.loadedMessages); // 内部含 renderPersistedRunTerminal(须在 taskMeta 赋值后)
renderChatMeta(); renderChatMeta();
applyChannelComposerLock(meta); applyChannelComposerLock(meta);
$("chat-hint").textContent = "";
if (meta.run_status === "running" || meta.run_status === "cancelling") { if (meta.run_status === "running" || meta.run_status === "cancelling") {
ensureRunningTaskSubscribed(tid, `/v1/tasks/${tid}/events`, meta); ensureRunningTaskSubscribed(tid, `/v1/tasks/${tid}/events`, meta);
} else { } else {
@ -2457,7 +2458,7 @@ async function sendMessage(overrideText) {
: e.message; : e.message;
appendErrorCard(msg); appendErrorCard(msg);
setActionMode("idle"); setActionMode("idle");
$("chat-hint").textContent = "就绪"; $("chat-hint").textContent = "";
} }
} }
@ -2556,7 +2557,7 @@ async function fetchSse(url, run) {
state.streaming = state.liveRuns.size > 0; state.streaming = state.liveRuns.size > 0;
syncTaskRowRunIndicator(ctx.taskId); // 先按本地态即时清标识,loadTaskList 随后带回服务端真相(如 error) syncTaskRowRunIndicator(ctx.taskId); // 先按本地态即时清标识,loadTaskList 随后带回服务端真相(如 error)
if (state.taskId === ctx.taskId) { if (state.taskId === ctx.taskId) {
hint.textContent = ctx.lastUsageHint || "就绪"; hint.textContent = ctx.lastUsageHint || "";
setActionMode("idle"); setActionMode("idle");
} }
} }

View File

@ -11,16 +11,16 @@
color-scheme: light; color-scheme: light;
/* Neutral workspace palette */ /* Neutral workspace palette */
--bg: #f6f4f1; --bg: #f7f7f7;
--panel: #ffffff; --panel: #ffffff;
--panel-subtle: #fbfaf8; --panel-subtle: #fafafa;
--panel-muted: #f7f4f0; --panel-muted: #f5f5f5;
--border: #e2ddd7; --border: #e1e3e5;
--border-soft: #ede8e2; --border-soft: #eceeef;
--list-divider: #ddd7d0; --list-divider: #d9dde0;
--text: #292623; --text: #242424;
--text-strong: #1c1a18; --text-strong: #171717;
--muted: #756f69; --muted: #73777c;
/* Institute red is deliberately reserved for primary emphasis. */ /* Institute red is deliberately reserved for primary emphasis. */
--accent: #b83a31; --accent: #b83a31;
@ -28,9 +28,9 @@
--accent-soft: #fbecea; --accent-soft: #fbecea;
--accent-ring: rgba(184, 58, 49, 0.16); --accent-ring: rgba(184, 58, 49, 0.16);
--hover: #f5f1ed; --hover: #f2f3f4;
--code-bg: #f6f3ef; --code-bg: #f5f5f5;
--user-bg: #f5f1eb; --user-bg: #f2f6fa;
--asst-bg: #ffffff; --asst-bg: #ffffff;
--c-green: #238653; --c-green: #238653;
@ -98,7 +98,7 @@ button.small {
} }
button:hover:not(:disabled) { button:hover:not(:disabled) {
border-color: #cdc5bd; border-color: #cbd1d6;
} }
button.primary { button.primary {
@ -137,7 +137,7 @@ select {
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):hover, input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):hover,
textarea:hover, textarea:hover,
select:hover { select:hover {
border-color: #c9c1b9; border-color: #c7cdd2;
} }
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):focus, input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):focus,
@ -149,7 +149,7 @@ select:focus {
} }
::placeholder { ::placeholder {
color: #9b958f; color: #999fa5;
} }
/* Application shell */ /* Application shell */
@ -203,7 +203,7 @@ header .who {
} }
.splitter::before { .splitter::before {
background: #d9d3cc; background: #d7dce0;
} }
.pane-head { .pane-head {
@ -225,7 +225,7 @@ header .who {
#task-scroll, #task-scroll,
#file-list { #file-list {
scrollbar-color: #cec7bf transparent; scrollbar-color: #cbd1d6 transparent;
scrollbar-width: thin; scrollbar-width: thin;
} }
@ -337,8 +337,8 @@ header .who {
line-height: 1.7; line-height: 1.7;
} }
/* Conversation: one centred reading rail, with each assistant turn grouped as /* Conversation: one left-anchored reading rail, with each assistant turn grouped
a document block rather than a floating chat label. */ as a document block rather than a floating chat label. */
#chat-meta { #chat-meta {
min-height: 40px; min-height: 40px;
@ -369,12 +369,12 @@ header .who {
.msg.assistant { .msg.assistant {
width: min(100%, 52rem); width: min(100%, 52rem);
max-width: min(100%, 52rem); max-width: min(100%, 52rem);
align-self: center; align-self: flex-start;
padding: 16px 18px; padding: 16px 18px;
border: 1px solid var(--border-soft); border: 1px solid var(--border-soft);
border-left: 3px solid #cbc3bb; border-left: 3px solid #cbd0d4;
border-radius: var(--r-lg); border-radius: var(--r-lg);
background: #fffdfa; background: #ffffff;
} }
.msg.assistant.live-run { .msg.assistant.live-run {
@ -401,7 +401,7 @@ header .who {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 7px; gap: 7px;
color: #6d665f; color: #656b70;
} }
.msg.assistant > .role::before { .msg.assistant > .role::before {
@ -417,7 +417,7 @@ header .who {
.msg.tool { .msg.tool {
width: min(100%, 52rem); width: min(100%, 52rem);
max-width: min(100%, 52rem); max-width: min(100%, 52rem);
align-self: center; align-self: flex-start;
padding: 0 18px; padding: 0 18px;
border: 0; border: 0;
background: transparent; background: transparent;
@ -430,7 +430,7 @@ header .who {
#task-progress-dock .task-progress { #task-progress-dock .task-progress {
width: min(100%, 52rem); width: min(100%, 52rem);
max-width: 52rem; max-width: 52rem;
margin: 0 auto; margin: 0;
} }
.msg .body { .msg .body {
@ -456,7 +456,7 @@ header .who {
.tool-call, .tool-call,
.reasoning { .reasoning {
color: #5f5851; color: #555b60;
} }
.tool-call summary, .tool-call summary,
@ -490,13 +490,17 @@ header .who {
padding: 10px 12px; padding: 10px 12px;
line-height: 1.6; line-height: 1.6;
border-radius: var(--r-lg); border-radius: var(--r-lg);
background: #fffdfa; background: #ffffff;
} }
#chat-form .row { #chat-form .row {
align-items: center; align-items: center;
} }
#chat-hint:empty {
display: none;
}
.composer-spacer { .composer-spacer {
flex: 1; flex: 1;
} }
@ -538,7 +542,7 @@ header .who {
} }
.file-row .name { .file-row .name {
color: #332f2b; color: #303438;
} }
.storage-foot { .storage-foot {
@ -549,7 +553,7 @@ header .who {
.storage-foot .bar { .storage-foot .bar {
height: 5px; height: 5px;
background: #e8e3de; background: #e5e8ea;
} }
/* Dialogs and notifications */ /* Dialogs and notifications */
@ -612,37 +616,45 @@ header .who {
@media (max-width: 640px) { @media (max-width: 640px) {
header { header {
min-height: 46px; min-height: 42px;
padding: 7px 10px; padding: 5px 9px;
} }
.pane-head { .pane-head {
min-height: 42px; min-height: 36px;
} }
.mobile-tabs { .mobile-tabs {
gap: 6px; gap: 5px;
} }
.mobile-tabs button { .mobile-tabs button {
min-height: 38px; min-height: 34px;
padding: 6px 8px; padding: 4px 7px;
font-size: 11.5px;
}
#hd-admin {
padding: 4px 8px;
font-size: 11px;
} }
#pane-mid > .pane-head { #pane-mid > .pane-head {
min-height: 38px; min-height: 34px;
padding: 5px 10px; padding: 4px 8px;
} }
#pane-mid > .pane-head > button { #pane-mid > .pane-head > button {
min-height: 30px; min-height: 28px;
padding: 3px 8px;
font-size: 11.5px;
} }
#chat-meta { #chat-meta {
min-height: auto; min-height: auto;
padding: 7px 10px; padding: 5px 8px;
column-gap: 7px; column-gap: 5px;
row-gap: 7px; row-gap: 5px;
} }
#chat-meta > .spacer { #chat-meta > .spacer {
@ -658,15 +670,24 @@ header .who {
#chat-meta select { #chat-meta select {
width: 100% !important; width: 100% !important;
max-width: none; max-width: none;
min-height: 30px;
padding: 2px 6px;
font-size: 12px;
}
#chat-meta button {
min-height: 30px;
padding: 2px 7px;
font-size: 11.5px;
} }
#task-progress-dock { #task-progress-dock {
padding: 6px 10px; padding: 5px 8px;
} }
#chat-stream { #chat-stream {
gap: 14px; gap: 11px;
padding: 14px 12px 18px; padding: 10px 10px 12px;
} }
.msg { .msg {
@ -676,7 +697,7 @@ header .who {
.msg.assistant { .msg.assistant {
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
padding: 13px 14px; padding: 10px 12px;
} }
.msg.tool { .msg.tool {
@ -691,24 +712,23 @@ header .who {
} }
#chat-form { #chat-form {
padding: 10px; padding: 8px 9px;
padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px)); padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
} }
#chat-input { #chat-input {
min-height: 64px; min-height: 52px;
} }
#chat-form .row { #chat-form .row {
flex-wrap: wrap; flex-wrap: wrap;
justify-content: flex-end; justify-content: flex-end;
gap: 8px; gap: 6px;
} }
#chat-hint { #chat-hint {
order: -1; order: -1;
flex: 1 0 100%; flex: 1 0 100%;
min-height: 18px;
line-height: 18px; line-height: 18px;
} }
@ -717,16 +737,16 @@ header .who {
} }
#chat-form .row .composer-tool { #chat-form .row .composer-tool {
width: 44px; width: 40px;
min-width: 44px; min-width: 40px;
height: 44px; height: 40px;
min-height: 44px; min-height: 40px;
padding: 0; padding: 0;
} }
.composer-tool svg { .composer-tool svg {
width: 19px; width: 18px;
height: 19px; height: 18px;
} }
.composer-tool .composer-label { .composer-tool .composer-label {
@ -742,10 +762,11 @@ header .who {
} }
#chat-action { #chat-action {
width: 72px; width: 68px;
min-width: 72px; min-width: 68px;
height: 44px; height: 40px;
min-height: 44px; min-height: 40px;
padding: 5px 12px;
} }
.modal { .modal {