# 对话区快捷窗口栈 · 设计定稿(2026-07-21) > 状态:demo 已验收,正式接入 `static/src` 中。 > Demo 位置:`demo/todo_float_window/`(目录名为历史遗留,内含全部 4 个窗口;浏览器直接打开 `index.html`)。 > 本文档是唯一设计依据;压缩对话后凭本文档 + demo 继续。 ## 0.1 正式接入变更(2026-07-21 用户补充,覆盖 demo 的悬浮定位) 1. **命名**:该功能正式名称为「**快捷窗口**」(quick dock / quick window)。 2. **布局:占位而非悬浮**:快捷窗口**不是** `position: fixed` 悬浮,而是在**对话区域右侧空白区域占位显示**的一列,常驻时会**把对话区域向左挤压**(与 git/终端 right-panels 同为 `chat-container` 的 flex 兄弟节点,位于 `` 之后、right-panels 之前)。有内容时才显示该列。 3. **设置项**:个人空间(PersonalizationDrawer)新增「**隐藏快捷窗口**」开关,**默认关闭**(即默认显示)。字段 `hide_quick_dock: boolean` 存 personalization form。 4. 详情面板(子智能体/后台指令)保持 fixed 悬浮,从快捷窗口列左侧滑出;文件预览侧边栏在快捷窗口列右侧(git/终端面板左侧)占位/滑出。 5. 移动端(isMobileViewport)第一版先隐藏。 ## 0. Demo 文件地图 | 文件 | 内容 | |------|------| | `index.html` | 页面结构:模拟对话区 + 悬浮栈(4 窗口)+ 详情面板 + 预览侧边栏 + 菜单 + toast | | `style.css` | 变量(配色/尺寸/缓动)+ 窗口基础 + 待办样式与全部 keyframes | | `app.js` | 待办逻辑(创建/完成/替换/滚动划线) | | `runner.css` / `runner.js` | 子智能体 + 后台指令窗口、左侧详情面板、⋯ 菜单(强制关闭) | | `file.css` / `file.js` | 文件记录窗口、右侧预览侧边栏、⋯ 菜单(下载/打开/复制路径)、toast | ## 1. 总体布局 - 位置:对话区域右侧**占位列**(非悬浮):`chat-container` 的 flex 兄弟节点,宽 294px(270 + 左右 padding 12),`flex: none`,整列上下排布。对话区被自然向左挤压。 - 窗口顺序(从上到下,固定):**待办事项 → 子智能体 → 后台指令 → 文件**。 - 窗口上下排布,右对齐,`gap: 12px`。 - **整列可滚动**:列高 100%,`overflow-y: auto`,滚动条隐藏。 - 防阴影被 overflow 裁剪:栈 `padding: 24px; margin: -24px`。 - 栈滚动时关闭所有打开的 ⋯ 菜单(菜单 fixed 定位不随栈滚动)。 - 各窗口初始隐藏,有内容时淡入出现(fade + translateY(-6px),0.3s)。 ## 2. 窗口通用规格 - **宽度固定 270px**(= 最大宽度 300px × 90%,变量 `--window-max-w` 联动)。 - **列表区高度固定 5 行**(行高 36px → 180px),不足留空,超出内部滚动,滚动条隐藏。 - 窗口 `flex: none`(防止被栈 flex 压缩变矮,已踩坑)。 - 圆角 12px;背景不透明(实体面板禁半透明);中性阴影(无发光);1px 细边框。 - 标题栏 38px:SVG 图标(accent 色)+ 标题(12.5px 次要色)+ 右侧计数(11.5px 三级色)。 - **列表无上下 padding**:首行 hover 紧贴标题栏分隔线,末行紧贴窗口底边。 - 行:固定 36px 高,直接铺在窗口上(无嵌套卡片/背景/边框),超长文字省略号。 - 正式接入时配色全部换成三主题语义 token(demo 为经典主题暖色写死)。 ## 3. 各窗口设计 ### 3.1 待办事项 - 行结构:`[状态点] 任务文字`;**无 hover、不可点击**。 - 计数:`done/total`。 - 动画: - **创建**:逐行从左向右(位移 14px + 淡入,0.34s,stagger 60ms)。 - **完成**:横线从左往右划过文字(260ms,`strike-in` scaleX 0→1,origin left)→ 文字延迟 120ms 变灰 + 状态点变色弹跳(`dot-pop`)。静态完成态(整表渲染时)不播划线,直接呈现。 - **整表替换**(收到新 todo):旧行从上到下逐行从右向左移出(0.26s,stagger 45ms,**带高度收拢**让下方行平滑上移)→ 新行逐行进入。 - **区域外条目**:要完成的行未完全显示时,先 `scrollIntoView({behavior:'smooth', block:'nearest'})` 滚出,滚动停稳 90ms 后再播划线(800ms 兜底)。 ### 3.2 子智能体 / 3.3 后台指令(两者同构,仅详情内容不同) - 行结构:`[●状态点] 名称 [⋯]`;**有 hover,整行可点击**;后台指令名称用等宽字体。 - 状态点:运行中 = accent 色呼吸(`status-pulse` 1.6s);完成/终止 = 灰,名称同步变灰。 - 计数:`running/total`。 - 新条目:进入动画同待办;**区域外先滚动露出再播进入动画**(先隐身插入,滚动到位后播)。 - **无删除**(条目永久保留)。 - ⋯ 菜单:仅「强制关闭」(警示红色,运行完/已终止置灰);菜单与按钮**右对齐**向下展开;两菜单(本菜单与文件菜单)互斥;Esc / 点空白关闭。 - **点击行 → 左侧展开详情面板**(480×460,`right: calc(20px + 270px + 12px)`,滑入 0.24s): - 子智能体:工具调用 + 文本输出流,**扁平行式无嵌套卡片**;工具行:运行中 spinner(旋转)→ 完成变 ✓ + 右侧落定结果;文本行次要色。 - 后台指令:等宽终端行,首行 `$ 命令` 加粗。 - 新进度出现动画:淡入 + translateY(5px) 上浮(0.25s)。 - **自动滚动**:距底 <80px 时新内容平滑滚到底;用户上翻则不打断。 - 再点同一行 / ✕ / Esc 关闭;点其他行切换(内容区快速淡入)。 - ⚠️ **实际 API 颗粒度提醒**:子智能体进度 API 没有细化的"运行中流式状态",**每个步骤完成后才推送一次进度**。demo 里的流式效果仅为演示,正式接入时的显示颗粒度届时再定。 ### 3.4 文件 - 行结构:`[⋯] 文件名`;**无状态点**;有 hover,整行可点击。 - 计数:文件总数。 - 显示逻辑(正式版):**文件被编辑/创建过 + 文件仍存在**才显示;同一文件按 path 去重只出现一次。 - ⋯ 菜单(与按钮**左对齐**向下展开): 1. **下载** 2. **在文件管理器中打开**——复用 git 状态侧边栏已有逻辑,用默认应用打开;**docker 模式下无此选项** 3. **复制路径**——工作区内相对路径 - **点击行 → 右侧滑出预览侧边栏**(非左侧详情面板): - 440px 宽、全高(top/bottom 20px),`translateX(100%+24px)` ↔ `0`,0.3s。 - 打开时**悬浮窗栈与详情面板同步左移让位**(栈 `right: 20+440+12`,均带 0.3s transition)。 - 头部:文件图标 + 文件名(等宽加粗)+ 目录(灰小字)+ 关闭按钮。 - 内容:等宽 12px + 行号(44px 灰),行 hover 有底色,滚动条隐藏。 - 再点同一文件 / ✕ / Esc 关闭;点其他文件直接切换。 - 仅支持特定种类 UTF-8 文本文件(正式版按扩展名白名单)。 ## 4. 后端配合点(正式接入) 1. **文件记录**:每次 `write_file` / `edit_file` 成功后,把文件**相对路径**(含时间戳、操作类型)写入**对话文件(对话 JSON)**;同一 path 更新而非追加重复项。 2. **前端读取**:前端从对话数据中读取文件记录列表渲染文件窗口。 3. **文件内容**:预览用**现有文件内容 API**(用户确认已有,无需新做)。 4. 待办 / 子智能体 / 后台指令:接各自现有数据流与事件推送。 ## 5. 已踩过的实现坑(接入时避开) 1. `display: flex` 会覆盖 `hidden` 属性的 `display: none` → 必须补 `.panel[hidden] { display: none; }`。 2. flex 容器会压缩子元素(窗口变矮)→ 窗口加 `flex: none`。 3. `animationend` 会冒泡(feed 行 → 详情面板),用 `{once:true}` 监听会被提前消费 → 时序控制改用 `setTimeout`。 4. `overflow` 裁剪窗口阴影 → 滚动容器 `padding + 负 margin` 扩大滚动盒。 5. 菜单 fixed 定位不随栈滚动 → 栈滚动时主动关闭菜单。 6. 两个 ⋯ 菜单 + Esc 多层浮层:菜单互斥;Esc 按 菜单 → 详情面板 → 预览栏 顺序逐个关(用 `e.preventDefault()` + `e.defaultPrevented` 串联)。 ## 6. 正式接入任务清单(2026-07-21 已全部完成) - [x] 后端:编辑文件路径记录写入对话 JSON(write/edit 钩子)+ 对话数据带出该列表 - [x] 前端:`static/src` 快捷窗口占位列容器(非悬浮,挤压对话区) - [x] 前端:4 个窗口组件 + 详情面板 + 文件预览侧边栏(配色走语义 token 三主题) - [x] 前端:接入待办 / 子智能体 / 后台指令 / 文件记录的真实数据与事件推送 - [x] 前端:文件预览内容接现有文件内容 API(扩展名白名单) - [x] 动画参数按 demo 定稿迁移(位移 14px、stagger 60/45ms、划线 260ms 等) ### 接入实现位置(2026-07-21) **后端** - `core/main_terminal_parts/tools_execution.py`:`_record_edited_file` / `_remove_edited_file` / `_rename_edited_file` + `_mutate_edited_files`(写入对话 `metadata.edited_files`,同 path 去重;广播 `edited_files_updated`) - `server/conversation.py`:`GET /api/conversations//messages` 返回 `edited_files`(过滤不存在文件) - `server/app_legacy.py`:`terminal_broadcast` 白名单加 `edited_files_updated` - `modules/personalization_manager.py`:`hide_quick_dock` 字段(默认 False) **前端** - `static/src/components/chat/quickdock/`:`QuickDock.vue`(占位列容器+菜单+Esc 分层+轮询)、`TodoWindow.vue`、`RunnerWindow.vue`(子智能体/后台指令通用)、`RunnerDetailPanel.vue`、`FileWindow.vue`、`FilePreviewPanel.vue`、`quickdock.css`(全部 keyframes 与语义 token 配色) - `static/src/stores/quickDock.ts`:editedFiles / detail / previewPath / menu 状态 - `static/src/composables/useLegacySocket.ts`:监听 `edited_files_updated`(按 conversation_id 过滤) - `static/src/app/methods/history.ts`:messages 加载后回填 editedFiles - `static/src/App.vue`:`` 后挂载 `QuickDock` + `FilePreviewPanel`(移动端与 hide_quick_dock 时不渲染) - `static/src/components/personalization/PersonalizationDrawer.vue`:「隐藏快捷窗口」设置行(默认隐藏工作区之后) **验证状态**:py_compile ✓ / 6 项后端冒烟 ✓ / tsc+stylelint+vite build ✓ / 新文件 eslint 0 问题,存量文件 lint 与基线持平。8091 服务重启后的实际运行验证待手动进行。