feat(memory): P1 长期记忆升级 —— 异步攒批 Consolidate + 软删 + importance/last_seen #1

Merged
Blizzard merged 181 commits from feat/wails3 into main 2026-07-17 01:12:32 +00:00
10 changed files with 122 additions and 221 deletions
Showing only changes of commit ca7ce4c26f - Show all commits
-62
View File
@@ -1,62 +0,0 @@
# 桌面端工业化升级规划(desktop-industrial-plan
承接 [desktop-ui-plan.md](desktop-ui-plan.md)(IA/能力规划)。本文聚焦把现有桌面端从
"深色控制台 MVP"打磨到**工业级桌面产品**的质量提升路线。
> 现状基线(2026-06-12 实测):`sundynix-desktop/frontend` = React 19 + TS + Tailwind +
> @xyflow/react**无 UI 组件库**README 写的 shadcn/ui 实际未安装),图标用 Unicode 字符,
> Wails Go 桥已生成(`wailsjs/`)但前端从未调用。已有视图:工作台/编排/知识库/报告/运行·观测/记忆。
## 诊断:为什么"不够工业级"
问题不在配色,而在**缺产品级骨架**:
1. **无设计系统** — 按钮/输入/卡片/徽标全是手搓内联 class、复制粘贴、不一致;强调色
(violet/cyan) 是散落字面量,无法换肤。
2. **图标是 Unicode 字符**(▤◆▣▦▸◇⌧⚙)— 最掉档次。
3. **Wails Go 桥未用** — 本质是"套了窗口的网页":无原生文件框、拖拽入库、本地读盘、
原生通知、自定义标题栏、"用系统 Word 打开报告"。这是"工业级桌面"最大缺口。
4. **无应用级 UX** — 无 toast / 全局错误边界 / 命令面板(⌘K) / 快捷键 / 确认弹窗 / 骨架屏。
5. **健康灯 5 个只 2 个真**NATS/Milvus/Neo4j 灰)— 缺网关 `/health` 聚合。
6. **状态全在 App useState,刷新即丢** — 无运行历史/最近项持久化。
7. **输出裸 `<pre>`** — 报告是 Markdown 却不渲染;无自动滚动/复制/速率。
8. **布局密度** — 为 1280 设计但窗口 min 1100,面板挤;固定高度;无可拖拽分隔。
## 升级阶段(按性价比/依赖排序)
### A. 设计系统地基(最高优先,其它都依赖它)
-`lucide-react` 图标;自建轻量 primitives(不引整套 shadcn,保零重依赖、clone 即跑):
`Button` `Input` `Textarea` `Select` `Card` `Badge` `Tabs` `Tooltip` `Dialog` `Toast`
`Skeleton` `EmptyState`,统一 `cn()`
- Tailwind 令牌补全:语义色(brand/accent/success/warn/danger+ 半径/间距/字阶 token
强调色字面量收敛到 token。
- 6 个视图 + shell 迁到 primitives,去掉复制粘贴。
### B. 真·桌面集成("工业级桌面"最大缺口)
- 启用 Wails Go 桥:`app.go``OpenFileDialog`/`SaveFileDialog`/`SaveReport`/
`OpenInSystem`/`ReadFile`,前端经 `wailsjs` 调。
- 知识库**拖拽 + 原生选择框**入库;报告**原生"另存为 .docx"** + "用系统 Word 打开"。
- 原生通知(报告完成弹系统通知)、最近文件、窗口标题随任务变。
- 自定义无边框标题栏(macOS 红绿灯内嵌 + 可拖拽区)。
### C. 应用级 UX 骨架
- 全局 `ToastProvider`(替代散落 err 文本)、`ErrorBoundary`、确认 `Dialog`
- 命令面板 `⌘K`(跳转/新建报告/搜知识库/运行)+ 全局快捷键。
- 骨架屏 + 有插画的空状态(取代"规划中"文字)。
### D. 内容与可信度
- Markdown 渲染(报告/输出)+ 代码高亮 + 复制 + 流式自动跟随底部 + tokens/s 速率表。
- 网关 `GET /health` 聚合 NATS/Milvus/Neo4j/PG/Redis → 5 灯全真。
- 运行历史持久化(Wails 配置或 localStorage),RunsView 列出历史可回看。
### E. 布局与密度
- 可拖拽分隔的面板、响应式断点、记忆面板宽度。
## 建议路线
**A(设计系统)** 打底 → **B(真桌面集成)**,这两个一起把"网页套壳"扭转成"工业级桌面应用",
感知提升最大。C/D/E 增量补。
## 边界/原则
- **零重依赖**:自建 primitives 而非整套组件库,守住 "clone 即跑、无重 native 依赖" 目标。
- 不破坏既有真实链路(任务流/工具调用/Token 流/执行事件/报告)。
- 深色优先;令牌化后保留未来切浅色的可能。
-133
View File
@@ -1,133 +0,0 @@
# sundynix-agentix · 桌面端 UI 规划
> 第 1 层 Client`sundynix-desktop`Wails + React 19 + React Flow)的产品级 UI 规划。
> 目标:不止是连 Gateway 的 UI,而是承载 **RAG 知识库 / 报告生成 / 垂直行业平台级复制** 的驾驶舱。
> 定位已定:**真 Desktop** —— 用上 Wails 本地能力(文件 I/O、TS/Go 强绑定、本地配置、离线嵌入)。
## 0 · 设计原则 & 角色
- **三类用户,一个壳**:**构建者**(搭 Agent 图、建知识库)/ **操作者**(跑工作流、看报告)/ **管理员**(开垂直、管租户)。UI 按 BUILD / RUN / MANAGE 分组,按角色露不同模块。
- **专业工具范式**:IDE / 节点编辑器布局——左导航 + 主区 + 右检查器 + 底部抽屉。信息密度高。
- **可信优先**:RAG 必带**引用**;记忆**可看可改**;生成**有评测门**。透明是平台级底线。
- **垂直白标**:整个壳由当前垂直 Pack 的配置驱动(名称/主题/启用模块/术语/默认模板)。换行业 = 换配置,不改 UI。
## 1 · 信息架构(左导航)
```
■ 工作台 首页:概览 / 最近 / 快捷
─ BUILD ─
◆ 编排 Studio React Flow Agent 图构建器(核心)
▣ 知识库 (RAG) KB 管理 / 入库 / 检索调试 / 图谱
▤ 报告 模板 / 生成 / 产出库
─ RUN ─
▸ 运行 · 观测 实时执行 / 轨迹 / 历史
─ MANAGE ─
◇ 记忆 画像 / 会话历史
⌧ 市场 · Packs 垂直包 / Agent 模板 / 开通
⚙ 管理 租户 / 用户计费 / 护栏 / 模型 / 设置
```
## 2 · 全局外壳
```
┌──────────────────────────────────────────────────────────────────┐
│ [垂直▾ 法律版] · 搜索/命令(⌘K) · ●GW ●NATS ●Milvus ●Neo4j · 用户▾ │ 顶栏
├────┬──────────────────────────────────────────┬───────────────────┤
│ 左 │ 主区(随模块切换) │ 右:检查器/详情 │
│ 导 ├──────────────────────────────────────────┴───────────────────┤
│ 航 │ 底部抽屉:输出 / 轨迹 / 工具调用 / 引用 / 评测 ▴展开▾收起 │
└────┴──────────────────────────────────────────────────────────────┘
```
- **垂直切换器**:切租户上下文 + 白标。
- **⌘K 命令面板**:跳转 / 跑模板 / 开库 / 查文档。
- **健康灯**GW/NATS/Milvus/Neo4j/LLM 在线状态(可观测性)。
- **底部抽屉全局常驻**:任何模块跑任务,流式/轨迹/引用都在此看。
## 3 · 各模块详规
### ① 工作台
概览卡(KB 数 / 文档数 / 近期运行 / 待办报告 / 配额计费)· 快捷区(跑模板 / 新建图 / 入库)· 最近列表。
### ② 编排 Studio(核心)
左节点面板 · 中画布 · 右检查器 · 顶工具栏。
节点类型(按颜色分类,沿用架构图配色):
| 节点 | 干什么 | 配置(检查器) |
|---|---|---|
| 输入 | 用户提问 / 文件 / 变量 | 来源、占位符 |
| 检索(RAG) | 绑定某 KB 混合检索 | 选 KB、topK、融合权重、rerank、元数据过滤 |
| Agent/LLM | 调模型生成 | 模型、系统提示词、温度、可用工具 |
| 工具 | 绑定 MCP 工具 | parse_document/render_doc/外部API… + 参数 |
| 记忆 | 注入画像/历史/语义 | 勾选注入项 |
| 分支 | 条件路由 | 条件表达式 |
| 并行/Map | fan-out(逐章节) | 拆分依据 |
| 汇聚 | reduce/合并 | 合并策略 |
| 渲染 | UniOffice 出 docx/pdf | 模板、样式 |
| 输出 | 展示/导出/落盘 | 目标(屏/文件) |
| 子图 | 嵌套可复用模板 | 选模板 |
顶工具栏:加节点 · ▶运行 · ■停止 · 保存/打开 `.dsl.json`(Wails 落盘) · **校验**(按后端 DSL schema 标红) · 版本。
运行时:节点逐个点亮 + 状态徽标(待/跑/流/完/错/降级) + token 数/耗时。
### ③ 知识库(RAG)—— 流水线,每段都露出
左 KB 列表 · 中主区(Tab) · 右文档/块详情。
- **入库监控**:拖文件/文件夹 → 流水线进度(解析→切块→向量化→三路建索引)、队列深度、失败重试。
- **检索调试台**:query → 三路召回 + 融合 + rerank 中间结果,每条带来源徽标(Bleve/Milvus/Neo4j)+ 分数。
- **文档/块浏览**:切块、向量邻居、引用回链。
- **知识图谱**Neo4j 实体-关系可视化(GraphRAG 的脸)。
- **检索评测**:跑 golden 集,召回质量/忠实度(接 harness eval)。
### ④ 报告 —— 长程多节点编排的成品界面
左大纲树 · 中预览 · 右章节检查器;顶部进度。
- 模板库(尽调/行业分析/合规,预制 Eino 子图)· 大纲编辑(增删改/拖排序/指定 KB)。
- 生成中:各章节独立进度(检索/撰写/复核),并行 fan-out 可见。
- 中预览:实时拼装(目录/图表/表格/内联引用)。
- 导出条:→ docx/pdf(UniOffice) → Wails 原生保存落盘。产出库:历史/重跑/版本对比。
### ⑤ 运行 · 观测
实时回 Studio 看节点点亮 + 抽屉五 Tab:输出(SSE) / 轨迹(节点时间线) / 工具调用 / 引用 / 评测。运行历史可复盘。
### ⑥ 记忆(透明可控)
"模型记得我什么"(画像逐条,改/删,来源)· 会话 & 历史(多轮,可清空)· 注入开关。
### ⑦ 市场 · Packs(垂直复制入口)
垂直包市场(法律/医疗/金融包)· Agent 模板市场(拉进 Studio 当子图)· 开通向导(选包→配置→建租户→入库→注册模板→应用配置)。
### ⑧ 管理
租户/工作区(多租户隔离/配额)· 用户 & 计费 · 护栏(脱敏/免责/强制引用)· 模型 & 连接(LLM 池/embedding/后端地址/健康)· 设置(本地目录/主题/离线)。
## 4 · 跨模块复用组件(设计系统)
引用 chip `[1]`(悬浮:源文档+块+分数+来源徽标)· 来源徽标(三色) · 节点卡(统一状态机) · 流式 token 视图 · KB/模型/工具选择器 · 运行时间线(甘特) · 评测徽章。
## 5 · 垂直白标
顶栏切垂直 → 壳按 Pack 的 `branding` 重渲染:名称/Logo、主色、启用模块、术语标签、默认模板、护栏横幅。同一引擎,不同皮 + 内容。
## 6 · 状态设计
空态(引导) · 加载/流式(骨架+token+进度) · 降级(横幅:"语义检索降级,已用全文兜底") · 错误/重试。
## 7 · 落地优先级
| 层级 | 做什么 | 价值 |
|---|---|---|
| **MVP** | 外壳(顶栏+导航+健康灯+⌘K) · ② Studio 类型化节点+检查器+校验 · 运行抽屉(输出+轨迹) | 搭图→跑→看 闭环 |
| **平台核心** | ③ 知识库(入库+检索台+引用) · ④ 报告(模板+大纲+预览+导出) · ⑥ 记忆做厚 | RAG/报告 成形 |
| **平台级** | ⑦ 市场+开通向导 · ⑧ 多租户/护栏 · ⑤ 白标 · 图谱/评测台 | 垂直复制 |
> UI 的"平台核心/平台级"依赖后端先就绪:③④依赖 RAG 核心链(embedding + 入库 worker + 真实混合检索),⑦依赖多租户 + Pack 格式。UI 与后端并行排,在"知识库/报告"界面会合。
## 8 · 后端依赖链(提醒)
```
Embedding + 真实 LLM ← 命门(现为桩)
RAG 核心(入库worker + 真Hybrid检索 + rerank) ← 拱心石
报告生成(多节点 Eino 图 + UniOffice 渲染)
多租户 + Pack 格式 + Provisioning ← 垂直复制
```
---
*(本规划随实现推进更新。当前进度见 git log 与 memory。)*
@@ -78,7 +78,7 @@ export function ExecTrace({ events, phase, compact }: { events: ExecEvent[]; pha
<div className="flex items-center gap-2">
<Icon className={cn("h-3.5 w-3.5 shrink-0", m.cls)} strokeWidth={2} />
<span className="text-xs font-medium text-slate-200">{n.label}</span>
<span className={cn("rounded bg-white/5 px-1.5 py-0.5 text-[9px]", m.cls)}>{m.name}</span>
<span className={cn("rounded bg-ink-800 px-1.5 py-0.5 text-[9px]", m.cls)}>{m.name}</span>
{n.ms != null && n.ms > 0 && <span className="ml-auto font-mono text-[10px] text-slate-500">{n.ms} ms</span>}
{n.status === "running" && <span className="ml-auto text-[10px] text-accent-400"></span>}
</div>
+50 -11
View File
@@ -2,6 +2,50 @@
@tailwind components;
@tailwind utilities;
/* 主题令牌::root=亮色,.dark=暗色。值为 RGB 三元组,供 tailwind 的 rgb(var() / α) 取用。
亮色走 shadcn 中性灰(zinc),暗色为精炼中性暗(zinc 系,替代原偏蓝的 ink)。 */
:root {
--ink-950: 249 249 250; /* 页面底 */
--ink-900: 255 255 255; /* 顶栏/面板 */
--ink-850: 255 255 255; /* 卡片 */
--ink-800: 244 244 245; /* hover/抬升 */
--ink-700: 228 228 231;
--ink-600: 212 212 216;
--line: 228 228 231; /* 边框 zinc-200 */
--slate-100: 24 24 27; /* 最强文字 */
--slate-200: 24 24 27; /* 主文字 */
--slate-300: 63 63 70;
--slate-400: 82 82 91;
--slate-500: 113 113 122; /* 次要 */
--slate-600: 161 161 170; /* 弱 */
--slate-700: 212 212 216;
--sb-thumb: 212 212 216;
--sb-thumb-hover: 161 161 170;
--shadow-card: 0 1px 2px rgba(0, 0, 0, 0.06), 0 8px 24px rgba(0, 0, 0, 0.06);
color-scheme: light;
}
.dark {
--ink-950: 10 10 12;
--ink-900: 20 20 24;
--ink-850: 24 24 27;
--ink-800: 31 31 35;
--ink-700: 38 38 43;
--ink-600: 50 50 56;
--line: 39 39 42; /* zinc-800 */
--slate-100: 244 244 245;
--slate-200: 228 228 231;
--slate-300: 212 212 216;
--slate-400: 161 161 170;
--slate-500: 138 138 147;
--slate-600: 113 113 122;
--slate-700: 82 82 91;
--sb-thumb: 39 39 42;
--sb-thumb-hover: 63 63 70;
--shadow-card: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.25);
color-scheme: dark;
}
html,
body,
#root {
@@ -10,25 +54,25 @@ body,
}
body {
background: #0b0d12;
color: #cbd2de;
background: rgb(var(--ink-950));
color: rgb(var(--slate-300));
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
/* 深色滚动条 */
/* 滚动条随主题 */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-thumb {
background: #242b3c;
background: rgb(var(--sb-thumb));
border-radius: 8px;
border: 2px solid transparent;
background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover {
background: #323a4f;
background: rgb(var(--sb-thumb-hover));
background-clip: padding-box;
}
::-webkit-scrollbar-track {
@@ -44,9 +88,4 @@ body {
color: transparent;
}
/* 输入控件深色统一 */
input,
textarea,
select {
color-scheme: dark;
}
/* 输入控件随主题(color-scheme 由 :root/.dark 设定,控件自动继承) */
@@ -0,0 +1,35 @@
// 主题(亮/暗)管理:持久化到 localStorage,切换在 <html> 上加/去 .dark 类。
// 默认亮色(暗色为可选)。初始化在 applyInitialThememain 启动时调,先于渲染避免闪烁)。
import { useCallback, useEffect, useState } from "react";
export type Theme = "light" | "dark";
const KEY = "sdx-theme";
export function getStoredTheme(): Theme {
const t = localStorage.getItem(KEY);
return t === "dark" ? "dark" : "light"; // 默认亮色
}
function apply(theme: Theme): void {
document.documentElement.classList.toggle("dark", theme === "dark");
}
// applyInitialTheme 在渲染前调用,依据存储值设好 .dark 类(避免首屏闪烁)。
export function applyInitialTheme(): void {
apply(getStoredTheme());
}
// useTheme 暴露当前主题与切换函数(写 localStorage + 切 .dark 类)。
export function useTheme(): { theme: Theme; toggle: () => void; setTheme: (t: Theme) => void } {
const [theme, setThemeState] = useState<Theme>(getStoredTheme);
useEffect(() => {
apply(theme);
localStorage.setItem(KEY, theme);
}, [theme]);
const setTheme = useCallback((t: Theme) => setThemeState(t), []);
const toggle = useCallback(() => setThemeState((t) => (t === "dark" ? "light" : "dark")), []);
return { theme, toggle, setTheme };
}
+3
View File
@@ -2,6 +2,9 @@ import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import { applyInitialTheme } from "./lib/theme";
applyInitialTheme(); // 先于渲染设好主题类,避免首屏闪烁
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
+10 -1
View File
@@ -1,8 +1,9 @@
import type { CSSProperties } from "react";
import { User, ChevronDown, Search as SearchIcon, LogOut } from "lucide-react";
import { User, ChevronDown, Search as SearchIcon, LogOut, Sun, Moon } from "lucide-react";
import type { AuthUser } from "../lib/api";
import { useHealth } from "../lib/health";
import { isMacDesktop } from "../lib/desktop";
import { useTheme } from "../lib/theme";
import { cn } from "../ui";
// Wails 拖拽区/控件 CSS 变量(浏览器忽略,桌面端生效)。
@@ -22,6 +23,7 @@ function Light({ on, label }: { on: boolean; label: string }) {
// 顶栏:品牌 · 垂直切换 · 健康灯 · 登录用户 + 登出(深色 + 毛玻璃)。
export function TopBar({ user, onLogout, onCommand }: { user: AuthUser; onLogout: () => void; onCommand?: () => void }) {
const h = useHealth();
const { theme, toggle } = useTheme();
return (
<header
style={DRAG}
@@ -62,6 +64,13 @@ export function TopBar({ user, onLogout, onCommand }: { user: AuthUser; onLogout
<Light on={h.neo4j} label="Neo4j" />
</div>
<div className="ml-auto flex items-center gap-2" style={NODRAG}>
<button
onClick={toggle}
className="flex items-center rounded-md border border-line bg-ink-800 px-2 py-1 text-slate-400 transition hover:border-ink-600 hover:text-slate-200"
title={theme === "dark" ? "切换到亮色" : "切换到暗色"}
>
{theme === "dark" ? <Sun className="h-3.5 w-3.5" /> : <Moon className="h-3.5 w-3.5" />}
</button>
<span className="flex items-center gap-1.5 rounded-md border border-line bg-ink-800 px-2.5 py-1 text-xs text-slate-300" title={user.email}>
<User className="h-3.5 w-3.5 text-slate-500" />
{user.name || user.email}
+1 -1
View File
@@ -4,7 +4,7 @@ import { cn } from "./cn";
type Tone = "neutral" | "brand" | "accent" | "success" | "warn" | "danger";
const tones: Record<Tone, string> = {
neutral: "bg-white/5 text-slate-400",
neutral: "bg-ink-800 text-slate-400",
brand: "bg-brand/15 text-brand-400",
accent: "bg-accent/15 text-accent-400",
success: "bg-success/15 text-success",
+1 -1
View File
@@ -33,7 +33,7 @@ export function Tabs<T extends string>({
>
{t.label}
{t.count != null && t.count > 0 && (
<span className="ml-1 rounded bg-white/5 px-1 text-[9px] text-slate-400">{t.count}</span>
<span className="ml-1 rounded bg-ink-800 px-1 text-[9px] text-slate-400">{t.count}</span>
)}
{active && <span className="absolute inset-x-2 -bottom-px h-0.5 rounded bg-brand" />}
</button>
+21 -11
View File
@@ -1,20 +1,31 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{ts,tsx}"],
darkMode: "class",
theme: {
extend: {
colors: {
// 深色 AI 控制台分层表面
// 分层表面 / 边框 / 文字均由 CSS 变量驱动(RGB 三元组,保留 /透明度 修饰符)。
// 变量在 index.css 的 :root(亮) 与 .dark(暗) 两套值间切换 —— 一处定义,全 app 换肤。
ink: {
950: "#0b0d12",
900: "#0f121a",
850: "#141824",
800: "#1a1f2d",
700: "#232a3b",
600: "#323a4f",
950: "rgb(var(--ink-950) / <alpha-value>)",
900: "rgb(var(--ink-900) / <alpha-value>)",
850: "rgb(var(--ink-850) / <alpha-value>)",
800: "rgb(var(--ink-800) / <alpha-value>)",
700: "rgb(var(--ink-700) / <alpha-value>)",
600: "rgb(var(--ink-600) / <alpha-value>)",
},
line: "#242b3c",
// 语义令牌 —— 强调色统一从这里取,便于整体换肤(紫=brand,青=accent)。
line: "rgb(var(--line) / <alpha-value>)",
slate: {
100: "rgb(var(--slate-100) / <alpha-value>)",
200: "rgb(var(--slate-200) / <alpha-value>)",
300: "rgb(var(--slate-300) / <alpha-value>)",
400: "rgb(var(--slate-400) / <alpha-value>)",
500: "rgb(var(--slate-500) / <alpha-value>)",
600: "rgb(var(--slate-600) / <alpha-value>)",
700: "rgb(var(--slate-700) / <alpha-value>)",
},
// 语义强调色 —— 两套主题通用(紫=brand,青=accent)。
brand: { DEFAULT: "#7c5cf6", 400: "#a78bfa", 500: "#8b5cf6", 600: "#6d28d9" },
accent: { DEFAULT: "#22d3ee", 400: "#22d3ee", 500: "#06b6d4" },
success: { DEFAULT: "#34d399", 500: "#10b981" },
@@ -22,14 +33,13 @@ export default {
danger: { DEFAULT: "#fb7185", 500: "#f43f5e" },
},
borderRadius: {
// 统一圆角档位
lg: "0.625rem", // 10px —— 卡片/面板
md: "0.5rem", // 8px —— 控件
},
boxShadow: {
glow: "0 0 0 1px rgba(139,92,246,0.45), 0 0 18px rgba(139,92,246,0.28)",
"glow-cyan": "0 0 0 1px rgba(34,211,238,0.4), 0 0 16px rgba(34,211,238,0.22)",
card: "0 1px 2px rgba(0,0,0,0.4), 0 8px 24px rgba(0,0,0,0.25)",
card: "var(--shadow-card)",
},
},
},