diff --git a/FRONTEND_BEAUTY_NOTEBOOK.md b/FRONTEND_BEAUTY_NOTEBOOK.md deleted file mode 100644 index 338d593..0000000 --- a/FRONTEND_BEAUTY_NOTEBOOK.md +++ /dev/null @@ -1,227 +0,0 @@ -# GADesktop 前端美化开发笔记本 - -**项目**:GADesktop-main-4 -**仓库**:origin=dd3xp/GADesktop(上游),myfork=wjl2023/GADesktop(个人 fork) -**起始 commit**:4004590(与 origin/main 一致) -**主题**:前端美化(UI/UX/视觉优化) -**Bridge**:http://127.0.0.1:14169(PID 见 /tmp/bridge_main4.log) - ---- - -## 0. 开发约束(每次开始前过一遍) - -| # | 约束 | 说明 | -|---|------|------| -| 1 | 仅改前端 | 主战场:`frontends/desktop/static/` (app.js / index.html / styles.css / annot.* / vendor) | -| 2 | 零硬编码 | 颜色用 CSS 变量,文本走 i18n,禁止字面量 | -| 3 | 单文件优先 | 高内聚低耦合,能在一个文件解决就不跨文件 | -| 4 | 风格一致 | 与现有命名/缩进/注释风格保持一致 | -| 5 | 改前必读 | 用 file_read 看完目标段落再 patch | -| 6 | 小步 commit | 每个功能验证 OK 再 commit,标题用 `style(desktop): ...` 或 `feat(desktop): ...` | -| 7 | 大改起 subagent | 跨文件 / >100 行的改动起监察 | -| 8 | Cmd+Shift+R | 用户测试前提醒强制刷新清缓存 | - ---- - -## 1. 项目入口速查 - -| 文件 | 作用 | -|------|------| -| `frontends/desktop_bridge.py` | aiohttp HTTP+WS 桥,端口 14168(默认),Web/桌面共用 | -| `frontends/desktop/static/index.html` | 主 HTML 框架 | -| `frontends/desktop/static/app.js` | 主前端逻辑(含 typewriter / 折叠 / scroll / 等) | -| `frontends/desktop/static/styles.css` | 主样式 | -| `frontends/desktop/static/annot.css/.js` | 注释相关 | -| `frontends/desktop/static/vendor/` | 第三方库 | - -启动桥: -```bash -cd /Users/lwj/Documents/ga/GADesktop-main-4 -BRIDGE_PORT=14169 nohup python3 frontends/desktop_bridge.py > /tmp/bridge_main4.log 2>&1 & -``` - ---- - -## 2. 美化方向(待用户填补) - -> 本节随用户指示更新 - -- [ ] T1 — 待定 -- [ ] T2 — 待定 - ---- - -## 3. 进度日志 - -| 日期 | 任务 | 文件 | 状态 | commit | -|------|------|------|------|--------| -| 2026-05-25 | 初始化笔记本,桥重启在 14169 | — | ✅ | — | -| 2026-05-25 | fix: summary 重复打印(最后一轮 head+body 双渲染) | app.js L826-838 | ✅ | pending | - ---- - -## 4. 已知决策记录 - -(每次设计抉择记录于此,避免反复折腾) - -- 主战场限定为 `frontends/desktop/static/`,不动 Python 后端 -- CSS 变量优先于 inline style -- 文本走 i18n(看 index.html / app.js 中已有 i18n 机制再扩展) - ---- - -## 5. PR 计划 - -- 分支名:`feat/frontend-beauty-v1`(待定) -- base:`origin/main` (4004590) -- 提交策略:每个美化任务一个 commit,最终汇总成一个 PR -- PR 描述模板见 `../memory/github_contribution_sop.md` - ---- - -## 6. 反模式提醒(来自上次 PR#11 教训) - -- ❌ 不读笔记直接动手 → 重复踩坑 -- ❌ 一次改太多 → 改崩无法回退 -- ❌ 硬编码颜色/文本 → 维护噩梦 -- ❌ 跳过用户验证 → 问题累积 -- ❌ 在崩溃代码上继续修 → 越修越烂,应 stash + checkout - - ---- - -## 7. Feature: 消息计时 (Message Timing) - -**状态**:📋 计划中 -**参考**:v1 (GenericAgent-main-6/frontends/genericagent-electron/renderer/app.js L292-415) -**目标**:在 assistant 消息上显示任务耗时 badge(实时跳动 → 结束后定格) - -### 7.1 v1 核心逻辑摘要 - -| 函数 | 作用 | -|------|------| -| `formatDuration(ms)` | ms→"1ms"/"3.2s"/"2m 15s" | -| `formatTaskElapsed(ms)` | 带前缀格式化,用于 badge 文本 | -| `startTaskTimer(sess)` | runtime.taskStartedAt=now, setInterval(1s) 更新 live badge | -| `stopTaskTimer(sess)` | clearInterval, 计算 endedAt, badge 定格 | -| `ensureAssistantTaskElapsed(wrap, start, end)` | 在 .msg-wrap 上创建/更新 .task-elapsed badge | - -CSS: `.task-elapsed { color: var(--accent); font-size: 12px; font-weight: 600; }` - -### 7.2 v2 Hook 点分析 - -| 事件 | v2 位置 | 说明 | -|------|---------|------| -| 任务开始 | `setBusy(sess, true)` (L1451) | sendPrompt 内 | -| 任务结束 | `setBusy(sess, false)` (L1334/1343/1477) | poll 正常结束 / error | -| 流式 DOM | `r.draftEl` 创建 (L1094) | streaming 时的 assistant 消息 | -| 最终消息 | `appendMessage(sess, msg)` | poll 结束后正式渲染 | -| runtime 对象 | L896 | `{ polling, busy, lastId, seen, draftEl, draftText }` | - -### 7.3 变更计划 - -**原则**:计时逻辑自包含为一个模块(~50行),通过 setBusy 的 2 个调用点接入,变化半径=2行。 - -| # | 变更 | 文件 | 位置 | 行数 | -|---|------|------|------|------| -| 1 | 添加 `formatTaskElapsed(ms)` 工具函数 | app.js | 工具函数区(formatDuration 不存在则新建) | ~15行 | -| 2 | runtime 增加 `taskStartedAt: null, taskTimerId: null` | app.js | L896 runtime 初始化 | +2字段 | -| 3 | 添加 `startTaskTimer(sess)` / `stopTaskTimer(sess)` | app.js | setBusy 上方 | ~25行 | -| 4 | setBusy 内调用:busy=true→start, busy=false→stop | app.js | L1144 setBusy 函数体 | +2行 | -| 5 | `ensureTaskElapsedBadge(wrap, startedAt, endedAt)` | app.js | 渲染辅助区 | ~15行 | -| 6 | draftEl 创建时挂 badge;appendMessage 对 role=assistant 挂 badge | app.js | L1094, L1043 | +2行 | -| 7 | `.task-elapsed` 样式 | styles.css | 消息样式区 | ~3行 | -| 8 | i18n key: `timing.elapsed` | app.js | i18n 对象 | +1行 | - -**总改动**:~65 行新增,2 行修改。仅 app.js + styles.css。 - -### 7.4 关联影响 - -- `setBusy()` — 新增 2 行调用,不改原有逻辑 -- `appendMessage()` — 在 assistant 分支末尾加 1 行 badge 挂载 -- `draftEl` 创建处 — 加 1 行 badge 挂载 -- 无 index.html 改动(badge 由 JS 动态创建) - -### 7.5 风险 - -- 低:setInterval 泄漏 → stopTaskTimer 必须在所有 setBusy(false) 路径调用(已覆盖 3 处) -- 低:页面切换时 badge 更新 → 只更新当前 session 的 badge(用 data-session-id 过滤) - -### 7.6 Bug修复:badge 闪烁 + 布局跳动 - -**现象**:badge 一跳一跳,只在变化时短暂显示,每次出现导致气泡上下跳动 - -**根因**: -1. `rewriteDraftBubble` 每 30ms 用 `innerHTML=` 重写 draftEl,badge 被销毁 -2. timer 每 1000ms 才重建 badge → badge 只存活 30ms -3. CSS 无固定高度,badge 出现/消失改变容器高度 - -**修复**(3 处,仅 app.js + styles.css): -1. `rewriteDraftBubble`:innerHTML 前保存 badge 文本,替换后恢复(L1133-1149) -2. `renderDraft`:创建 draftEl 时若正在计时立即挂载 badge(L1109) -3. CSS `.task-elapsed`:`display:block; min-height:18px; line-height:18px` 固定占位 - -**关联**: -- `rewriteDraftBubble` — 新增 badge 保存/恢复逻辑 -- `renderDraft` — 新增 1 行 ensureTaskElapsedBadge 调用 -- styles.css `.task-elapsed` — 改为 block + 固定行高 - -### 7.7 Code Review & PR - -**PR #14**: https://github.com/dd3xp/GADesktop/pull/14 -- 分支: `feat/message-timing-badge` -- 改动: app.js +93, styles.css +8 -- Review 结论: 可合并,无阻塞问题 -- 可优化点(后续迭代):rewriteDraftBubble 可移动节点而非重建;多条 assistant 消息只有最后一条带 badge - ---- - -## 8. 主题精简 + 气泡美化(进行中) - -### TODO 清单 - -| # | 任务 | 目标 | 验证 | 状态 | -|---|------|------|------|------| -| 1 | 主题缩减为 2 个圆点 | 保留 swatch 圆点 UI,只留白色(浅色)和灰色(深色)两个 | 设置弹窗只显示 2 个圆点;点击切换 light/dark 正常;无控制台报错 | ✅ 已完成 | -| 2 | 气泡样式重构 | 待定 | 待定 | 待定 | -| 3 | 字体与行高优化 | 待定 | 待定 | 待定 | - ---- - -### TODO 1: 主题缩减为 2 个圆点(白色=浅色,灰色=深色) - -**改动计划**: -- **index.html**: `#theme-swatches` 从 8 个 swatch 缩减为 2 个 -- **styles.css**: `--swatch-1`~`--swatch-8` 缩减为 2 个;删除多余 `html[data-theme]` 规则 -- **app.js**: `applyTheme()` 简化为切换 light/dark appearance;swatch 点击逻辑适配 - - -**实施完成** (2026-05-25): -- index.html: 8 个 swatch 缩减为 2 个(data-theme="1" 白色, data-theme="2" 灰色) -- styles.css: 删除 swatch 3-8 颜色变量和规则;删除 theme="7"/"8" 残留规则 -- app.js: applyTheme() 简化,swatch 与 appearance-seg 双向同步 - -**Bug 修复** (2026-05-25): -- Bug1: 侧边栏选中项文字不可见 → 根因:`applyTheme` 中 `root.style.setProperty('--blue', swatch颜色)` 把全局强调色覆盖为白/灰 → 删除该行,--blue 保持 CSS 定义的 #1f6f53 -- Bug2: swatch 高亮环永远在深色 → 同一根因,--blue 被设为白色导致 box-shadow 白底白环不可见 → 同上修复 -- 额外:白色 swatch 的勾选标记改用深色描边(避免白底白勾不可见) - -**验证结果**: -- --blue = #1f6f53(固定绿色强调色),不再被 swatch 颜色覆盖 -- 侧边栏选中项文字 rgb(31,111,83) 在浅色/深色背景上均可见 -- swatch 高亮环 rgb(31,111,83) 正确跟随选中状态切换 - ---- - -### 计时器刷新驻留修复 v2 (2026-05-25) - -**问题:计时"已运行x"刷新后丢失** -- 根因:`r.taskStartedAt` 仅存于内存 runtime Map,刷新后 runtime 重建;pollSession 重新检测 running → setBusy → startTaskTimer 设新 Date.now(),真正开始时间丢失 -- ❌ sessionStorage 方案失败(刷新后仍丢失) -- ✅ 最终方案:基于消息 ts 时间戳恢复(2026-05-25 成功验证) - - 根因:消息对象本身有 `ts` 字段(API 返回),但 `normalize()` 函数丢弃了它 - - 修复 3 处(仅 app.js): - 1. `normalize()` 增加 `if (m.ts) o.ts = m.ts;` — 保留时间戳 - 2. `restoreElapsedBadges(sess, box)` 从 `renderAllMessages` 移到 `pollSession` finally 块 — 确保消息已加载 - 3. `startTaskTimer(sess)` 从最后一条 user 消息的 ts 恢复 taskStartedAt — 运行中任务继续计时 - - 验证结果:刷新后 badge 正确显示"已运行 17s"/"已运行 14s",已完成任务静态显示,运行中任务实时计时 diff --git a/frontends/desktop/static/app.js b/frontends/desktop/static/app.js index ca004fe..7bead08 100644 --- a/frontends/desktop/static/app.js +++ b/frontends/desktop/static/app.js @@ -2352,8 +2352,9 @@ async function tokPollBridge() { function tokGetFiltered() { let records = tokLoadHistory(); - const since = tokSince?.value ? new Date(tokSince.value).getTime()/1000 : 0; - const until = tokUntil?.value ? new Date(tokUntil.value).getTime()/1000 : 0; + const parseD = v => v ? new Date(v.replace(/\s+/,'T')).getTime()/1000 : 0; + const since = parseD(tokSince?.value); + const until = parseD(tokUntil?.value); if (since) records = records.filter(r=>r.ts>=since); if (until) records = records.filter(r=>r.ts<=until); return records; @@ -2405,10 +2406,12 @@ function tokRenderTable(records) { } async function loadTokenPage(){await tokPollBridge();const f=tokGetFiltered();const all=tokLoadHistory();tokRenderStats(f,all);tokRenderTable(f);} -if(tokSince)tokSince.addEventListener('change',()=>{_tokPage=0;loadTokenPage();}); -if(tokUntil)tokUntil.addEventListener('change',()=>{_tokPage=0;loadTokenPage();}); +/* Flatpickr 初始化 */ +const _fpOpts = { enableTime:true, time_24hr:true, dateFormat:'Y-m-d H:i', locale:window.flatpickr?.l10ns?.[document.documentElement.lang==='en'?'default':'zh']||'default', allowInput:false, onChange(){ _tokPage=0; loadTokenPage(); } }; +const fpSince = tokSince ? flatpickr(tokSince, _fpOpts) : null; +const fpUntil = tokUntil ? flatpickr(tokUntil, _fpOpts) : null; const tokResetBtn=document.getElementById('tok-reset'); -if(tokResetBtn)tokResetBtn.addEventListener('click',()=>{if(tokSince)tokSince.value='';if(tokUntil)tokUntil.value='';_tokPage=0;loadTokenPage();}); +if(tokResetBtn)tokResetBtn.addEventListener('click',()=>{if(fpSince)fpSince.clear();if(fpUntil)fpUntil.clear();_tokPage=0;loadTokenPage();}); nav.addEventListener('click',(e)=>{const item=e.target.closest('.nav-item');if(item&&item.dataset.page==='token')loadTokenPage();if(item&&item.dataset.page==='channels')renderChannelList(gaServiceStore.list());if(item&&item.dataset.page==='status')loadStatusPanel();}); /* ═══════════════ 自定义预设 ═══════════════ */ const CP_KEY = 'ga_custom_presets'; diff --git a/frontends/desktop/static/index.html b/frontends/desktop/static/index.html index 2ffc4c4..4d6fd4a 100644 --- a/frontends/desktop/static/index.html +++ b/frontends/desktop/static/index.html @@ -8,6 +8,10 @@ + + + +