新增: 音频可视化8种新模式+节拍检测+鼠标交互

This commit is contained in:
2026-06-06 18:06:57 +08:00
parent 0302546267
commit bbbc32142d
2 changed files with 206 additions and 1 deletions

171
docs/claude-code-monitor.md Normal file
View File

@@ -0,0 +1,171 @@
# Claude Code 工作状态监控
> 状态:方案设计中 | 2026-06-05
## 需求
在 u-desktop 中即时反馈当前 Claude Code 工作状态(多项目、多会话并行),基于会话指令解析出任务便于跟踪。
两个入口:
1. **桌面 Overlay 卡片** — 状态概览 + 会话名 + busy/idle
2. **托盘看板窗口** — 完整详情 + 任务列表 + 团队信息 + 进度(置顶 + 可调大小)
---
## 数据源
| 源文件 | 内容 | 路径 |
|--------|------|------|
| sessions/*.json | 活跃进程PID、sessionId、cwd、status、name | `~/.claude/sessions/` |
| history.jsonl | 用户输入历史sessionId + display | `~/.claude/history.jsonl` |
| tasks/<sid>/*.json | 任务列表subject、status、依赖 | `~/.claude/tasks/` |
| teams/<name>/config.json | 团队:成员、模型、工作目录 | `~/.claude/teams/` |
### 数据关联
```
sessions/*.json → 活跃会话列表
├─ cwd → 项目名extractProjectName
├─ name → 会话名(自动生成的任务摘要,优先展示)
└─ sessionId → 关联 history/tasks
history.jsonl → 尾读 64KB覆盖 ~3 天)
└─ sessionId → lastPrompt每个会话最后一条指令
tasks/<sid>/ → 临时文件,完成即清除
└─ N.json → subject + status + 依赖关系
```
### 字段实测2026-06-05
```
PID 20804 → name: "knowledge-card-markdown-rendering" status: busy 项目: u-desktop
PID 22244 → name: "claude-code-status-monitor" status: busy 项目: u-desktop
PID 30064 → name: "add-audio-viz-modes" status: idle 项目: u-desktop
PID 7516 → name: "" status: idle 项目: u-desk
```
- `name` 字段是 Claude Code 自动生成的会话名,比 lastPrompt 更结构化
- `status` 在 busy 时 updatedAt 实时刷新(~15sidle 时长时间不更新
- history.jsonl 64KB 覆盖 352 行 / 4828 分钟 / 28 个会话lastPrompt 命中率 4/4
---
## 技术方案
### 采集策略
**定时轮询 5 秒**(非文件监视)。
理由:
- sessions/ 通常 1-5 个文件 × 300B全读 <1ms
- history.jsonl 尾读 64KB <1ms
- 与现有 weatherLoop/knowledgeLoop 模式一致
- ReadDirectoryChangesW 在此场景无优势
### 数据结构
```go
type ClaudeSession struct {
PID int `json:"pid"`
SessionID string `json:"sessionId"`
Project string `json:"project"` // cwd → 项目名
CWD string `json:"cwd"`
Status string `json:"status"` // idle | busy
Name string `json:"name"` // 会话名(任务摘要)
LastPrompt string `json:"lastPrompt"` // 最后用户指令
StartedAt int64 `json:"startedAt"`
UpdatedAt int64 `json:"updatedAt"`
Version string `json:"version"`
Duration string `json:"duration"` // 运行时长
}
type ClaudeTask struct {
ID string `json:"id"`
Subject string `json:"subject"`
Status string `json:"status"` // pending | in_progress | completed
}
type ClaudeStatusData struct {
Sessions []ClaudeSession `json:"sessions"`
TotalActive int `json:"totalActive"`
TotalBusy int `json:"totalBusy"`
UpdatedAt int64 `json:"updatedAt"`
}
```
### 关键修正(来自研判)
1. **isProcessAlive(pid)** — 用 OpenProcess 验证进程存活,过滤幽灵会话
2. **history 尾读跳过首行** — Seek 后首行必然被截断,跳过避免 JSON 解析失败
3. **缓存 lastPrompts** — 按 PID 集合变化决定是否重新读 history避免重复解析
### 推送到前端
复用现有 `evalJS()` + `window.updateClaudeStatusFromGo()` 模式。
看板窗口通过 `WebView2.Dispatch()` 独立推送。
---
## 卡片设计
```
┌─────────────────────────────────────┐
│ Claude Code ●2 忙碌 / 4 活跃 │
│ ─────────────────────────────────── │
│ ● u-desktop knowledge-card... │
│ 最后: 知识卡片刷新按钮修复 │
│ ○ u-desktop add-audio-viz-modes │
│ ○ u-desk ~ │
└─────────────────────────────────────┘
```
- 优先展示 `name`(会话名),补充 `lastPrompt`
- busy = 绿色脉冲idle = 灰色
- 最多 5 个会话,超出折叠
---
## 看板窗口
- 托盘菜单"Claude 看板"入口
- 独立 WebView2纯 HTML embed不走 Vite 构建)
- 深色主题匹配 overlay 风格
- **置顶**`wsExTopmost` (0x00000008) 切换
- **可调大小**:保留 `wsSizebox`
- 会话详情卡片 + 任务列表 + 团队信息
---
## 文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `claude_status.go` | 新建 | 监控引擎:轮询采集、数据结构、推送 |
| `claude_board.go` | 新建 | 看板窗口WebView2 + 置顶 + 可调大小 |
| `web/claude-board.html` | 新建 | 看板页面(纯 HTML embed |
| `ClaudeStatusCard.vue` | 新建 | Overlay 卡片组件 |
| `config.go` | 修改 | +`HideClaudeStatus` |
| `win32.go` | 修改 | +`wsExTopmost` + `procOpenProcess` + `procCloseHandle` |
| `systray.go` | 修改 | +看板菜单 + 启动 loop |
| `wallpaper.go` | 修改 | +cardVisibility + reloadAllCards |
| `settings.go` | 修改 | +toggle + refreshVisibleCards |
| `types.ts` | 修改 | +ClaudeStatusData 类型 |
| `useOverlayState.ts` | 修改 | +claudeStatus 状态 |
| `bridge.ts` | 修改 | +updateClaudeStatusFromGo |
| `SingleLayout.vue` | 修改 | +ClaudeStatusCard |
| `MultiLayout.vue` | 修改 | +ClaudeStatusCard + grid area |
| `ToggleSection.vue` | 修改 | +Claude 状态 toggle |
---
## 研判结论
| 维度 | 评估 |
|------|------|
| 数据可靠性 | 中→高加进程存活校验后。sessions 是第一方数据源,字段稳定 |
| 性能影响 | 极低。每轮 <5ms / 65KB I/O。0.1% CPU |
| 对工具负担 | 零。纯只读、共享读、不锁文件、不连 socket |
| 价值 | 高。多会话并行监控的刚需,无其他工具提供 |
| 成本 | 低。~450 行代码。运行开销可忽略 |
| 性价比 | **极高** |

View File

@@ -48,6 +48,25 @@ func initKnowledgeDB() {
if err != nil {
log.Println("知识库建表失败:", err)
}
cleanPromptLeakedCards()
}
// cleanPromptLeakedCards 清理数据库中 prompt 泄露的垃圾卡片
func cleanPromptLeakedCards() {
if knowledgeDB == nil {
return
}
for _, sig := range promptLeakSignals {
result, err := knowledgeDB.Exec(
"DELETE FROM knowledge_cards WHERE content LIKE ?",
"%"+sig+"%",
)
if err == nil {
if n, _ := result.RowsAffected(); n > 0 {
log.Printf("清理 prompt 泄露卡片: 特征=%q, 删除=%d条", sig, n)
}
}
}
}
func saveKnowledgeCard(keyword, content string) {
@@ -312,12 +331,27 @@ var weakPhrases = []string{
"可以帮助", "广泛应用", "具有重要作用", "不可忽视", "具有重要意义",
}
// isQualityKnowledgeCard 检查质量字数≥120、有空泛表述上限、必须含数字、信息密度
// promptLeakSignals prompt 泄露特征词,命中任意一条即判定为 prompt 而非知识内容
var promptLeakSignals = []string{
"围绕「", "写一条", "角度写", "面向资深开发者",
"信息密度极高", "120-180字", "必须包含至少一个精确数值",
"必须讲清因果链", "不要写常见知识点", "不要复述官方文档",
"附加风格要求", "围绕「", "从「", "」角度",
}
// isQualityKnowledgeCard 检查质量字数≥120、有空泛表述上限、必须含数字、信息密度、prompt 泄露检测
func isQualityKnowledgeCard(content string) bool {
runes := utf8.RuneCountInString(content)
if runes < minKnowledgeRunes {
return false
}
// prompt 泄露检测:包含 prompt 指令特征的内容直接拒绝
for _, sig := range promptLeakSignals {
if strings.Contains(content, sig) {
log.Printf("知识卡片 prompt 泄露检测命中 %q丢弃", sig)
return false
}
}
// 必须包含数字(确保有具体数值)
if !numberRe.MatchString(content) {
return false