dsh-memory-eternal 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +246 -275
- package/index.js +63 -4
- package/lib/capture.js +11 -0
- package/lib/client.js +4 -4
- package/lib/vault.js +52 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,275 +1,246 @@
|
|
|
1
|
-
# 🧠
|
|
2
|
-
|
|
3
|
-
<p align="center">
|
|
4
|
-
<img src="https://img.shields.io/badge/DeepSeek%20Harness-plugin-3B82F6" alt="DSH plugin" />
|
|
5
|
-
<img src="https://img.shields.io/npm/v/dsh-memory-eternal" alt="npm version" />
|
|
6
|
-
<img src="https://img.shields.io/github/stars/EternalNight996/dsh-memory-eternal?style=flat" alt="GitHub stars" />
|
|
7
|
-
<img src="https://img.shields.io/github/license/EternalNight996/dsh-memory-eternal" alt="license" />
|
|
8
|
-
<img src="https://img.shields.io/badge
|
|
9
|
-
</p>
|
|
10
|
-
|
|
11
|
-
>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
|
28
|
-
|
|
29
|
-
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
|
69
|
-
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
>
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
|
174
|
-
|
|
|
175
|
-
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
-
|
|
189
|
-
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
-
|
|
235
|
-
-
|
|
236
|
-
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
- [ ] 卡片拖拽归类 / 批量合并更智能(按相似度预分组)
|
|
248
|
-
- [ ] Market 收录:dsh-marketplace / dsh-plugin-marketplace(待社区审核)
|
|
249
|
-
|
|
250
|
-
## 📦 发布记录
|
|
251
|
-
|
|
252
|
-
- **v0.4.3**(待发布):记忆侧**省 token / 提质量**——**沉淀预筛**:turn-stopping 先做启发式「可复用信号」判定(结构化标记/决策/代码/事实关键词/列表密度),纯闲聊或无可复用内容**直接跳过、不触发 LLM**(省掉每轮昂贵的判定调用);**召回相关性 + 精简**:`memory_recall` 现按 `minScore` 过滤弱命中(降噪声)、注入片段截断到 130 字(降体积),召回更准、更省。
|
|
253
|
-
- **v0.4.2**(已发布):修复 GitHub/npm 顶部产品演示 GIF 无法显示——压缩至 GitHub README 10MB 渲染阈值以下(8MB)。
|
|
254
|
-
- **v0.4.1**(已发布):**Marketplace 收录标准**落地——声明 `package.json#dsh.marketplace`(`profiles:["web"]` 等),README 收录规则表对齐 dsh-marketplace / dsh-find-plugin / dsh-plugin-marketplace 的**自动扫描**机制;产品展示头部 GIF(`assets/screen/dsh-memory-eternal.gif`)置顶。
|
|
255
|
-
- **v0.4.0**(已发布):图谱交互全面增强——**图例过滤**、**时间维着色**、**右键菜单**、**Shift 框选多选**、**单删/批量删除**、**多卡合并**、**一键导出 PNG**(预览:下载/另存为/复制/全屏);知识卡——**Markdown 渲染**、**长卡折叠**、**新卡角标**、**搜索命中高亮**、**卡片行删除**、**无限滚动懒加载**、最近/标题/热点排序、自定义滚动条;**相似度关联建议**(similar 边);**记忆库导出/导入**(MD/JSON 备份,导出可选**自选位置**);操作**顶部醒目绿/红 toast**(反馈结果 + 导出位置/文件名);默认视图与节点调大。
|
|
256
|
-
- **v0.3.0**:知识图谱**科学布局**升级——多环径向/力导向混合布局(按 kind 分扇区、枢纽在内圈、环间距不重叠,数量大也不乱);只保留**高影响力主干边**(避免毛球);默认只显示**枢纽标签**、放大后全量;大图关闭节点发光提升性能。
|
|
257
|
-
- **v0.2.9**:修复 **GitHub 源安装无法启动**——提交 `lib/client.js`(此前被忽略未入库,GitHub 拉取的包缺客户端 bundle,导致 DSH 启动失败)。
|
|
258
|
-
- **v0.2.8**:安装说明去掉 `npx` 前缀,只保留跨平台 `dsh plugin --profile web add`。
|
|
259
|
-
- **v0.2.7**:安装说明跨平台化——主推 `dsh plugin --profile web add`(无需 cd/平台路径),注明官方 registry(避免 npmmirror 滞后)与 pnpm 备选。
|
|
260
|
-
- **v0.2.6**:安装/更新说明补充——DSH profile 为 pnpm workspace,用 `pnpm add dsh-memory-eternal@latest`(勿用 `npm install`,会因 `link:` 报 `EUNSUPPORTEDPROTOCOL`)。
|
|
261
|
-
- **v0.2.5**:安装方式统一为 `dsh plugin --profile web add`(README 标注 npx 备选写法)。
|
|
262
|
-
- **v0.2.4**:知识库分类扩展——新增「工具 Tools」(07) 与「教训 Mistakes」(08) 两类(错误/反模式/踩坑/debug 教训),前端筛选、图谱着色、自动捕获提示词同步更新。
|
|
263
|
-
- **v0.2.3**:放宽兼容性——`@deepseek-ai/dsh-tools` / `dsh-llm` peer 依赖改为 `>=0.1.0-rc.2 <0.2.0`,跨整个 `0.1.x` 系列均兼容(大多数 DSH 版本可直接安装)。
|
|
264
|
-
- **v0.2.2**:放宽 `@deepseek-ai/dsh-tools` / `dsh-llm` peer 依赖至 `^0.1.0-rc.7`,同时兼容旧版与新版 DSH。
|
|
265
|
-
- **v0.2.1**:更新 `@deepseek-ai/dsh-tools` / `dsh-llm` peer 依赖至 `^0.1.1-rc.2`(对齐当前 DSH 运行时)。
|
|
266
|
-
- **v0.2.0**:新增侧边栏「记忆」入口 + 完整记忆库弹窗;增强知识图谱(力导向/径向布局、滚轮缩放、左键拖拽平移、左键凸显关联/右键打开卡片、大规模抗压:节点/连线上限 + 标签节流 + O(n) 径向布局);README 头部动态演示 GIF。
|
|
267
|
-
- **v0.1.0**:首个可用版本。对话自动沉淀(turn-stopping 钩子 + LLM 压缩 + 双层去重)、`memory_recall` 工具、system prompt 召回段、Markdown Vault(02-08 分类 + frontmatter)、CJK 检索、自研知识图谱、设置页图形化知识库。
|
|
268
|
-
|
|
269
|
-
## 📄 License
|
|
270
|
-
|
|
271
|
-
MIT
|
|
272
|
-
|
|
273
|
-
---
|
|
274
|
-
|
|
275
|
-
> 让 DSH 拥有第二大脑:**对话自动沉淀,知识随手可查。** 🧠
|
|
1
|
+
# 🧠 最强记忆大脑 dsh-memory-eternal — 给 DeepSeek Harness 装上「第二大脑」
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://img.shields.io/badge/DeepSeek%20Harness-plugin-3B82F6" alt="DSH plugin" />
|
|
5
|
+
<img src="https://img.shields.io/npm/v/dsh-memory-eternal" alt="npm version" />
|
|
6
|
+
<img src="https://img.shields.io/github/stars/EternalNight996/dsh-memory-eternal?style=flat" alt="GitHub stars" />
|
|
7
|
+
<img src="https://img.shields.io/github/license/EternalNight996/dsh-memory-eternal" alt="license" />
|
|
8
|
+
<img src="https://img.shields.io/badge/自研内核-CJK检索-10B981" alt="self-built" />
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
> **对话结束自动沉淀,跨会话真实不失忆;召回只取相关小块,省 token、少噪音。**
|
|
12
|
+
> 全自研、零第三方记忆框架、不改 DSH 源码、一条命令安装——为你装一个「自己说了算」的、可 git 管理的本地记忆库。
|
|
13
|
+
|
|
14
|
+
<p align="center"><strong>⭐ 觉得好用就点个 Star</strong>,让更多被「AI 总忘事」困扰的人用上它。<br/><sub>一条命令:<code>dsh plugin --profile web add dsh-memory-eternal</code></sub></p>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img src="assets/screen/dsh-memory-eternal.gif" width="880" alt="记忆核心 · 对话自动沉淀 + 图形化知识库 + 增强知识图谱(动态演示)" />
|
|
20
|
+
<br/><em>对话自动沉淀 · 图形化知识库 · 交互式知识图谱(动态演示)</em>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🔥 先看痛点:AI 助手为什么「总让人失望」?
|
|
26
|
+
|
|
27
|
+
| # | 痛点(每个用 AGENT 的人都遇到过) | 没有记忆的后果 |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| 1 | **长对话就忘事** | 聊到第 10 轮,前面定的方案、改的口径全忘了,反复重来 |
|
|
30
|
+
| 2 | **跨会话失忆** | 换新会话 = 白纸一张,项目背景要重新讲一遍 |
|
|
31
|
+
| 3 | **长会话 token 暴涨** | 上下文越来越长,越聊越贵、越聊越慢,甚至爆窗 |
|
|
32
|
+
| 4 | **存了却找不到** | 就算有笔记,也搜不到、管不好,等于没存 |
|
|
33
|
+
| 5 | **数据被锁死** | 记忆存在私有库里,改不了、导不出、不敢用 |
|
|
34
|
+
|
|
35
|
+
> 这不是你的错觉,是**绝大多数 AI 产品共通的硬伤**。而解法,正是 Agent 记忆——**总结 → 存储 → 按需召回**。
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 🚀 装上它之后:五道痛点逐一被解决
|
|
40
|
+
|
|
41
|
+
| 痛点 | 装上记忆核心后 | 靠什么实现 |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| ① 长对话忘事 | 保留「最近原文」,旧轮次自动总结成卡 | 自动沉淀 + 语义去重 |
|
|
44
|
+
| ② 跨会话失忆 | 新会话也能召回旧决策/方案 | `memory_recall` 按需召回 |
|
|
45
|
+
| ③ 长会话 token | **只召回相关小块**,不每轮全量塞 | 相关性阈值 + 片段截断 + 沉淀预筛 |
|
|
46
|
+
| ④ 存了找不到 | 中文整词+bigram 检索 + 可视化图谱 | CJK 感知检索 + 知识图谱 |
|
|
47
|
+
| ⑤ 数据被锁死 | 纯 Markdown,可读可改可 git 托管 | 本地 Markdown Vault |
|
|
48
|
+
|
|
49
|
+
```mermaid
|
|
50
|
+
flowchart LR
|
|
51
|
+
A["对话每一轮"] --> B{"turn-stopping<br/>有没有可复用信号?"}
|
|
52
|
+
B -- "无 → 跳过(省LLM)" --> X["❌ 不调模型"]
|
|
53
|
+
B -- "有" --> C["LLM 压缩成知识卡"]
|
|
54
|
+
C --> D{"去重核对"}
|
|
55
|
+
D -- "同一主题" --> E["追加更新到已有卡"]
|
|
56
|
+
D -- "新主题" --> F["写入 02-08 分类目录"]
|
|
57
|
+
F & E --> G["本地 Markdown Vault"]
|
|
58
|
+
G --> H["memory_recall 按需召回"]
|
|
59
|
+
H --> I["AI 下次不再失忆"]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 🧬 核心内核设计:为什么要「全自研」
|
|
65
|
+
|
|
66
|
+
市面上记忆方案很多,但大多**依赖第三方框架 / 动不动就起一个 MCP 服务 / 把记忆锁进私有库**。本插件把「记忆骨架」整个自己搭出来,**零第三方运行时依赖**,逻辑能逐行看懂:
|
|
67
|
+
|
|
68
|
+
| 模块 | 自研实现 | 替代什么 |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| **去重** | 词法 Jaccard bigram(0.62)+ **语义去重**(把已有卡索引喂模型,让它决定「新建 vs 追加」) | 防重复、防 LLM 改写导致的垃圾卡 |
|
|
71
|
+
| **检索** | CJK 感知:中文整词 + 字符 bigram 命中 | 无需全文搜索引擎,短查询也能命中长文 |
|
|
72
|
+
| **图谱** | 力导向/径向布局 + `[[wikilink]]`/共享标签/相似度连边 | 让「知识之间怎么关联」一眼看清 |
|
|
73
|
+
| **存储** | 带 frontmatter 的普通 `.md`,落盘 `~/.dsh/memory-vault` | 不锁库、可读、可 git、可被任意工具读 |
|
|
74
|
+
|
|
75
|
+
> **与其他热门记忆项目的思路同向(总结→存储→按需召回),但定位不同。** 参考同类热门思路,本插件主打「**本地、自研、零依赖、可读可控**」:
|
|
76
|
+
|
|
77
|
+
| 热门项目 | 定位 | 本插件的差异 |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| [agentmemory](https://github.com/rohitg00/agentmemory) | 通用 Agent 记忆服务 | 本插件是 **DSH 原生插件**,不另起服务,直接进设置/侧边栏 |
|
|
80
|
+
| [mem0](https://github.com/mem0ai/mem0) | 记忆层(常配 MCP) | 本插件**无 MCP 依赖**,离线可跑,走本地 `memory_recall` 工具 |
|
|
81
|
+
| [MemGPT / Letta](https://github.com/letta-ai/letta) | 上下文内存管理 | 本插件承载「**持久记忆**」这一层;会话内实时压缩属 harness 层 |
|
|
82
|
+
| **[Zep](https://github.com/getzep/zep)** | 时间/图记忆 | 本插件零外部服务,图谱/检索纯本地 |
|
|
83
|
+
|
|
84
|
+
> 如果你已在用其中某一层,也完全能把本插件当「**本地持久记忆底座**」叠加使用——它只消费你已配置好的 provider,不抢模型、不改你的习惯。
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## ✨ 功能总览(交互全在这里)
|
|
89
|
+
|
|
90
|
+
<details>
|
|
91
|
+
<summary><b>📇 知识卡:存得进、找得到、管得了</b></summary>
|
|
92
|
+
|
|
93
|
+
- **全自动沉淀**:每轮结束自动判定+压缩成 300-800 字知识卡,无需手动保存
|
|
94
|
+
- **双层去重**:Jaccard 词法 + 语义追加,不堆重复卡
|
|
95
|
+
- **`.md` 全文**:frontmatter 里带 `kind/title/tags/created/updated`,就是普通 Markdown
|
|
96
|
+
- **Markdown 渲染**、**长卡折叠**、**新卡「新」角标**、**搜索命中高亮**、**卡片行删除**
|
|
97
|
+
- **无限滚动懒加载**、自定义**滚动条**配色
|
|
98
|
+
- 排序:**最近 / 标题 / 热点(按连接数)**
|
|
99
|
+
|
|
100
|
+
</details>
|
|
101
|
+
|
|
102
|
+
<details>
|
|
103
|
+
<summary><b>🕸 知识图谱:秒级看懂「知识之间的关系」</b></summary>
|
|
104
|
+
|
|
105
|
+
- **力导向/径向混合布局**:按 kind 分扇区、枢纽在内圈、环间距不重叠,数量大也不乱;**几千节点不卡**
|
|
106
|
+
- 连线 = `[[wikilink]]` / 共享标签 / **相似度关联**;只保留**高影响力主干边**避免毛球
|
|
107
|
+
- 交互:**左键凸显关联**、**右键菜单**(打开/凸显/复制/删除)、**Shift 框选多选**(导出/删除/合并)、**图例过滤**、**时间维着色**(3天内绿/2周琥珀/1月橙/更早灰蓝)、**滚轮缩放**、**导图 PNG**(下载/另存为/复制/全屏)
|
|
108
|
+
|
|
109
|
+
</details>
|
|
110
|
+
|
|
111
|
+
<details>
|
|
112
|
+
<summary><b>💾 备份与控制:数据永远是你的</b></summary>
|
|
113
|
+
|
|
114
|
+
- 记忆库**导出 MD / JSON**、**导入**(自动去重)
|
|
115
|
+
- 导出可选**默认下载**或**自选位置**另存为
|
|
116
|
+
- 所有操作**顶部醒目 toast**:✓ 绿 = 成功、✕ 红 = 失败,并显示导出位置/文件名
|
|
117
|
+
- 可指向任意目录(如 Obsidian Vault)
|
|
118
|
+
</details>
|
|
119
|
+
|
|
120
|
+
<p align="center">
|
|
121
|
+
<img src="assets/screen/memory-settings.png" width="840" alt="设置 → 记忆:统计 + 检索 + 卡片/图谱 Tab" />
|
|
122
|
+
<br/><em>设置 → 记忆:统计概览、CJK 检索、知识卡网格、按 kind 分类</em>
|
|
123
|
+
</p>
|
|
124
|
+
<p align="center">
|
|
125
|
+
<img src="assets/screen/memory-popup.png" width="840" alt="侧边栏「记忆」一键打开完整记忆库弹窗" />
|
|
126
|
+
<br/><em>侧边栏底部「记忆」按钮 → 一键打开完整记忆库弹窗(含增强知识图谱)</em>
|
|
127
|
+
</p>
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 🚀 安装(一条命令)
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# 已发布后(npm)—— 跨平台通用
|
|
135
|
+
dsh plugin --profile web add dsh-memory-eternal
|
|
136
|
+
|
|
137
|
+
# 从 GitHub
|
|
138
|
+
dsh plugin --profile web add github:EternalNight996/dsh-memory-eternal
|
|
139
|
+
|
|
140
|
+
# 本地联调(改代码即时生效)
|
|
141
|
+
dsh plugin --profile web add F:/MyApp/eternal/dsh-memory-eternal
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
> 提示:
|
|
145
|
+
> - `dsh` 为 DSH CLI(`npm i -g @deepseek-ai/dsh`)。
|
|
146
|
+
> - 要**官方最新**(避免 npmmirror 滞后):`npm_config_registry=https://registry.npmjs.org/ dsh plugin --profile web add dsh-memory-eternal`。
|
|
147
|
+
> - **profile 是 pnpm workspace**:`cd ~/.dsh/profiles/web && pnpm add dsh-memory-eternal@latest`(`npm install` 会报 `EUNSUPPORTEDPROTOCOL`)。
|
|
148
|
+
|
|
149
|
+
装完**重启 dsh web**:设置 → 记忆 出现知识库;侧边栏底部出现「记忆」按钮。此后每轮对话自动沉淀。
|
|
150
|
+
|
|
151
|
+
### 🧭 插件发现 / 收录标准
|
|
152
|
+
|
|
153
|
+
仓库已打 **GitHub `dsh-plugin` topic** 并符合社区标准结构,**自动被以下机制发现**(无需手动 PR):
|
|
154
|
+
|
|
155
|
+
| 机制 | 收录方式 | 状态 |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| **dsh-marketplace**(ouyangyipeng) | 实时读 `topic:dsh-plugin` | ✅ topic 已打 |
|
|
158
|
+
| **dsh-find-plugin**(awesome-dsh-plugin) | 会话内按 topic+星数搜索 | ✅ |
|
|
159
|
+
| **dsh-plugin-marketplace**(YELEBAI) | 每 2h 自动扫描 + 静态验证进入 Registry | ✅ 已声明 `dsh.marketplace` 元数据 |
|
|
160
|
+
| **dsh-market**(2BingLing) | 提交 Issue | ✅ 已提 |
|
|
161
|
+
| **awesome-dsh-plugin** | 提交 PR | ✅ 已提 |
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 🔧 设置
|
|
166
|
+
|
|
167
|
+
| 设置项 | 默认 | 说明 |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| 启用 | 开 | 总开关 |
|
|
170
|
+
| 自动沉淀 | 开 | 每轮对话结束自动捕获 |
|
|
171
|
+
| 自动召回 | 开 | 注入 system prompt + `memory_recall` 工具 |
|
|
172
|
+
| 记忆库目录 | 空 | 留空 = `~/.dsh/memory-vault`;可指向任意目录(如 Obsidian Vault) |
|
|
173
|
+
| 去重阈值 | 0.62 | 词法去重 Jaccard 相似度阈值 |
|
|
174
|
+
| 捕获最小长度 | 200 | 少于该字符数不触发捕获 |
|
|
175
|
+
| 日配额 | 60 | 每天最多自动写卡数,防止烧 token |
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 🛠 开发 / 构建 / 测试
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
npm i
|
|
183
|
+
npm test # 单元测试:vault 去重/检索/图谱 + capture 管线 + API 形状
|
|
184
|
+
npm run build # 打包 client(改 src/client 后需要;改 index.js / lib 无需构建)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
dsh-memory-eternal/
|
|
189
|
+
├── index.js # host:设置 + turn-stopping 钩子 + 工具 + JSON API
|
|
190
|
+
├── lib/
|
|
191
|
+
│ ├── vault.js # 存储层(frontmatter/去重/检索/图谱)
|
|
192
|
+
│ └── capture.js # 自动沉淀管线(LLM 压缩 + 语义去重 + 预筛)
|
|
193
|
+
├── src/client/index.tsx # client:设置页 + 侧边栏「记忆」+ 完整记忆库弹窗
|
|
194
|
+
├── build.mjs # esbuild 打包 client → lib/client.js
|
|
195
|
+
├── cordis.patch.yml # bundle 补丁层(自动挂载 host 行)
|
|
196
|
+
└── tests/ # node:test 单测
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 🗺 Roadmap / 待办(记忆侧核心优化)
|
|
202
|
+
|
|
203
|
+
**已实现**:
|
|
204
|
+
- 自动沉淀 + 双层去重 + 日配额
|
|
205
|
+
- CJK 检索 + 图谱 + 召回工具
|
|
206
|
+
- 交互图谱(图例/时间维/框选/删除合并/导出)
|
|
207
|
+
- 知识卡管理(MD渲染/折叠/新角标/搜索高亮/无限滚动)
|
|
208
|
+
- 备份导入导出(MD/JSON)
|
|
209
|
+
- **沉淀预筛**(skip LLM)+ **召回相关性/截断**(v0.4.3)
|
|
210
|
+
- **注入体积可配置**(召回条数/摘要长度/是否带正文,设置项)
|
|
211
|
+
- **多 Vault / 多 Profile**(命名分库,当前激活一个)
|
|
212
|
+
- **每日回顾 + 用量统计**(今日新增/近7天/分类/今日沉淀清单)
|
|
213
|
+
- **整理建议**(相似卡对合并/陈旧卡,非破坏预览)
|
|
214
|
+
- **召回用户反馈**(`/feedback`,为相关性调优留数据)
|
|
215
|
+
- **会话预算**(`sessionBudgetChars` + `/budget`)
|
|
216
|
+
|
|
217
|
+
**待办(核心功能优化)**:
|
|
218
|
+
- [x] ~~多 Vault / 多 Profile~~、~~注入体积可配置~~、~~每日回顾+用量~~、~~自动归档整理~~、~~召回用户反馈~~、~~会话级 token 预算(接口+配置)~~(v0.4.5)
|
|
219
|
+
- [ ] **上下文压缩真正落地**:记忆侧已提供 `/compress` 压缩产物接口,待 harness 在会话内接「召回替代 + 压缩旧轮」(需 harness 配合)
|
|
220
|
+
- [ ] **语义召回升级**:`recallEmbedding` 接口已预留,接入 embedding provider 后召回更精
|
|
221
|
+
- [ ] 自动归档的**批量智能合并**(按相似度预分组后一键合并多张)
|
|
222
|
+
- [ ] 多 Vault 的**图谱/检索跨库聚合**
|
|
223
|
+
- [ ] 每日回顾的**定时推送**(主动提醒而非手动查看)
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## 📦 发布记录
|
|
228
|
+
|
|
229
|
+
- **v0.4.5**:记忆侧**核心功能优化**——**注入体积可配置**(召回条数/摘要长度/是否带正文)、**多 Vault/多 Profile**(命名分库)、**每日回顾 + 用量统计**(管理面板:用量/今日/整理建议)、**自动归档整理建议**(相似卡对合并/陈旧卡,非破坏预览)、**召回用户反馈**、**会话级 token 预算**(`sessionBudgetChars` + `/budget`)、**上下文压缩接口**(`/compress`,供 harness 会话内压缩)。
|
|
230
|
+
- **v0.4.3**:记忆侧**省 token/提质量**——沉淀预筛(无可复用信号直接跳过、不触发 LLM);召回按 `minScore` 过滤弱命中、注入片段截断到 130 字。
|
|
231
|
+
- **v0.4.2**:修复 GitHub/npm 顶部演示 GIF 无法显示(压缩至 README 10MB 阈值以下,8MB)。
|
|
232
|
+
- **v0.4.1**:Marketplace 收录标准落地(`dsh.marketplace` 元数据 + 自动扫描规则说明)。
|
|
233
|
+
- **v0.4.0**:图谱交互全面增强(图例过滤/时间维/右键菜单/框选/删除合并/导出PNG另存自选位置)+ 知识卡增强(MD渲染/长卡折叠/新角标/搜索高亮/删除/无限滚动)+ 相似度关联 + 导入导出 + 顶部绿红 toast。
|
|
234
|
+
- **v0.3.0**:知识图谱科学布局(多环径向/主干边/默认仅枢纽标签/大图关发光)。
|
|
235
|
+
- **v0.2.x**:GitHub 源安装修复、安装说明跨平台化/去 npx、分类扩展(工具/教训)、peer 依赖放宽。
|
|
236
|
+
- **v0.1.0**:首个可用版本(自动沉淀 + 召回工具 + Markdown Vault + CJK 检索 + 图谱 + 设置页)。
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## 📄 License
|
|
241
|
+
|
|
242
|
+
MIT
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
> **让 DSH 真正记住你:对话自动沉淀,知识随手可查。** ⭐ 觉得有用就点个 Star,Let's make AI not forget.
|
package/index.js
CHANGED
|
@@ -19,7 +19,8 @@ import path from 'node:path'
|
|
|
19
19
|
import os from 'node:os'
|
|
20
20
|
import z from '@deepseek-ai/schemastery'
|
|
21
21
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
22
|
-
import { ensureVault, listCards, readCard, search, graph, overview, exportCards, deleteCard, writeCard, parseCard } from './lib/vault.js'
|
|
22
|
+
import { ensureVault, listCards, readCard, search, graph, overview, exportCards, deleteCard, writeCard, parseCard, stats, optimizeCandidates, readFeedback, addFeedback } from './lib/vault.js'
|
|
23
|
+
import { compressExcerpt } from './lib/capture.js'
|
|
23
24
|
import { summarizeTurn, extractLastTurn, sliceNewEvents, resolveRoute, captureCard, captureUpdate, pickNeighbors } from './lib/capture.js'
|
|
24
25
|
|
|
25
26
|
export const name = 'memory-eternal'
|
|
@@ -34,6 +35,17 @@ export const Config = z.object({
|
|
|
34
35
|
captureMinChars: z.number().default(200),
|
|
35
36
|
captureCooldownMs: z.number().default(5 * 60 * 1000),
|
|
36
37
|
maxCardsPerDay: z.number().default(60),
|
|
38
|
+
// 注入体积可配置(召回)
|
|
39
|
+
recallLimit: z.number().min(1).max(20).default(5),
|
|
40
|
+
recallSummaryLen: z.number().min(40).max(400).default(130),
|
|
41
|
+
recallIncludeBody: z.boolean().default(false),
|
|
42
|
+
// 多 Vault / 多 Profile:命名分库,当前激活一个
|
|
43
|
+
vaultProfiles: z.array(z.object({ name: z.string(), path: z.string() })).default([]),
|
|
44
|
+
activeVault: z.string().default(''),
|
|
45
|
+
// 语义召回(可选 embedding provider,默认空=零依赖 bigram + LLM 判定兜底)
|
|
46
|
+
recallEmbedding: z.string().default(''),
|
|
47
|
+
// 会话级 token 预算(字符),供 harness 触发压缩/轮换;记忆侧提供估算与阈值
|
|
48
|
+
sessionBudgetChars: z.number().default(80000),
|
|
37
49
|
})
|
|
38
50
|
|
|
39
51
|
const API_PREFIX = '/memory-eternal/api'
|
|
@@ -43,6 +55,11 @@ export function apply(ctx, config) {
|
|
|
43
55
|
|
|
44
56
|
const vaultDir = () => {
|
|
45
57
|
const cfg = settings.get() ?? {}
|
|
58
|
+
// 多 Vault:若配了 vaultProfiles 且选中了 activeVault,则用该 profile 的目录。
|
|
59
|
+
const profiles = Array.isArray(cfg.vaultProfiles) ? cfg.vaultProfiles : []
|
|
60
|
+
const active = String(cfg.activeVault || '').trim()
|
|
61
|
+
const hit = active && profiles.find((p) => p.name === active)
|
|
62
|
+
if (hit && hit.path && hit.path.trim()) return path.resolve(hit.path.trim())
|
|
46
63
|
if (cfg.vaultDir && cfg.vaultDir.trim()) return path.resolve(cfg.vaultDir.trim())
|
|
47
64
|
const home = process.env.DSH_HOME || path.join(os.homedir(), '.dsh')
|
|
48
65
|
return path.join(home, 'memory-vault')
|
|
@@ -187,13 +204,19 @@ export function apply(ctx, config) {
|
|
|
187
204
|
if (!cfg.enabled) return '(记忆核心已禁用)'
|
|
188
205
|
const query = String(args.query || '').trim()
|
|
189
206
|
if (!query) return '(未提供检索词)'
|
|
190
|
-
const
|
|
207
|
+
const cfg2 = settings.get() ?? {}
|
|
208
|
+
const defLimit = Number(cfg2.recallLimit) || 5
|
|
209
|
+
const defLen = Number(cfg2.recallSummaryLen) || 130
|
|
210
|
+
const includeBody = cfg2.recallIncludeBody === true
|
|
211
|
+
const limit = Math.min(Math.max(Number(args.limit) || defLimit, 1), 20)
|
|
191
212
|
const hits = await search(vaultDir(), query, { limit, minScore: 2 })
|
|
192
213
|
if (hits.length === 0) return `记忆库中没有与「${query}」相关的内容。`
|
|
193
214
|
const lines = hits.map((h, i) => {
|
|
194
215
|
const tags = h.tags.length ? ` [${h.tags.join(', ')}]` : ''
|
|
195
|
-
const snippet = String(h.summary
|
|
196
|
-
|
|
216
|
+
const snippet = String(includeBody ? h.summary : h.summary || '')
|
|
217
|
+
.replace(/\s+/g, ' ').trim().slice(0, defLen)
|
|
218
|
+
const body = includeBody ? `\n${String(h.excerpt || '')}` : ''
|
|
219
|
+
return `### ${i + 1}. ${h.title}${tags}\n路径:${h.path}\n${snippet}${body}`
|
|
197
220
|
})
|
|
198
221
|
return `从记忆核心检索到 ${hits.length} 条相关卡片:\n\n${lines.join('\n\n')}`
|
|
199
222
|
},
|
|
@@ -320,6 +343,42 @@ async function handleApi(req, res, vaultRoot) {
|
|
|
320
343
|
json(res, 200, { ok: true })
|
|
321
344
|
return
|
|
322
345
|
}
|
|
346
|
+
case '/stats': {
|
|
347
|
+
await ensureVault(vaultRoot)
|
|
348
|
+
json(res, 200, { ok: true, ...(await stats(vaultRoot)) })
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
case '/optimize': {
|
|
352
|
+
// 非破坏性:只返回「整理建议」(相似卡对 + 陈旧卡),不自动删改。
|
|
353
|
+
json(res, 200, { ok: true, ...(await optimizeCandidates(vaultRoot)) })
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
case '/feedback': {
|
|
357
|
+
let raw = ''
|
|
358
|
+
for await (const chunk of req) raw += chunk
|
|
359
|
+
let rec
|
|
360
|
+
try { rec = JSON.parse(raw || '{}') } catch { return json(res, 400, { ok: false, error: 'JSON 解析失败' }) }
|
|
361
|
+
if (!rec || !rec.path) return json(res, 400, { ok: false, error: '缺少 path' })
|
|
362
|
+
await addFeedback(vaultRoot, { query: String(rec.query || ''), path: String(rec.path || ''), useful: rec.useful === true })
|
|
363
|
+
json(res, 200, { ok: true })
|
|
364
|
+
return
|
|
365
|
+
}
|
|
366
|
+
case '/budget': {
|
|
367
|
+
const cfg = settings.get() ?? {}
|
|
368
|
+
json(res, 200, { ok: true, budgetChars: cfg.sessionBudgetChars ?? 80000, recallLimit: cfg.recallLimit ?? 5, embedding: cfg.recallEmbedding || '' })
|
|
369
|
+
return
|
|
370
|
+
}
|
|
371
|
+
case '/compress': {
|
|
372
|
+
// 记忆侧「压缩产物」接口:供 harness 在会话内压缩旧轮次时调用,返回一段可注入的摘要。
|
|
373
|
+
const cfg = settings.get() ?? {}
|
|
374
|
+
const body = new URLSearchParams(query)
|
|
375
|
+
const text = body.get('text') || ''
|
|
376
|
+
const maxChars = Math.min(Number(body.get('max')) || 2400, 6000)
|
|
377
|
+
if (!text.trim()) return json(res, 400, { ok: false, error: '缺少 text' })
|
|
378
|
+
const compressed = await compressExcerpt(text, maxChars)
|
|
379
|
+
json(res, 200, { ok: true, compressed, budgetChars: cfg.sessionBudgetChars ?? 80000 })
|
|
380
|
+
return
|
|
381
|
+
}
|
|
323
382
|
default:
|
|
324
383
|
json(res, 404, { ok: false, error: '未知接口' })
|
|
325
384
|
}
|
package/lib/capture.js
CHANGED
|
@@ -221,6 +221,17 @@ export function sliceNewEvents(events, lastSeq) {
|
|
|
221
221
|
return out
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
/** 解析当前模型路由:取第一个 provider 的旗舰模型(与驯兽师一致)。 */
|
|
225
|
+
/** 记忆侧「压缩产物」接口:确定性(无 LLM)从一段文本抽出结构化关键内容,供 harness 会话内压缩旧轮次时注入。 */
|
|
226
|
+
export function compressExcerpt(text, maxChars = 2400) {
|
|
227
|
+
const lines = String(text || '').split(/\r?\n/).map((L) => L.trim()).filter(Boolean)
|
|
228
|
+
const structured = lines.filter((L) => /^(#{1,6}\s|[-*]\s|\d+[.、)]\s|>|`|\[x\]|\[ \])/.test(L))
|
|
229
|
+
const src = structured.length ? structured : lines
|
|
230
|
+
let out = src.join('\n')
|
|
231
|
+
if (out.length > maxChars) out = out.slice(0, maxChars).replace(/\n+$/, '') + '\n…(已压缩,细节见记忆库)'
|
|
232
|
+
return out
|
|
233
|
+
}
|
|
234
|
+
|
|
224
235
|
/** 解析当前模型路由:取第一个 provider 的旗舰模型(与驯兽师一致)。 */
|
|
225
236
|
export async function resolveRoute(llm) {
|
|
226
237
|
try {
|
package/lib/client.js
CHANGED
|
@@ -3,7 +3,7 @@ window.__ModuleLoader__.load({
|
|
|
3
3
|
factory: (require) => {
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
|
-
var
|
|
6
|
+
var st=Object.create;var Oe=Object.defineProperty;var lt=Object.getOwnPropertyDescriptor;var dt=Object.getOwnPropertyNames;var ct=Object.getPrototypeOf,pt=Object.prototype.hasOwnProperty;var mt=(a,s)=>{for(var p in s)Oe(a,p,{get:s[p],enumerable:!0})},tt=(a,s,p,b)=>{if(s&&typeof s=="object"||typeof s=="function")for(let r of dt(s))!pt.call(a,r)&&r!==p&&Oe(a,r,{get:()=>s[r],enumerable:!(b=lt(s,r))||b.enumerable});return a};var ut=(a,s,p)=>(p=a!=null?st(ct(a)):{},tt(s||!a||!a.__esModule?Oe(p,"default",{value:a,enumerable:!0}):p,a)),gt=a=>tt(Oe({},"__esModule",{value:!0}),a);var Et={};mt(Et,{apply:()=>xt,inject:()=>ft});module.exports=gt(Et);var d=ut(require("react"),1),e=require("react/jsx-runtime"),Me="memory-eternal",q="/memory-eternal/api",ft=["settingsScope","slots","locale","connection","remote"],bt={nav:"\u8BB0\u5FC6",loading:"\u52A0\u8F7D\u4E2D\u2026",refresh:"\u5237\u65B0",overview:"\u8BB0\u5FC6\u5E93\u6982\u89C8",total:"\u77E5\u8BC6\u5361",recent:"\u8FD1 7 \u5929\u65B0\u589E",tags:"\u6807\u7B7E",searchPlaceholder:"\u641C\u7D22\u8BB0\u5FC6\uFF08\u652F\u6301\u4E2D\u6587\u7247\u6BB5\uFF09\u2026",search:"\u641C\u7D22",all:"\u5168\u90E8",kindProject:"\u9879\u76EE",kindKnowledge:"\u77E5\u8BC6",kindContent:"\u5185\u5BB9",kindPrompt:"\u63D0\u793A\u8BCD",kindBusiness:"\u4E1A\u52A1",kindTool:"\u5DE5\u5177",kindMistake:"\u6559\u8BAD",cardsTab:"\u77E5\u8BC6\u5361",graphTab:"\u77E5\u8BC6\u56FE\u8C31",graph:"\u77E5\u8BC6\u56FE\u8C31",graphHint:"\u8282\u70B9=\u77E5\u8BC6\u5361\uFF1B\u8FDE\u7EBF=[[\u94FE\u63A5]] \u6216\u5171\u4EAB\u6807\u7B7E\u3002\u70B9\u51FB\u8282\u70B9\u9605\u8BFB\u5361\u7247\u3002",graphTip:"\u5DE6\u952E\u51F8\u663E \xB7 Shift+\u62D6\u62FD\u6846\u9009 \xB7 \u53F3\u952E\u83DC\u5355 \xB7 \u6EDA\u8F6E\u7F29\u653E \xB7 \u62D6\u62FD\u5E73\u79FB",rebuild:"\u91CD\u5EFA",reset:"\u91CD\u7F6E",expandAll:"\u5168\u90E8\u5C55\u5F00",capped:"\u5DF2\u5C55\u793A\u6700\u6838\u5FC3\u7684",clearFocus:"\u53D6\u6D88\u9AD8\u4EAE",empty:"\u8BB0\u5FC6\u5E93\u8FD8\u662F\u7A7A\u7684\u3002\u591A\u804A\u51E0\u8F6E\u540E\uFF0C\u503C\u5F97\u4FDD\u5B58\u7684\u5185\u5BB9\u4F1A\u81EA\u52A8\u6C89\u6DC0\u6210\u77E5\u8BC6\u5361\u3002",emptyGraph:"\u56FE\u8C31\u6682\u65E0\u6570\u636E\u3002",open:"\u9605\u8BFB",updated:"\u66F4\u65B0\u4E8E",created:"\u521B\u5EFA\u4E8E",source:"\u6765\u6E90",close:"\u5173\u95ED",vaultDir:"\u8BB0\u5FC6\u5E93\u76EE\u5F55",capture:"\u81EA\u52A8\u6C89\u6DC0",recall:"\u81EA\u52A8\u53EC\u56DE",enabled:"\u5DF2\u542F\u7528",disabled:"\u5DF2\u7981\u7528",error:"\u52A0\u8F7D\u5931\u8D25",back:"\u8FD4\u56DE\u5217\u8868",cardCount:"\u5F20\u5361",nodes:"\u8282\u70B9",edges:"\u8FDE\u7EBF",memoryTitle:"\u8BB0\u5FC6\u5E93",memoryHint:"\u4F60\u7684\u672C\u5730\u7B2C\u4E8C\u5927\u8111",zoomIn:"\u653E\u5927",zoomOut:"\u7F29\u5C0F",fit:"\u9002\u5E94",exportGraph:"\u5BFC\u51FA",openCard:"\u6253\u5F00\u5361\u7247",focusNeighbors:"\u51F8\u663E\u5173\u8054",copyName:"\u590D\u5236\u540D\u79F0",noMatch:"\u6CA1\u6709\u5339\u914D\u7684\u8BB0\u5FC6",clearFilter:"\u6E05\u9664\u8FC7\u6EE4",sortRecent:"\u6700\u8FD1",sortTitle:"\u6807\u9898",sortHot:"\u70ED\u70B9",exportSel:"\u5BFC\u51FA\u9009\u4E2D",clearSelection:"\u6E05\u7A7A\u9009\u62E9",download:"\u4E0B\u8F7D",openNewTab:"\u65B0\u6807\u7B7E\u6253\u5F00",copyImage:"\u590D\u5236\u56FE\u7247",done:"\u5B8C\u6210",downloaded:"\u5DF2\u5F00\u59CB\u4E0B\u8F7D",openedTab:"\u5DF2\u5728\u65B0\u6807\u7B7E\u6253\u5F00",copied:"\u5DF2\u590D\u5236",exportedSel:"\u4E2A\u8282\u70B9\u5DF2\u5BFC\u51FA",fullscreen:"\u5168\u5C4F",exitFull:"\u9000\u51FA\u5168\u5C4F",copyFail:"\u590D\u5236\u5931\u8D25",timeDim:"\u65F6\u95F4\u7EF4",timeNew:"3 \u5929\u5185",timeRecent:"2 \u5468\u5185",timeMonth:"1 \u6708\u5185",timeOld:"\u66F4\u65E9",newBadge:"\u65B0",expand:"\u5C55\u5F00\u5168\u90E8",collapse:"\u6536\u8D77",exportVault:"\u5BFC\u51FAMD",exportJson:"\u5BFC\u51FAJSON",exporting:"\u5BFC\u51FA\u4E2D\u2026",exportedVault:"\u8BB0\u5FC6\u5E93\u5DF2\u5BFC\u51FA",exportFail:"\u5BFC\u51FA\u5931\u8D25",importVault:"\u5BFC\u5165",importedVault:"\u5BFC\u5165\u5B8C\u6210",importedSkipped:"\uFF08\u8DF3\u8FC7\u91CD\u590D ",importFail:"\u5BFC\u5165\u5931\u8D25",location:"\u81EA\u9009\u4F4D\u7F6E",saveAs:"\u53E6\u5B58\u4E3A",saveAsUnsupported:"\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u9009\u62E9\u4FDD\u5B58\u4F4D\u7F6E",exportedTo:"\u5DF2\u5BFC\u51FA\u5230",defaultDownloads:"\u9ED8\u8BA4\u4E0B\u8F7D\u6587\u4EF6\u5939",exportCancel:"\u5DF2\u53D6\u6D88\u5BFC\u51FA",manage:"\u7BA1\u7406",tabUsage:"\u7528\u91CF/\u4ECA\u65E5",tabOptimize:"\u6574\u7406\u5EFA\u8BAE",todayAdd:"\u4ECA\u65E5\u65B0\u589E",weekAdd:"\u8FD1 7 \u5929",byKind:"\u5206\u7C7B\u7EDF\u8BA1",todayList:"\u4ECA\u65E5\u6C89\u6DC0",budgetLabel:"\u4F1A\u8BDD\u9884\u7B97",budgetChars:"\u9884\u7B97\u5B57\u7B26",recallLimitLabel:"\u53EC\u56DE\u6761\u6570",embeddingLabel:"\u8BED\u4E49\u53EC\u56DE",mergePairs:"\u76F8\u4F3C\u5361\u5BF9\uFF08\u53EF\u5408\u5E76\uFF09",staleCards:"\u9648\u65E7\u5361\uFF08>90 \u5929\u672A\u66F4\u65B0\uFF09",noOptimize:"\u6682\u65E0\u53EF\u6574\u7406\u9879\uFF0C\u5F88\u5065\u5EB7 \u{1F389}",mergeNow:"\u5408\u5E76",delete:"\u5220\u9664",deleteConfirm:"\u786E\u5B9A\u5220\u9664\u8BE5\u8BB0\u5FC6\u5361\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u64A4\u9500\u3002",deleted:"\u5DF2\u5220\u9664",deleteFail:"\u5220\u9664\u5931\u8D25",deleteSelected:"\u5220\u9664\u9009\u4E2D",deletedSelected:"\u5F20\u5361\u5DF2\u5220\u9664",merge:"\u5408\u5E76",mergeNeed:"\u81F3\u5C11\u9009 2 \u5F20\u5361",mergeConfirm:"\u5C06\u6240\u9009\u5361\u7247\u5408\u5E76\u4E3A\u4E00\u5F20\u5E76\u5220\u9664\u539F\u5361\uFF1F",merged:"\u5DF2\u5408\u5E76",mergeFail:"\u5408\u5E76\u5931\u8D25"},ht={nav:"Memory",loading:"Loading\u2026",refresh:"Refresh",overview:"Memory Overview",total:"Cards",recent:"Added (7d)",tags:"Tags",searchPlaceholder:"Search memory\u2026",search:"Search",all:"All",kindProject:"Projects",kindKnowledge:"Knowledge",kindContent:"Content",kindPrompt:"Prompts",kindBusiness:"Business",kindTool:"Tools",kindMistake:"Mistakes",cardsTab:"Cards",graphTab:"Graph",graph:"Knowledge Graph",graphHint:"Nodes = cards; edges = [[links]] or shared tags. Click a node to read.",graphTip:"Left-click highlights \xB7 Shift+drag box-select \xB7 right-click menu \xB7 scroll zoom \xB7 drag to pan",rebuild:"Rebuild",reset:"Reset",expandAll:"Expand all",capped:"Showing the",clearFocus:"Clear highlight",empty:"Memory is empty. After a few conversations, valuable content is auto-captured.",emptyGraph:"No graph data yet.",open:"Read",updated:"Updated",created:"Created",source:"Source",close:"Close",vaultDir:"Vault directory",capture:"Auto capture",recall:"Auto recall",enabled:"on",disabled:"off",error:"Load failed",back:"Back to list",cardCount:"cards",nodes:"nodes",edges:"edges",memoryTitle:"Memory Library",memoryHint:"Your local second brain",zoomIn:"Zoom in",zoomOut:"Zoom out",fit:"Fit",exportGraph:"Export",openCard:"Open card",focusNeighbors:"Highlight links",copyName:"Copy name",noMatch:"No matching memory",clearFilter:"Clear filter",sortRecent:"Recent",sortTitle:"Title",sortHot:"Hot",exportSel:"Export selected",clearSelection:"Clear selection",download:"Download",openNewTab:"Open in new tab",copyImage:"Copy image",done:"Done",downloaded:"Download started",openedTab:"Opened in new tab",copied:"Copied",exportedSel:" nodes exported",fullscreen:"Fullscreen",exitFull:"Exit fullscreen",copyFail:"Copy failed",timeDim:"Time",timeNew:"\u22643 days",timeRecent:"\u22642 weeks",timeMonth:"\u22641 month",timeOld:"Older",newBadge:"NEW",expand:"Expand",collapse:"Collapse",exportVault:"Export MD",exportJson:"Export JSON",exporting:"Exporting\u2026",exportedVault:"Vault exported",exportFail:"Export failed",importVault:"Import",importedVault:"Import complete",importedSkipped:" (skipped dup ",importFail:"Import failed",location:"Choose location",saveAs:"Save as",saveAsUnsupported:"Choose-save-location not supported here",exportedTo:"Exported to",defaultDownloads:"default Downloads folder",exportCancel:"Export cancelled",manage:"Manage",tabUsage:"Usage / Today",tabOptimize:"Optimize",todayAdd:"Added today",weekAdd:"Last 7d",byKind:"By kind",todayList:"Captured today",budgetLabel:"Session budget",budgetChars:"Budget chars",recallLimitLabel:"Recall limit",embeddingLabel:"Semantic recall",mergePairs:"Similar pairs (mergeable)",staleCards:"Stale (>90d)",noOptimize:"Nothing to organize, healthy \u{1F389}",mergeNow:"Merge",delete:"Delete",deleteConfirm:"Delete this memory card? This cannot be undone.",deleted:"Deleted",deleteFail:"Delete failed",deleteSelected:"Delete selected",deletedSelected:" cards deleted",merge:"Merge",mergeNeed:"Select at least 2 cards",mergeConfirm:"Merge the selected cards into one and delete the originals?",merged:"Merged",mergeFail:"Merge failed"},yt=["all","project","knowledge","content","prompt","business","tool","mistake"],We={project:"#3B82F6",knowledge:"#10B981",content:"#F59E0B",prompt:"#A855F7",business:"#EC4899",tool:"#06B6D4",mistake:"#EF4444",other:"#6B7280"},ve={project:"kindProject",knowledge:"kindKnowledge",content:"kindContent",prompt:"kindPrompt",business:"kindBusiness",tool:"kindTool",mistake:"kindMistake",other:"kindKnowledge"},we=`
|
|
7
7
|
.memory-eternal-root { font-family: inherit; color: var(--dsw-alias-label-primary, #1f2937); }
|
|
8
8
|
.mc-card { background: var(--dsw-alias-bg-layer-1, #fff); border: 1px solid var(--dsw-alias-border-l1, #e5e7eb); border-radius: 12px; padding: 14px 16px; }
|
|
9
9
|
.mc-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 10px; margin-bottom: 14px; }
|
|
@@ -105,12 +105,12 @@ var it=Object.create;var je=Object.defineProperty;var st=Object.getOwnPropertyDe
|
|
|
105
105
|
.me-graph-ctxmenu button { display: block; width: 100%; text-align: left; padding: 7px 10px; border: none; border-radius: 7px; background: transparent; color: inherit; font: inherit; cursor: pointer; }
|
|
106
106
|
.me-graph-ctxmenu button:hover { background: rgba(255,255,255,0.1); }
|
|
107
107
|
.me-graph-hint { font-size: 11px; opacity: 0.6; margin-left: auto; }
|
|
108
|
-
`;function xt(
|
|
108
|
+
`;function xt(a){a.effect(()=>a.locale.register(Me,{zh:bt,en:ht}),"memory-eternal: locale");let s=a.locale.bind(Me);a.effect(()=>a.slots.inject("settings.section",()=>a.slots.register({name:"settings.section",id:Me,order:25,label:()=>s("nav"),locale:Me,inject:()=>({})},()=>d.default.createElement(nt,{t:s,inModal:!1}))),"memory-eternal: settings section"),a.effect(()=>a.slots.inject("sidebar.footer.action",()=>a.slots.register({name:"sidebar.footer.action",id:`${Me}:footer`,order:100,label:()=>s("nav"),locale:Me,inject:()=>({})},p=>d.default.createElement(vt,{t:s,wide:!(p&&p.wide===!1)}))),"memory-eternal: sidebar footer action")}function vt({t:a,wide:s}){let[p,b]=(0,d.useState)(!1);return(0,e.jsxs)("div",{className:`me-footer${s?"":" rail"}`,children:[(0,e.jsx)("style",{children:we}),(0,e.jsxs)("button",{type:"button",className:"me-footer-btn",onClick:()=>b(!0),"aria-label":a("nav"),title:a("nav"),children:[(0,e.jsx)("span",{className:"me-footer-ico","aria-hidden":"true",children:(0,e.jsx)(at,{})}),(0,e.jsx)("span",{className:"me-footer-label",children:a("nav")})]}),p&&(0,e.jsx)(wt,{t:a,onClose:()=>b(!1)})]})}function at(){return(0,e.jsxs)("svg",{viewBox:"0 0 24 24","aria-hidden":"true",children:[(0,e.jsx)("defs",{children:(0,e.jsxs)("linearGradient",{id:"mg-brain",x1:"0",y1:"0",x2:"1",y2:"1",children:[(0,e.jsx)("stop",{offset:"0%",stopColor:"#FFC46B"}),(0,e.jsx)("stop",{offset:"50%",stopColor:"#F97316"}),(0,e.jsx)("stop",{offset:"100%",stopColor:"#EA580C"})]})}),(0,e.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 0-6.4.1 4.1 4.1 0 0 0-2.7 5.9 4.1 4.1 0 0 0 .6 6.7A4.2 4.2 0 0 0 12 18.2Z"}),(0,e.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 1 6.4.1 4.1 4.1 0 0 1 2.7 5.9 4.1 4.1 0 0 1-.6 6.7A4.2 4.2 0 0 1 12 18.2Z"}),(0,e.jsx)("path",{d:"M12 9.2c-.8-1.4-1.7-2-3.1-2.2M12 9.2c.8-1.4 1.7-2 3.1-2.2M12 12.4c-1.2 1-3.2 1.6-5 1.4M12 12.4c1.2 1 3.2 1.6 5 1.4M12 13.2v4",fill:"none",stroke:"rgba(255,255,255,0.62)",strokeWidth:"1",strokeLinecap:"round"}),(0,e.jsx)("circle",{cx:"12",cy:"8.6",r:"1.1",fill:"rgba(255,255,255,0.85)"})]})}function wt({t:a,onClose:s}){return(0,d.useEffect)(()=>{let p=b=>{b.key==="Escape"&&s()};return window.addEventListener("keydown",p),()=>window.removeEventListener("keydown",p)},[s]),(0,e.jsxs)("div",{className:"me-overlay-top",onClick:s,children:[(0,e.jsx)("style",{children:we}),(0,e.jsx)("div",{className:"me-modal",onClick:p=>p.stopPropagation(),children:(0,e.jsx)(nt,{t:a,inModal:!0,onClose:s})})]})}function nt({t:a,inModal:s,onClose:p}){let[b,r]=(0,d.useState)(null),[u,S]=(0,d.useState)([]),[j,C]=(0,d.useState)(!0),[M,H]=(0,d.useState)(""),[G,ie]=(0,d.useState)("all"),[X,Ie]=(0,d.useState)(""),[R,x]=(0,d.useState)("cards"),[D,O]=(0,d.useState)("recent"),[$,F]=(0,d.useState)(null),A=(0,d.useRef)(null),ze=(0,d.useRef)(null),[fe,U]=(0,d.useState)(!1),[ee,Ye]=(0,d.useState)(!1),[te,oe]=(0,d.useState)(null),[se,V]=(0,d.useState)(null),le=(0,d.useRef)(null),[J,ce]=(0,d.useState)(24),pe=(0,d.useRef)(null);(0,d.useEffect)(()=>{if(R!=="cards")return;let o=pe.current;if(!o)return;let m=new IntersectionObserver(g=>{g[0]&&g[0].isIntersecting&&ce(T=>T+24)},{rootMargin:"400px"});return m.observe(o),()=>m.disconnect()},[R,u,G,X]);let Te=(0,d.useCallback)(async(o=G,m=X)=>{try{C(!0),H("");let g=new URLSearchParams;o&&o!=="all"&&g.set("kind",o),m.trim()&&g.set("q",m.trim());let L=await(await fetch(`${q}/cards?${g.toString()}`)).json();L.ok&&(S(L.cards||[]),ce(24))}catch(g){H(String(g&&g.message?g.message:g))}finally{C(!1)}},[G,X]),ae=(0,d.useCallback)(async()=>{try{let[o,m]=await Promise.all([fetch(`${q}/overview`).then(g=>g.json()),fetch(`${q}/cards`).then(g=>g.json())]);o.ok&&r(o),m.ok&&(S(m.cards||[]),ce(24))}catch(o){H(String(o&&o.message?o.message:o))}finally{C(!1)}},[]),Le=async o=>{try{let g=await(await fetch(`${q}/delete?path=${encodeURIComponent(o)}`)).json();g.ok?(F({ok:!0,msg:a("deleted")}),await ae()):F({ok:!1,msg:g.error||a("deleteFail")})}catch{F({ok:!1,msg:a("deleteFail")})}A.current&&clearTimeout(A.current),A.current=setTimeout(()=>F(null),2e3)};(0,d.useEffect)(()=>(ae(),()=>{le.current&&clearTimeout(le.current)}),[ae]);let be=o=>{Ie(o),le.current&&clearTimeout(le.current),le.current=setTimeout(()=>Te(G,o),280)},he=o=>{ie(o),Te(o,X)},B=async o=>{try{let g=await(await fetch(`${q}/card?path=${encodeURIComponent(o.path||o.id)}`)).json();g.ok&&oe({path:g.path,title:o.title,text:g.text})}catch(m){H(String(m&&m.message?m.message:m))}},ye=async(o,m)=>{U(!0);try{let g=await fetch(`${q}/export`);if(!g.ok)throw new Error("HTTP "+g.status);let T=await g.json();if(!T.ok)throw new Error(T.error||"export failed");let L,t,xe;o==="json"?(t="memory-vault.json",xe="application/json",L=JSON.stringify(T.cards.map(ue=>({path:ue.path,title:ue.title,kind:ue.kind,text:ue.text})),null,2)):(t="memory-vault.md",xe="text/markdown;charset=utf-8",L=T.cards.map(ue=>ue.text.trim()).filter(Boolean).join(`
|
|
109
109
|
|
|
110
110
|
---
|
|
111
111
|
|
|
112
|
-
`));let e=new Blob([N],{type:D}),ne=await at(e,R,a);if(!ne.ok){z({ok:!1,msg:ne.aborted?n("exportCancel"):n("exportFail")}),O(!1);return}z({ok:!0,msg:ne.picked?n("exportedTo")+"\uFF1A"+ne.name:n("exportedTo")+"\uFF1A"+n("defaultDownloads")+" \xB7 "+ne.name})}catch(t){z({ok:!1,msg:n("exportFail")+" \xB7 "+(t.message||"")})}finally{O(!1)}j.current&&clearTimeout(j.current),j.current=setTimeout(()=>z(null),2600)},F=async d=>{let a=d.target.files&&d.target.files[0];if(d.target.value="",!!a){try{let t=await a.text(),N=await(await fetch(`${ee}/import`,{method:"POST",headers:{"Content-Type":"application/json"},body:t})).json();N.ok?(await Z(),z({ok:!0,msg:n("importedVault")+"\uFF1A"+(N.imported||0)+(N.skipped?n("importedSkipped")+N.skipped:"")})):z({ok:!1,msg:N.error||n("importFail")})}catch{z({ok:!1,msg:n("importFail")})}j.current&&clearTimeout(j.current),j.current=setTimeout(()=>z(null),2400)}},be=(0,c.useMemo)(()=>{let d=b.slice();return me==="title"?d.sort((a,t)=>String(a.title||"").localeCompare(String(t.title||""))):me==="hot"?d.sort((a,t)=>(t.weight||t.links||0)-(a.weight||a.links||0)):d.sort((a,t)=>new Date(t.updated||0).getTime()-new Date(a.updated||0).getTime()),d},[b,me]);return(0,o.jsxs)("div",{className:"memory-eternal-root",style:{height:"100%",display:"flex",flexDirection:"column"},children:[(0,o.jsx)("style",{children:Ne}),oe&&(0,o.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:8,position:"fixed",left:"50%",transform:"translateX(-50%)",top:22,zIndex:70,background:"rgba(20,22,26,0.97)",backdropFilter:"blur(12px)",WebkitBackdropFilter:"blur(12px)",color:"#f5f5f5",borderRadius:12,padding:"10px 20px",fontSize:13.5,fontWeight:600,boxShadow:"0 14px 44px rgba(0,0,0,.5)",pointerEvents:"none",animation:"me-pop .22s ease",borderLeft:"4px solid "+(oe.ok?"#22c55e":"#ef4444"),maxWidth:"90vw"},children:[(0,o.jsx)("span",{style:{color:oe.ok?"#34d399":"#f87171",fontWeight:800,fontSize:15},children:oe.ok?"\u2713":"\u2715"}),(0,o.jsx)("span",{children:oe.msg})]}),l&&(0,o.jsxs)("div",{className:"me-modal-head",children:[(0,o.jsxs)("div",{className:"me-modal-title",children:[(0,o.jsx)("span",{className:"me-wicon",children:(0,o.jsx)(ot,{})}),(0,o.jsx)("h2",{children:n("memoryTitle")}),(0,o.jsx)("span",{children:n("memoryHint")})]}),(0,o.jsx)("div",{className:"spacer"}),(0,o.jsx)("button",{type:"button",className:"me-modal-close",onClick:m,"aria-label":n("close"),children:"\u2715"})]}),(0,o.jsxs)("div",{className:"me-modal-body",children:[(0,o.jsxs)("div",{className:"mc-stats",children:[(0,o.jsx)(De,{label:n("total"),value:h?h.total:"\u2014"}),(0,o.jsx)(De,{label:n("recent"),value:h?h.recent:"\u2014"}),(0,o.jsx)(De,{label:n("tags"),value:h?h.tags:"\u2014"}),(0,o.jsx)(De,{label:n("cardCount"),value:h?h.byKind?.knowledge??0:"\u2014"})]}),(0,o.jsxs)("div",{className:"mc-toolbar",children:[(0,o.jsx)("input",{type:"text",placeholder:n("searchPlaceholder"),value:q,onChange:d=>xe(d.target.value)}),(0,o.jsxs)("div",{className:"mc-tabs",children:[(0,o.jsx)("button",{type:"button",className:`mc-tab${B==="cards"?" active":""}`,onClick:()=>se("cards"),children:n("cardsTab")}),(0,o.jsx)("button",{type:"button",className:`mc-tab${B==="graph"?" active":""}`,onClick:()=>se("graph"),children:n("graphTab")})]}),(0,o.jsx)("input",{ref:Me,type:"file",accept:".json,application/json",style:{display:"none"},onChange:F}),(0,o.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>Me.current&&Me.current.click(),children:n("importVault")}),(0,o.jsxs)("label",{className:"mc-btn",style:{display:"inline-flex",alignItems:"center",gap:5,cursor:"pointer"},title:n("location"),children:[(0,o.jsx)("input",{type:"checkbox",checked:V,onChange:d=>Ye(d.target.checked)}),n("location")]}),(0,o.jsx)("button",{type:"button",className:"mc-btn",disabled:ge,onClick:()=>ce("md",V),children:n(ge?"exporting":"exportVault")}),(0,o.jsx)("button",{type:"button",className:"mc-btn",disabled:ge,onClick:()=>ce("json",V),children:n(ge?"exporting":"exportJson")}),(0,o.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>Z(),children:n("refresh")})]}),B==="cards"&&(0,o.jsxs)("div",{className:"mc-chips",children:[bt.map(d=>(0,o.jsx)("button",{type:"button",className:`mc-chip${ie===d?" active":""}`,onClick:()=>Ee(d),children:n(d==="all"?"all":Ce[d])},d)),(0,o.jsx)("span",{className:"spacer",style:{flex:1}}),[{key:"recent",label:n("sortRecent")},{key:"title",label:n("sortTitle")},{key:"hot",label:n("sortHot")}].map(d=>(0,o.jsx)("button",{type:"button",className:`mc-chip${me===d.key?" active":""}`,onClick:()=>ue(d.key),children:d.label},d.key))]}),E&&(0,o.jsxs)("div",{className:"mc-empty",children:[n("error"),"\uFF1A",E]}),B==="cards"?W&&!b.length?(0,o.jsx)("div",{className:"mc-empty",children:n("loading")}):b.length===0?(0,o.jsx)("div",{className:"mc-empty",children:n("empty")}):(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)("div",{className:"mc-grid",children:be.slice(0,A).map(d=>(0,o.jsx)(vt,{card:d,t:n,query:q.trim(),onOpen:le,onDelete:Ie},d.path))}),be.length>A&&(0,o.jsx)("div",{ref:U,style:{height:1}})]}):(0,o.jsx)(Ct,{t:n,onOpen:le}),_&&(0,o.jsx)(kt,{t:n,card:_,onClose:()=>J(null),onDelete:d=>{J(null),Ie(d)}})]})]})}var De=({label:n,value:l})=>(0,o.jsxs)("div",{className:"mc-stat mc-card",children:[(0,o.jsx)("b",{children:l}),(0,o.jsx)("span",{children:n})]}),vt=({card:n,t:l,onOpen:m,query:h,onDelete:i})=>(0,o.jsxs)("article",{className:"mc-cardrow mc-card",onClick:()=>m(n),children:[(0,o.jsxs)("h4",{children:[(0,o.jsx)("span",{className:"mc-kind",style:{background:Pe[n.kind]||Pe.other}}),h?tt(n.title,h):n.title,St(n.updated)&&(0,o.jsx)("span",{className:"mc-new",children:l("newBadge")})]}),(0,o.jsx)("p",{children:h?tt(n.summary||"",h):n.summary||""}),n.tags.length>0&&(0,o.jsx)("div",{className:"mc-tags",children:n.tags.slice(0,4).map(b=>(0,o.jsx)("span",{className:"mc-tag",children:b},b))}),(0,o.jsxs)("footer",{children:[(0,o.jsx)("span",{children:l(Ce[n.kind])}),(0,o.jsx)("span",{children:zt(n.updated)}),(0,o.jsx)("span",{className:"spacer",style:{flex:1}}),(0,o.jsx)("button",{type:"button",className:"mc-card-del",title:l("delete"),onClick:b=>{b.stopPropagation(),window.confirm(l("deleteConfirm"))&&i&&i(n.path)},children:"\u2715"})]})]});function kt({t:n,card:l,onClose:m,onDelete:h}){(0,c.useEffect)(()=>{let W=C=>{C.key==="Escape"&&m()};return window.addEventListener("keydown",W),()=>window.removeEventListener("keydown",W)},[m]);let[i,b]=(0,c.useState)(!1),T=(l.text||"").length>460;return(0,o.jsxs)("div",{className:"me-overlay",onClick:m,children:[(0,o.jsx)("style",{children:Ne}),(0,o.jsxs)("div",{className:"me-dialog",onClick:W=>W.stopPropagation(),children:[(0,o.jsxs)("div",{className:"me-dialog-head",children:[(0,o.jsx)("h3",{children:l.title}),(0,o.jsxs)("div",{style:{display:"flex",gap:8},children:[T&&(0,o.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>b(W=>!W),children:n(i?"collapse":"expand")}),(0,o.jsx)("button",{type:"button",className:"mc-btn",style:{color:"#f87171"},onClick:()=>{window.confirm(n("deleteConfirm"))&&h&&h(l.path)},children:n("delete")}),(0,o.jsx)("button",{type:"button",className:"mc-btn",onClick:m,children:n("close")})]})]}),(0,o.jsx)("div",{className:"me-dialog-body",style:i?{}:{maxHeight:300,overflow:"hidden"},dangerouslySetInnerHTML:{__html:Tt(l.text)}})]})]})}function Ct({t:n,onOpen:l}){let[m,h]=(0,c.useState)(null),[i,b]=(0,c.useState)(""),T=(0,c.useCallback)(async()=>{try{let E=await(await fetch(`${ee}/graph`)).json();h(E.ok?E:null),b("")}catch(C){b(String(C&&C.message?C.message:C))}},[]);(0,c.useEffect)(()=>{let C=!0;return fetch(`${ee}/graph`).then(E=>E.json()).then(E=>{C&&h(E.ok?E:null)}).catch(E=>{C&&b(String(E&&E.message?E.message:E))}),()=>{C=!1}},[]);let W=(0,c.useCallback)(async C=>{try{let G=await(await fetch(`${ee}/delete?path=${encodeURIComponent(C)}`)).json();return G&&G.ok?(await T(),!0):!1}catch{return!1}},[T]);return(0,o.jsxs)("div",{className:"me-graph",children:[(0,o.jsx)("style",{children:Ne}),i&&(0,o.jsxs)("div",{className:"mc-empty",children:[n("error"),"\uFF1A",i]}),!m&&!i&&(0,o.jsx)("div",{className:"mc-empty",children:n("loading")}),m&&m.nodes.length===0&&(0,o.jsx)("div",{className:"mc-empty",children:n("emptyGraph")}),m&&m.nodes.length>0&&(0,o.jsx)(Mt,{nodes:m.nodes,edges:m.edges,onOpen:l,onDelete:W,t:n,countLabel:`${m.nodes.length} ${n("nodes")} \xB7 ${Nt(m.edges)} ${n("edges")}`})]})}function Nt(n){let l=new Set;for(let m of n||[])l.add([m.source,m.target].sort().join("|"));return l.size}var $e={colors:{project:"#3B82F6",knowledge:"#10B981",content:"#F59E0B",prompt:"#A855F7",business:"#EC4899",tool:"#06B6D4",mistake:"#EF4444",other:"#6B7280"},shapes:{project:"circle",knowledge:"circle",content:"rect",prompt:"diamond",business:"hexagon",tool:"circle",mistake:"diamond",other:"circle"}};function Mt({nodes:n,edges:l,onOpen:m,onDelete:h,t:i,countLabel:b}){let T=(0,c.useRef)(null),W=(0,c.useRef)(null),C=(0,c.useRef)(null),E=(0,c.useRef)(null),G=(0,c.useRef)(null),ie=(0,c.useRef)(m);(0,c.useEffect)(()=>{ie.current=m},[m]);let pe=(0,c.useRef)(h);(0,c.useEffect)(()=>{pe.current=h},[h]);let[q,Oe]=(0,c.useState)(""),[B,se]=(0,c.useState)(null),me=(0,c.useRef)("");(0,c.useEffect)(()=>{me.current=q.trim().toLowerCase()},[q]);let[ue,oe]=(0,c.useState)("all"),z=(0,c.useRef)("all"),[j,Me]=(0,c.useState)(!1),ge=(0,c.useRef)(!1),[O,V]=(0,c.useState)(null);(0,c.useEffect)(()=>{z.current=ue},[ue]),(0,c.useEffect)(()=>{ge.current=j},[j]);let Ye=(()=>{let a=q.trim().toLowerCase();return!!a&&!n.some(t=>(t.title||t.name||"").toLowerCase().includes(a)||String(t.kind||"").toLowerCase().includes(a)||String(Ce[t.kind]||"").toLowerCase().includes(a))})();(0,c.useEffect)(()=>{let a=C.current;a&&a.wake&&a.wake()},[q]),(0,c.useEffect)(()=>{let a=C.current;a&&a.render&&a.render()},[ue,j]);let _=(0,c.useRef)(null),[J,X]=(0,c.useState)([]),[A,fe]=(0,c.useState)(null),[U,he]=(0,c.useState)(!1),[Z,Ie]=(0,c.useState)(null),[xe,Ee]=(0,c.useState)(""),le=(0,c.useRef)(null),ce=(0,c.useRef)(null),F=(a,t=!0)=>{le.current&&clearTimeout(le.current),Ie({msg:a,ok:t}),le.current=setTimeout(()=>Ie(null),1900)},be=a=>{Ee(a),ce.current&&clearTimeout(ce.current),ce.current=setTimeout(()=>Ee(""),1600)};(0,c.useEffect)(()=>()=>{le.current&&clearTimeout(le.current),ce.current&&clearTimeout(ce.current)},[]);let d=(0,c.useMemo)(()=>n.reduce((a,t)=>(a[t.id]=t,a),{}),[n]);return(0,c.useEffect)(()=>{let a=T.current;if(!a||n.length===0)return;let t=a.getContext("2d");if(!t)return;let y=Math.max(1,window.devicePixelRatio||1),N=()=>document.documentElement.getAttribute("data-theme")==="dark",R={};l.forEach(r=>{R[r.source]=(R[r.source]||0)+1,R[r.target]=(R[r.target]||0)+1});let D=Math.max(1,...Object.values(R)),e={nodes:n.map(r=>({id:r.id,name:r.title||"",type:r.kind||"other",r:9+Math.min(10,(R[r.id]||0)/D*8),x:0,y:0,vx:0,vy:0})),edges:l.map(r=>({sourceNodeId:r.source,targetNodeId:r.target,weight:1})),panX:0,panY:0,zoom:1,running:!0,raf:0,tickCount:0,quietTicks:0,dragNode:null,selectedId:null,mouseX:0,mouseY:0,w:0,h:0,dpr:y,ctx:t,multi:[],marquee:null};e.domain=n,e.domainById=n.reduce((r,u)=>(r[u.id]=u,r),{}),e.nodes.forEach((r,u)=>{let p=u/e.nodes.length*Math.PI*2-Math.PI/2;r.x=Math.cos(p)*60,r.y=Math.sin(p)*60});let ne=[...new Set(e.nodes.map(r=>r.type))];e.clustered=ne.length>1,e.typeCenters={},ne.forEach((r,u)=>{let p=u/ne.length*Math.PI*2-Math.PI/2;e.typeCenters[r]={x:Math.cos(p)*160,y:Math.sin(p)*160}}),C.current=e;let ae=()=>{let r=a.clientWidth,u=a.clientHeight;e.w=r,e.h=u,a.width=r*y,a.height=u*y,t.setTransform(y,0,0,y,0,0)};ae();let We=new ResizeObserver(ae);We.observe(a.parentElement);let rt=(r,u)=>r&&r.length>u?r.slice(0,u)+"\u2026":r,Se=(r,u,p,g)=>{if(t.beginPath(),g==="rect")t.rect(r-p,u-p*.75,p*2,p*1.5);else if(g==="diamond")t.moveTo(r,u-p),t.lineTo(r+p,u),t.lineTo(r,u+p),t.lineTo(r-p,u),t.closePath();else if(g==="hexagon"){for(let f=0;f<6;f++){let v=Math.PI/3*f-Math.PI/2,M=r+p*Math.cos(v),s=u+p*Math.sin(v);f===0?t.moveTo(M,s):t.lineTo(M,s)}t.closePath()}else t.arc(r,u,p,0,Math.PI*2)},Fe=()=>{if(!e.nodes.length)return;let r=1/0,u=-1/0,p=1/0,g=-1/0;e.nodes.forEach(k=>{r=Math.min(r,k.x),u=Math.max(u,k.x),p=Math.min(p,k.y),g=Math.max(g,k.y)});let f=36,v=u-r+f*2,M=g-p+f*2,s=Math.min(e.w/v,e.h/M);e.zoom=Math.max(.45,Math.min(2.4,s*1.22)),e.panX=e.w/2-(r+u)/2*e.zoom,e.panY=e.h/2-(p+g)/2*e.zoom};E.current=Fe;let qe=()=>{t.clearRect(0,0,e.w,e.h),e.searchTerm=me.current,e.kindFilter=z.current,e.timeMode=ge.current;let r=e.kindFilter,u=s=>String(s||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");t.save(),t.strokeStyle=N()?"rgba(255,255,255,0.04)":"rgba(0,0,0,0.04)",t.lineWidth=.5;for(let s=0;s<e.w;s+=24)t.beginPath(),t.moveTo(s,0),t.lineTo(s,e.h),t.stroke();for(let s=0;s<e.h;s+=24)t.beginPath(),t.moveTo(0,s),t.lineTo(e.w,s),t.stroke();t.restore(),t.save(),t.translate(e.panX,e.panY),t.scale(e.zoom,e.zoom);let p={};e.nodes.forEach(s=>p[s.id]=s);let g=e.nodes.length>40,f=g?1.5:.55,v=e.selectedId||e.hoverId;e.edges.forEach(s=>{let k=p[s.sourceNodeId],I=p[s.targetNodeId];if(!k||!I)return;if(r!=="all"){let Y=e.domainById[s.sourceNodeId]&&e.domainById[s.sourceNodeId].kind,Ze=e.domainById[s.targetNodeId]&&e.domainById[s.targetNodeId].kind;if(Y!==r&&Ze!==r)return}let x=I.x-k.x,w=I.y-k.y,S=Math.sqrt(x*x+w*w)||1,L=g?12:18,$=-w/S*L,K=x/S*L,P=(k.x+I.x)/2+$,te=(k.y+I.y)/2+K,H=$e.colors[e.domainById[k.id]&&e.domainById[k.id].kind||"other"],re=v&&(s.sourceNodeId===v||s.targetNodeId===v),ye=v?re?.6:.08:g?.14:.24,ze=parseInt(H.slice(1,3),16),Le=parseInt(H.slice(3,5),16),Je=parseInt(H.slice(5,7),16);t.strokeStyle="rgba("+ze+","+Le+","+Je+","+ye+")",t.lineWidth=re?1.6+(s.weight||1):.8+(s.weight||1)*.5,t.beginPath(),t.moveTo(k.x,k.y),t.quadraticCurveTo(P,te,I.x,I.y),t.stroke()});let M=[];if(e.nodes.forEach(s=>{let k=e.domainById[s.id]||{},I=k.kind||"other",x=e.timeMode?It(k.updated):$e.colors[I],w=$e.shapes[I],S=e.selectedId===s.id,L=e.hoverId===s.id,$=e.searchTerm||"",K=!$||(s.name||"").toLowerCase().includes($)||String(Ce[I]||"").toLowerCase().includes($)||String(I).toLowerCase().includes($),P=r==="all"||I===r,te=v&&s.id!==v&&!e.edges.some(Y=>Y.sourceNodeId===v&&Y.targetNodeId===s.id||Y.targetNodeId===v&&Y.sourceNodeId===s.id)||$&&!K||!P;t.save(),t.globalAlpha=te?.08:1,(S||L)&&(t.shadowColor=x,t.shadowBlur=S?20:14),Se(s.x,s.y,s.r,w);let H=parseInt(x.slice(1,3),16),re=parseInt(x.slice(3,5),16),ye=parseInt(x.slice(5,7),16),ze=t.createRadialGradient(s.x-s.r*.3,s.y-s.r*.3,0,s.x,s.y,s.r*1.2);ze.addColorStop(0,"rgba("+Math.min(255,H+70)+","+Math.min(255,re+70)+","+Math.min(255,ye+70)+",0.95)"),ze.addColorStop(1,x),t.fillStyle=ze,t.fill(),t.restore(),S?(t.save(),Se(s.x,s.y,s.r+3,w),t.strokeStyle=x,t.lineWidth=3,t.shadowColor=x,t.shadowBlur=12,t.stroke(),t.restore()):L?(t.save(),Se(s.x,s.y,s.r+2,w),t.strokeStyle=x,t.lineWidth=2,t.stroke(),t.restore()):$&&K?(t.save(),Se(s.x,s.y,s.r+2,w),t.strokeStyle="#e11d48",t.lineWidth=2,t.stroke(),t.restore()):e.multi.indexOf(s.id)>=0&&(t.save(),Se(s.x,s.y,s.r+2,w),t.strokeStyle="#60a5fa",t.lineWidth=2,t.shadowColor="#60a5fa",t.shadowBlur=8,t.stroke(),t.restore());let Le=rt(s.name,16);if((S||L||e.zoom>f||!g&&e.zoom>.6)&&P){let Y=1/e.zoom;t.font="500 "+(12*Y).toFixed(1)+"px -apple-system,Segoe UI,sans-serif";let de=t.measureText(Le).width+16*Y,Be=18*Y,we=s.y+s.r+8*Y,Qe=!0;for(let ve of M)if(s.x-de/2<ve.x+ve.w&&s.x+de/2>ve.x&&we<ve.y+ve.h&&we+Be>ve.y){Qe=!1;break}if(!Qe&&!S&&!L)return;M.push({x:s.x-de/2,y:we,w:de,h:Be}),t.fillStyle=N()?"rgba(30,30,35,0.92)":"rgba(255,255,255,0.92)",t.beginPath(),t.roundRect?t.roundRect(s.x-de/2,we,de,Be,4*Y):t.rect(s.x-de/2,we,de,Be),t.fill(),t.strokeStyle=N()?"rgba(255,255,255,0.08)":"rgba(0,0,0,0.08)",t.lineWidth=1*Y,t.stroke(),t.fillStyle=N()?S||L?"#eee":"#bbb":S||L?"#111":"#444",t.textAlign="center",t.fillText(Le,s.x,we+13*Y)}}),t.restore(),e.marquee){let s=e.marquee,k=Math.min(s.sx,s.ex),I=Math.min(s.sy,s.ey),x=Math.abs(s.ex-s.sx),w=Math.abs(s.ey-s.sy);t.save(),t.setLineDash([4,4]),t.strokeStyle="rgba(96,165,250,0.9)",t.lineWidth=1,t.fillStyle="rgba(96,165,250,0.12)",t.fillRect(k,I,x,w),t.strokeRect(k,I,x,w),t.restore()}if(e.hoverId&&_.current){let s=e.domainById[e.hoverId],k=e.nodes.find(function(I){return I.id===e.hoverId});if(s&&k){let I=e.edges.filter(function(S){return S.sourceNodeId===s.id||S.targetNodeId===s.id}).length,x=k.x*e.zoom+e.panX,w=k.y*e.zoom+e.panY;_.current.style.display="block",_.current.style.left=x+14+"px",_.current.style.top=w-6+"px",_.current.innerHTML="<b>"+u(s.title||s.name||"")+'</b><br/><span style="opacity:.8">'+u(s.kind||"")+" \xB7 "+I+" \u8FDE\u63A5</span>"}}else _.current&&(_.current.style.display="none")},Q=()=>{e.running=!0,e.raf||(e.raf=requestAnimationFrame(Ae))};e.wake=Q,e.render=qe;let Xe=()=>{let r=e.nodes.length;e.tickCount++;let p=.9-Math.min(.4,e.tickCount/1500),g=r>1e3?3e3:r>100?2e3:r>50?1200:800,f=r>100?.002:.005,v=r>1e3?.012:r>100?.005:.01,M=r>1e3?6:r>200?12:24,s={};e.nodes.forEach(x=>s[x.id]=x);for(let x=0;x<r;x++){if(e.dragNode===e.nodes[x])continue;let w=e.nodes[x],S=0,L=0;for(let P=0;P<r;P++){if(x===P)continue;let te=w.x-e.nodes[P].x,H=w.y-e.nodes[P].y,re=Math.sqrt(te*te+H*H)||1,ye=g/(re*re);S+=te/re*ye,L+=H/re*ye}if(e.clustered&&e.typeCenters[w.type]){let P=e.typeCenters[w.type];S+=(P.x-w.x)*.006,L+=(P.y-w.y)*.006}else S-=w.x*v,L-=w.y*v;let $=(w.vx+S)*p,K=(w.vy+L)*p;$=Math.max(-M,Math.min(M,$)),K=Math.max(-M,Math.min(M,K)),w.vx=$,w.vy=K}e.edges.forEach(x=>{let w=s[x.sourceNodeId],S=s[x.targetNodeId];if(!w||!S)return;let L=S.x-w.x,$=S.y-w.y,K=Math.sqrt(L*L+$*$)||1,P=(K-100)*f,te=L/K*P,H=$/K*P;e.dragNode!==w&&(w.vx+=te,w.vy+=H),e.dragNode!==S&&(S.vx-=te,S.vy-=H)});let k=0;e.nodes.forEach(x=>{e.dragNode!==x&&(x.x+=x.vx,x.y+=x.vy,k+=x.vx*x.vx+x.vy*x.vy)}),e.autoFitPending&&e.tickCount>45&&(e.autoFitPending=!1,Fe()),(r?Math.sqrt(k/r):0)<.05&&e.tickCount>60&&!e.dragNode?e.quietTicks=(e.quietTicks|0)+1:e.quietTicks=0},Ae=()=>{if(e.running){try{Xe(),qe()}catch{}if(e.quietTicks>30){e.raf=0;return}e.raf=requestAnimationFrame(Ae)}},Te=(r,u)=>{let p=a.getBoundingClientRect();return{x:(r-p.left-e.panX)/e.zoom,y:(u-p.top-e.panY)/e.zoom}},Re=(r,u,p)=>Math.max(u,Math.min(p,r)),Ue=r=>{e.mouseX=r.clientX,e.mouseY=r.clientY;let u=Te(r.clientX,r.clientY),p=null;for(let g=e.nodes.length-1;g>=0;g--){let f=e.nodes[g];if(z.current!=="all"&&(e.domainById[f.id]&&e.domainById[f.id].kind)!==z.current)continue;let v=f.x-u.x,M=f.y-u.y;if(v*v+M*M<f.r*f.r+36){p=f.id;break}}p!==e.hoverId&&(e.hoverId=p,Q())},Ke=r=>{if(r.button!==0)return;let u=Te(r.clientX,r.clientY),p=null;for(let g=e.nodes.length-1;g>=0;g--){let f=e.nodes[g];if(z.current!=="all"&&(e.domainById[f.id]&&e.domainById[f.id].kind)!==z.current)continue;let v=f.x-u.x,M=f.y-u.y;if(v*v+M*M<f.r*f.r+25){p=f;break}}if(r.shiftKey){e.marquee={sx:r.clientX,sy:r.clientY,ex:r.clientX,ey:r.clientY};return}e.dragStart={x:r.clientX,y:r.clientY,px:e.panX,py:e.panY},e.dragging=!1,e.dragNode=p||null,p&&(e.dragOffset={dx:p.x-u.x,dy:p.y-u.y})},He=r=>{if(e.marquee){e.marquee.ex=r.clientX,e.marquee.ey=r.clientY,Q();return}if(!e.dragStart)return;let u=r.clientX-e.dragStart.x,p=r.clientY-e.dragStart.y;if(Math.abs(u)+Math.abs(p)>3&&(e.dragging=!0),e.dragNode){let g=Te(r.clientX,r.clientY);e.dragNode.x=g.x+e.dragOffset.dx,e.dragNode.y=g.y+e.dragOffset.dy,Q()}else e.dragging&&(e.panX=Re(e.dragStart.px+u,-1e5,1e5),e.panY=Re(e.dragStart.py+p,-1e5,1e5),Q())},Ge=r=>{if(e.marquee){let p=e.marquee;e.marquee=null;let g=a.getBoundingClientRect(),f=Math.min(p.sx,p.ex)-g.left,v=Math.max(p.sx,p.ex)-g.left,M=Math.min(p.sy,p.ey)-g.top,s=Math.max(p.sy,p.ey)-g.top,k=e.nodes.filter(I=>{let x=I.x*e.zoom+e.panX,w=I.y*e.zoom+e.panY;return x>=f&&x<=v&&w>=M&&w<=s}).map(I=>I.id);e.multi=k,X(k),Q();return}if(e.dragStart&&!e.dragging){let p=Te(r.clientX,r.clientY),g=null;for(let f=e.nodes.length-1;f>=0;f--){let v=e.nodes[f];if(z.current!=="all"&&(e.domainById[v.id]&&e.domainById[v.id].kind)!==z.current)continue;let M=v.x-p.x,s=v.y-p.y;if(M*M+s*s<v.r*v.r+20){g=v;break}}g?(e.selectedId=g.id,se(g.id),Q()):(e.selectedId=null,e.hoverId=null,r.shiftKey||(e.multi=[],X([])),se(null),Q())}e.dragStart=null,e.dragging=!1,e.dragNode=null},Ve=r=>{r.preventDefault();let u=Te(r.clientX,r.clientY),p=null;for(let g=e.nodes.length-1;g>=0;g--){let f=e.nodes[g];if(z.current!=="all"&&(e.domainById[f.id]&&e.domainById[f.id].kind)!==z.current)continue;let v=f.x-u.x,M=f.y-u.y;if(v*v+M*M<f.r*f.r+25){p=f;break}}if(p){let g=e.domainById[p.id];g&&V({x:r.clientX,y:r.clientY,id:p.id,path:g.id,title:g.title})}else V(null)},_e=r=>{r.preventDefault();let u=a.getBoundingClientRect(),p=(r.clientX-u.left-e.panX)/e.zoom,g=(r.clientY-u.top-e.panY)/e.zoom,f=Math.max(.3,Math.min(3,e.zoom*(r.deltaY<0?1.1:1/1.1)));e.panX=Re(e.panX+p*(e.zoom-f),-1e5,1e5),e.panY=Re(e.panY+g*(e.zoom-f),-1e5,1e5),e.zoom=f,Q()};a.addEventListener("mousemove",Ue),a.addEventListener("mousedown",Ke),window.addEventListener("mousemove",He),window.addEventListener("mouseup",Ge),a.addEventListener("wheel",_e,{passive:!1}),a.addEventListener("contextmenu",Ve),G.current=()=>{e.selectedId=null,e.hoverId=null,e.multi=[],X([]),Fe(),Q()};for(let r=0;r<200;r++)Xe();return Fe(),Q(),()=>{e.running=!1,e.raf&&cancelAnimationFrame(e.raf),We.disconnect(),a.removeEventListener("mousemove",Ue),a.removeEventListener("mousedown",Ke),window.removeEventListener("mousemove",He),window.removeEventListener("mouseup",Ge),a.removeEventListener("wheel",_e),a.removeEventListener("contextmenu",Ve)}},[n,l]),(0,o.jsxs)("div",{className:"me-graph",ref:W,children:[(0,o.jsxs)("div",{className:"me-graph-toolbar",children:[(0,o.jsxs)("span",{className:"me-graph-count",children:[n.length," ",i("nodes")," \xB7 ",l.length," ",i("edges")]}),(0,o.jsx)("span",{className:"spacer",style:{flex:1}}),(0,o.jsx)("input",{type:"text",className:"mc-btn",style:{padding:"6px 10px",minWidth:140},placeholder:i("search"),value:q,onChange:a=>Oe(a.target.value)}),(0,o.jsx)("button",{type:"button",className:`mc-btn${j?" me-on":""}`,onClick:()=>Me(a=>!a),children:i("timeDim")}),(0,o.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{let a=T.current;if(a)try{a.toBlob(t=>{t&&fe({url:URL.createObjectURL(t),blob:t})},"image/png")}catch{}},children:i("exportGraph")}),(0,o.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>E.current&&E.current(),children:i("fit")}),(0,o.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{se(null),oe("all"),G.current&&G.current()},children:i("reset")})]}),(0,o.jsxs)("div",{className:"me-graph-canvas",style:{position:"relative",overflow:"hidden"},children:[(0,o.jsx)("canvas",{ref:T,style:{width:"100%",height:"100%",display:"block",cursor:"grab"}}),(0,o.jsx)("div",{className:"me-graph-tooltip",ref:_,style:{display:"none",position:"absolute",zIndex:4,pointerEvents:"none",background:"rgba(28,28,32,0.88)",backdropFilter:"blur(10px)",WebkitBackdropFilter:"blur(10px)",color:"#eee",borderRadius:8,padding:"7px 10px",fontSize:12,maxWidth:220,boxShadow:"0 8px 24px rgba(0,0,0,.2)"}}),(0,o.jsxs)("div",{className:"me-graph-legend",style:{position:"absolute",left:12,bottom:8,zIndex:2},children:[j?[{c:"#10b981",l:i("timeNew")},{c:"#f59e0b",l:i("timeRecent")},{c:"#f97316",l:i("timeMonth")},{c:"#94a3b8",l:i("timeOld")}].map(a=>(0,o.jsxs)("span",{className:"lg",children:[(0,o.jsx)("span",{className:"mc-kind",style:{background:a.c}}),a.l]},a.c)):Object.keys(Pe).map(a=>(0,o.jsxs)("button",{type:"button",className:`lg${ue===a?" active":""}`,onClick:()=>oe(t=>t===a?"all":a),children:[(0,o.jsx)("span",{className:"mc-kind",style:{background:$e.colors[a]||Pe[a]}}),i(Ce[a])]},a)),!j&&ue!=="all"&&(0,o.jsxs)("button",{type:"button",className:"lg lg-clear",onClick:()=>oe("all"),children:[i("clearFilter")," \u2715"]})]}),Ye&&(0,o.jsx)("div",{className:"mc-empty",style:{position:"absolute",inset:0,zIndex:2,display:"flex",alignItems:"center",justifyContent:"center",backdropFilter:"blur(2px)",WebkitBackdropFilter:"blur(2px)"},children:i("noMatch")}),O&&(0,o.jsx)("div",{className:"me-graph-ctx-back",style:{position:"fixed",inset:0,zIndex:30},onMouseDown:()=>V(null)}),O&&(0,o.jsxs)("div",{className:"me-graph-ctxmenu",style:{position:"fixed",left:O.x,top:O.y,zIndex:31},onMouseDown:a=>a.stopPropagation(),children:[(0,o.jsx)("button",{type:"button",onClick:()=>{ie.current({path:O.path,title:O.title}),V(null)},children:i("openCard")}),(0,o.jsx)("button",{type:"button",onClick:()=>{O.id&&(C.current.selectedId=O.id,se(O.id)),V(null);let a=C.current;a&&a.wake&&a.wake()},children:i("focusNeighbors")}),(0,o.jsx)("button",{type:"button",onClick:()=>{try{navigator.clipboard&&navigator.clipboard.writeText(O.title||"")}catch{}V(null),F(i("copied"))},children:i("copyName")}),(0,o.jsx)("button",{type:"button",style:{color:"#f87171"},onClick:()=>{let a=O.path;V(null),window.confirm(i("deleteConfirm"))&&(async()=>{try{(pe.current?await pe.current(a):!1)?F(i("deleted")):F(i("deleteFail"),!1)}catch{F(i("deleteFail"),!1)}})()},children:i("delete")})]}),J.length>0&&(0,o.jsxs)("div",{style:{position:"absolute",left:10,top:10,zIndex:3,display:"flex",gap:8,alignItems:"center",background:"rgba(28,28,32,0.92)",backdropFilter:"blur(10px)",WebkitBackdropFilter:"blur(10px)",color:"#eee",borderRadius:10,padding:"6px 10px",fontSize:12,boxShadow:"0 10px 30px rgba(0,0,0,.25)"},children:[(0,o.jsxs)("span",{style:{fontWeight:700},children:[J.length," ",i("nodes")]}),(0,o.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{let t=J.map(D=>d[D]).filter(Boolean).map(D=>D.title||D.name).join(`
|
|
113
|
-
`),
|
|
112
|
+
`));let Re=new Blob([L],{type:xe}),me=await rt(Re,t,m);if(!me.ok){F({ok:!1,msg:me.aborted?a("exportCancel"):a("exportFail")}),U(!1);return}F({ok:!0,msg:me.picked?a("exportedTo")+"\uFF1A"+me.name:a("exportedTo")+"\uFF1A"+a("defaultDownloads")+" \xB7 "+me.name})}catch(g){F({ok:!1,msg:a("exportFail")+" \xB7 "+(g.message||"")})}finally{U(!1)}A.current&&clearTimeout(A.current),A.current=setTimeout(()=>F(null),2600)},_=async o=>{let m=o.target.files&&o.target.files[0];if(o.target.value="",!!m){try{let g=await m.text(),L=await(await fetch(`${q}/import`,{method:"POST",headers:{"Content-Type":"application/json"},body:g})).json();L.ok?(await ae(),F({ok:!0,msg:a("importedVault")+"\uFF1A"+(L.imported||0)+(L.skipped?a("importedSkipped")+L.skipped:"")})):F({ok:!1,msg:L.error||a("importFail")})}catch{F({ok:!1,msg:a("importFail")})}A.current&&clearTimeout(A.current),A.current=setTimeout(()=>F(null),2400)}},i=(0,d.useMemo)(()=>{let o=u.slice();return D==="title"?o.sort((m,g)=>String(m.title||"").localeCompare(String(g.title||""))):D==="hot"?o.sort((m,g)=>(g.weight||g.links||0)-(m.weight||m.links||0)):o.sort((m,g)=>new Date(g.updated||0).getTime()-new Date(m.updated||0).getTime()),o},[u,D]);return(0,e.jsxs)("div",{className:"memory-eternal-root",style:{height:"100%",display:"flex",flexDirection:"column"},children:[(0,e.jsx)("style",{children:we}),$&&(0,e.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:8,position:"fixed",left:"50%",transform:"translateX(-50%)",top:22,zIndex:70,background:"rgba(20,22,26,0.97)",backdropFilter:"blur(12px)",WebkitBackdropFilter:"blur(12px)",color:"#f5f5f5",borderRadius:12,padding:"10px 20px",fontSize:13.5,fontWeight:600,boxShadow:"0 14px 44px rgba(0,0,0,.5)",pointerEvents:"none",animation:"me-pop .22s ease",borderLeft:"4px solid "+($.ok?"#22c55e":"#ef4444"),maxWidth:"90vw"},children:[(0,e.jsx)("span",{style:{color:$.ok?"#34d399":"#f87171",fontWeight:800,fontSize:15},children:$.ok?"\u2713":"\u2715"}),(0,e.jsx)("span",{children:$.msg})]}),s&&(0,e.jsxs)("div",{className:"me-modal-head",children:[(0,e.jsxs)("div",{className:"me-modal-title",children:[(0,e.jsx)("span",{className:"me-wicon",children:(0,e.jsx)(at,{})}),(0,e.jsx)("h2",{children:a("memoryTitle")}),(0,e.jsx)("span",{children:a("memoryHint")})]}),(0,e.jsx)("div",{className:"spacer"}),(0,e.jsx)("button",{type:"button",className:"me-modal-close",onClick:p,"aria-label":a("close"),children:"\u2715"})]}),(0,e.jsxs)("div",{className:"me-modal-body",children:[(0,e.jsxs)("div",{className:"mc-stats",children:[(0,e.jsx)(Ae,{label:a("total"),value:b?b.total:"\u2014"}),(0,e.jsx)(Ae,{label:a("recent"),value:b?b.recent:"\u2014"}),(0,e.jsx)(Ae,{label:a("tags"),value:b?b.tags:"\u2014"}),(0,e.jsx)(Ae,{label:a("cardCount"),value:b?b.byKind?.knowledge??0:"\u2014"})]}),(0,e.jsxs)("div",{className:"mc-toolbar",children:[(0,e.jsx)("input",{type:"text",placeholder:a("searchPlaceholder"),value:X,onChange:o=>be(o.target.value)}),(0,e.jsxs)("div",{className:"mc-tabs",children:[(0,e.jsx)("button",{type:"button",className:`mc-tab${R==="cards"?" active":""}`,onClick:()=>x("cards"),children:a("cardsTab")}),(0,e.jsx)("button",{type:"button",className:`mc-tab${R==="graph"?" active":""}`,onClick:()=>x("graph"),children:a("graphTab")})]}),(0,e.jsx)("input",{ref:ze,type:"file",accept:".json,application/json",style:{display:"none"},onChange:_}),(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>ze.current&&ze.current.click(),children:a("importVault")}),(0,e.jsxs)("label",{className:"mc-btn",style:{display:"inline-flex",alignItems:"center",gap:5,cursor:"pointer"},title:a("location"),children:[(0,e.jsx)("input",{type:"checkbox",checked:ee,onChange:o=>Ye(o.target.checked)}),a("location")]}),(0,e.jsx)("button",{type:"button",className:"mc-btn",disabled:fe,onClick:()=>ye("md",ee),children:a(fe?"exporting":"exportVault")}),(0,e.jsx)("button",{type:"button",className:"mc-btn",disabled:fe,onClick:()=>ye("json",ee),children:a(fe?"exporting":"exportJson")}),(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>V({tab:"stats"}),children:a("manage")}),(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>ae(),children:a("refresh")})]}),R==="cards"&&(0,e.jsxs)("div",{className:"mc-chips",children:[yt.map(o=>(0,e.jsx)("button",{type:"button",className:`mc-chip${G===o?" active":""}`,onClick:()=>he(o),children:a(o==="all"?"all":ve[o])},o)),(0,e.jsx)("span",{className:"spacer",style:{flex:1}}),[{key:"recent",label:a("sortRecent")},{key:"title",label:a("sortTitle")},{key:"hot",label:a("sortHot")}].map(o=>(0,e.jsx)("button",{type:"button",className:`mc-chip${D===o.key?" active":""}`,onClick:()=>O(o.key),children:o.label},o.key))]}),M&&(0,e.jsxs)("div",{className:"mc-empty",children:[a("error"),"\uFF1A",M]}),R==="cards"?j&&!u.length?(0,e.jsx)("div",{className:"mc-empty",children:a("loading")}):u.length===0?(0,e.jsx)("div",{className:"mc-empty",children:a("empty")}):(0,e.jsxs)(e.Fragment,{children:[(0,e.jsx)("div",{className:"mc-grid",children:i.slice(0,J).map(o=>(0,e.jsx)(kt,{card:o,t:a,query:X.trim(),onOpen:B,onDelete:Le},o.path))}),i.length>J&&(0,e.jsx)("div",{ref:pe,style:{height:1}})]}):(0,e.jsx)(Mt,{t:a,onOpen:B}),te&&(0,e.jsx)(Nt,{t:a,card:te,onClose:()=>oe(null),onDelete:o=>{oe(null),Le(o)}}),se&&(0,e.jsx)(Ct,{t:a,tab:se.tab,onTab:o=>V({tab:o}),onClose:()=>V(null),onReload:()=>ae()})]})]})}var Ae=({label:a,value:s})=>(0,e.jsxs)("div",{className:"mc-stat mc-card",children:[(0,e.jsx)("b",{children:s}),(0,e.jsx)("span",{children:a})]}),kt=({card:a,t:s,onOpen:p,query:b,onDelete:r})=>(0,e.jsxs)("article",{className:"mc-cardrow mc-card",onClick:()=>p(a),children:[(0,e.jsxs)("h4",{children:[(0,e.jsx)("span",{className:"mc-kind",style:{background:We[a.kind]||We.other}}),b?ot(a.title,b):a.title,Tt(a.updated)&&(0,e.jsx)("span",{className:"mc-new",children:s("newBadge")})]}),(0,e.jsx)("p",{children:b?ot(a.summary||"",b):a.summary||""}),a.tags.length>0&&(0,e.jsx)("div",{className:"mc-tags",children:a.tags.slice(0,4).map(u=>(0,e.jsx)("span",{className:"mc-tag",children:u},u))}),(0,e.jsxs)("footer",{children:[(0,e.jsx)("span",{children:s(ve[a.kind])}),(0,e.jsx)("span",{children:it(a.updated)}),(0,e.jsx)("span",{className:"spacer",style:{flex:1}}),(0,e.jsx)("button",{type:"button",className:"mc-card-del",title:s("delete"),onClick:u=>{u.stopPropagation(),window.confirm(s("deleteConfirm"))&&r&&r(a.path)},children:"\u2715"})]})]});function Ct({t:a,tab:s,onTab:p,onClose:b,onReload:r}){let[u,S]=(0,d.useState)(null),[j,C]=(0,d.useState)(null),[M,H]=(0,d.useState)(null),G=(0,d.useCallback)(async()=>{try{let[x,D,O]=await Promise.all([fetch(`${q}/stats`).then($=>$.json()),fetch(`${q}/optimize`).then($=>$.json()),fetch(`${q}/budget`).then($=>$.json())]);x.ok&&S(x),D.ok&&C(D),O.ok&&H(O)}catch{}},[]);(0,d.useEffect)(()=>{G()},[G]);let ie=async(x,D)=>{if(window.confirm(a("mergeConfirm")))try{(await fetch(`${q}/merge?paths=${encodeURIComponent(x+","+D)}`).then($=>$.json())).ok&&(await G(),r&&r())}catch{}},X=async x=>{if(window.confirm(a("deleteConfirm")))try{(await fetch(`${q}/delete?path=${encodeURIComponent(x)}`).then(O=>O.json())).ok&&(await G(),r&&r())}catch{}},Ie=["project","knowledge","content","prompt","business","tool","mistake"],R=({title:x,kind:D,updated:O,path:$})=>(0,e.jsxs)("li",{style:{display:"flex",alignItems:"center",gap:8,padding:"3px 4px",borderBottom:"1px solid rgba(127,127,127,0.12)"},children:[(0,e.jsx)("span",{className:"mc-kind",style:{background:Se.colors[D]||"#666"}}),(0,e.jsx)("span",{style:{flex:1,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:x}),(0,e.jsx)("span",{style:{opacity:.55,fontSize:11},children:it(O)}),(0,e.jsxs)("button",{type:"button",className:"mc-btn",style:{padding:"2px 8px"},onClick:()=>X($),children:[a("delete")," \u2715"]})]});return(0,e.jsxs)("div",{className:"me-overlay",onClick:b,children:[(0,e.jsx)("style",{children:we}),(0,e.jsxs)("div",{className:"me-dialog",style:{maxWidth:720,width:"94vw",maxHeight:"86vh"},onClick:x=>x.stopPropagation(),children:[(0,e.jsxs)("div",{className:"me-dialog-head",children:[(0,e.jsxs)("div",{style:{display:"flex",gap:8},children:[(0,e.jsx)("button",{type:"button",className:`mc-tab${s==="stats"?" active":""}`,onClick:()=>p("stats"),children:a("tabUsage")}),(0,e.jsx)("button",{type:"button",className:`mc-tab${s==="optimize"?" active":""}`,onClick:()=>p("optimize"),children:a("tabOptimize")})]}),(0,e.jsx)("div",{className:"spacer",style:{flex:1}}),(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:b,children:a("close")})]}),(0,e.jsxs)("div",{className:"me-dialog-body",style:{overflow:"auto"},children:[s==="stats"&&(0,e.jsx)("div",{children:u&&(0,e.jsxs)(e.Fragment,{children:[(0,e.jsxs)("div",{className:"mc-stats",children:[(0,e.jsxs)("div",{className:"mc-stat mc-card",children:[(0,e.jsx)("b",{children:u.total}),(0,e.jsx)("span",{children:a("total")})]}),(0,e.jsxs)("div",{className:"mc-stat mc-card",children:[(0,e.jsx)("b",{children:u.today}),(0,e.jsx)("span",{children:a("todayAdd")})]}),(0,e.jsxs)("div",{className:"mc-stat mc-card",children:[(0,e.jsx)("b",{children:u.week}),(0,e.jsx)("span",{children:a("weekAdd")})]}),(0,e.jsxs)("div",{className:"mc-stat mc-card",children:[(0,e.jsx)("b",{children:u.tags}),(0,e.jsx)("span",{children:a("tags")})]})]}),(0,e.jsxs)("div",{className:"mc-card",style:{marginBottom:10},children:[(0,e.jsx)("div",{style:{fontSize:12,fontWeight:700,marginBottom:6},children:a("byKind")}),(0,e.jsx)("div",{style:{display:"flex",gap:10,flexWrap:"wrap"},children:Ie.map(x=>(0,e.jsxs)("span",{style:{fontSize:12},children:[(0,e.jsx)("span",{className:"mc-kind",style:{background:Se.colors[x]||"#666"}}),a(ve[x]),": ",u.byKind?.[x]||0]},x))})]}),M&&(M.budgetChars||M.recallLimit||M.embedding)&&(0,e.jsxs)("div",{className:"mc-card",style:{marginBottom:10,fontSize:12},children:[(0,e.jsx)("b",{children:a("budgetLabel")}),"\uFF1A",a("budgetChars")," ",(0,e.jsx)("b",{children:M.budgetChars||8e4})," \xB7 ",a("recallLimitLabel")," ",(0,e.jsx)("b",{children:M.recallLimit||5})," \xB7 ",a("embeddingLabel")," ",M.embedding?"ON":"OFF"]}),(0,e.jsxs)("div",{className:"mc-card",children:[(0,e.jsxs)("div",{style:{fontSize:12,fontWeight:700,marginBottom:6},children:[a("todayList"),"\uFF08",u.todayCards?.length||0,"\uFF09"]}),u.todayCards?.length?(0,e.jsx)("ul",{style:{margin:0,padding:0,listStyle:"none"},children:u.todayCards.map(x=>(0,e.jsx)(R,{title:x.title,kind:x.kind,updated:x.updated,path:x.path},x.path))}):(0,e.jsx)("div",{style:{opacity:.6,fontSize:12},children:a("noOptimize")})]})]})}),s==="optimize"&&(0,e.jsx)("div",{children:j&&(0,e.jsxs)(e.Fragment,{children:[(0,e.jsx)("div",{style:{fontSize:12,fontWeight:700,marginBottom:6},children:a("mergePairs")}),j.merge?.length?(0,e.jsx)("ul",{style:{margin:"0 0 12px",padding:0,listStyle:"none"},children:j.merge.map((x,D)=>(0,e.jsxs)("li",{style:{display:"flex",alignItems:"center",gap:8,padding:"4px 4px",borderBottom:"1px solid rgba(127,127,127,0.12)",fontSize:12},children:[(0,e.jsxs)("span",{style:{flex:1,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:[x.a.title," \u21C4 ",x.b.title]}),(0,e.jsxs)("span",{style:{opacity:.6},children:[Math.round(x.sim*100),"%"]}),(0,e.jsx)("button",{type:"button",className:"mc-btn",style:{padding:"2px 8px"},onClick:()=>ie(x.a.path,x.b.path),children:a("mergeNow")})]},D))}):(0,e.jsx)("div",{style:{opacity:.6,fontSize:12,marginBottom:12},children:a("noOptimize")}),(0,e.jsxs)("div",{style:{fontSize:12,fontWeight:700,marginBottom:6},children:[a("staleCards"),"\uFF08",j.stale?.length||0,"\uFF09"]}),j.stale?.length?(0,e.jsx)("ul",{style:{margin:0,padding:0,listStyle:"none"},children:j.stale.map(x=>(0,e.jsx)(R,{title:x.title,kind:"other",updated:x.updated,path:x.path},x.path))}):(0,e.jsx)("div",{style:{opacity:.6,fontSize:12},children:a("noOptimize")})]})})]})]})]})}function Nt({t:a,card:s,onClose:p,onDelete:b}){(0,d.useEffect)(()=>{let j=C=>{C.key==="Escape"&&p()};return window.addEventListener("keydown",j),()=>window.removeEventListener("keydown",j)},[p]);let[r,u]=(0,d.useState)(!1),S=(s.text||"").length>460;return(0,e.jsxs)("div",{className:"me-overlay",onClick:p,children:[(0,e.jsx)("style",{children:we}),(0,e.jsxs)("div",{className:"me-dialog",onClick:j=>j.stopPropagation(),children:[(0,e.jsxs)("div",{className:"me-dialog-head",children:[(0,e.jsx)("h3",{children:s.title}),(0,e.jsxs)("div",{style:{display:"flex",gap:8},children:[S&&(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>u(j=>!j),children:a(r?"collapse":"expand")}),(0,e.jsx)("button",{type:"button",className:"mc-btn",style:{color:"#f87171"},onClick:()=>{window.confirm(a("deleteConfirm"))&&b&&b(s.path)},children:a("delete")}),(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:p,children:a("close")})]})]}),(0,e.jsx)("div",{className:"me-dialog-body",style:r?{}:{maxHeight:300,overflow:"hidden"},dangerouslySetInnerHTML:{__html:Lt(s.text)}})]})]})}function Mt({t:a,onOpen:s}){let[p,b]=(0,d.useState)(null),[r,u]=(0,d.useState)(""),S=(0,d.useCallback)(async()=>{try{let M=await(await fetch(`${q}/graph`)).json();b(M.ok?M:null),u("")}catch(C){u(String(C&&C.message?C.message:C))}},[]);(0,d.useEffect)(()=>{let C=!0;return fetch(`${q}/graph`).then(M=>M.json()).then(M=>{C&&b(M.ok?M:null)}).catch(M=>{C&&u(String(M&&M.message?M.message:M))}),()=>{C=!1}},[]);let j=(0,d.useCallback)(async C=>{try{let H=await(await fetch(`${q}/delete?path=${encodeURIComponent(C)}`)).json();return H&&H.ok?(await S(),!0):!1}catch{return!1}},[S]);return(0,e.jsxs)("div",{className:"me-graph",children:[(0,e.jsx)("style",{children:we}),r&&(0,e.jsxs)("div",{className:"mc-empty",children:[a("error"),"\uFF1A",r]}),!p&&!r&&(0,e.jsx)("div",{className:"mc-empty",children:a("loading")}),p&&p.nodes.length===0&&(0,e.jsx)("div",{className:"mc-empty",children:a("emptyGraph")}),p&&p.nodes.length>0&&(0,e.jsx)(It,{nodes:p.nodes,edges:p.edges,onOpen:s,onDelete:j,t:a,countLabel:`${p.nodes.length} ${a("nodes")} \xB7 ${St(p.edges)} ${a("edges")}`})]})}function St(a){let s=new Set;for(let p of a||[])s.add([p.source,p.target].sort().join("|"));return s.size}var Se={colors:{project:"#3B82F6",knowledge:"#10B981",content:"#F59E0B",prompt:"#A855F7",business:"#EC4899",tool:"#06B6D4",mistake:"#EF4444",other:"#6B7280"},shapes:{project:"circle",knowledge:"circle",content:"rect",prompt:"diamond",business:"hexagon",tool:"circle",mistake:"diamond",other:"circle"}};function It({nodes:a,edges:s,onOpen:p,onDelete:b,t:r,countLabel:u}){let S=(0,d.useRef)(null),j=(0,d.useRef)(null),C=(0,d.useRef)(null),M=(0,d.useRef)(null),H=(0,d.useRef)(null),G=(0,d.useRef)(p);(0,d.useEffect)(()=>{G.current=p},[p]);let ie=(0,d.useRef)(b);(0,d.useEffect)(()=>{ie.current=b},[b]);let[X,Ie]=(0,d.useState)(""),[R,x]=(0,d.useState)(null),D=(0,d.useRef)("");(0,d.useEffect)(()=>{D.current=X.trim().toLowerCase()},[X]);let[O,$]=(0,d.useState)("all"),F=(0,d.useRef)("all"),[A,ze]=(0,d.useState)(!1),fe=(0,d.useRef)(!1),[U,ee]=(0,d.useState)(null);(0,d.useEffect)(()=>{F.current=O},[O]),(0,d.useEffect)(()=>{fe.current=A},[A]);let Ye=(()=>{let i=X.trim().toLowerCase();return!!i&&!a.some(o=>(o.title||o.name||"").toLowerCase().includes(i)||String(o.kind||"").toLowerCase().includes(i)||String(ve[o.kind]||"").toLowerCase().includes(i))})();(0,d.useEffect)(()=>{let i=C.current;i&&i.wake&&i.wake()},[X]),(0,d.useEffect)(()=>{let i=C.current;i&&i.render&&i.render()},[O,A]);let te=(0,d.useRef)(null),[oe,se]=(0,d.useState)([]),[V,le]=(0,d.useState)(null),[J,ce]=(0,d.useState)(!1),[pe,Te]=(0,d.useState)(null),[ae,Le]=(0,d.useState)(""),be=(0,d.useRef)(null),he=(0,d.useRef)(null),B=(i,o=!0)=>{be.current&&clearTimeout(be.current),Te({msg:i,ok:o}),be.current=setTimeout(()=>Te(null),1900)},ye=i=>{Le(i),he.current&&clearTimeout(he.current),he.current=setTimeout(()=>Le(""),1600)};(0,d.useEffect)(()=>()=>{be.current&&clearTimeout(be.current),he.current&&clearTimeout(he.current)},[]);let _=(0,d.useMemo)(()=>a.reduce((i,o)=>(i[o.id]=o,i),{}),[a]);return(0,d.useEffect)(()=>{let i=S.current;if(!i||a.length===0)return;let o=i.getContext("2d");if(!o)return;let m=Math.max(1,window.devicePixelRatio||1),g=()=>document.documentElement.getAttribute("data-theme")==="dark",T={};s.forEach(n=>{T[n.source]=(T[n.source]||0)+1,T[n.target]=(T[n.target]||0)+1});let L=Math.max(1,...Object.values(T)),t={nodes:a.map(n=>({id:n.id,name:n.title||"",type:n.kind||"other",r:9+Math.min(10,(T[n.id]||0)/L*8),x:0,y:0,vx:0,vy:0})),edges:s.map(n=>({sourceNodeId:n.source,targetNodeId:n.target,weight:1})),panX:0,panY:0,zoom:1,running:!0,raf:0,tickCount:0,quietTicks:0,dragNode:null,selectedId:null,mouseX:0,mouseY:0,w:0,h:0,dpr:m,ctx:o,multi:[],marquee:null};t.domain=a,t.domainById=a.reduce((n,f)=>(n[f.id]=f,n),{}),t.nodes.forEach((n,f)=>{let c=f/t.nodes.length*Math.PI*2-Math.PI/2;n.x=Math.cos(c)*60,n.y=Math.sin(c)*60});let xe=[...new Set(t.nodes.map(n=>n.type))];t.clustered=xe.length>1,t.typeCenters={},xe.forEach((n,f)=>{let c=f/xe.length*Math.PI*2-Math.PI/2;t.typeCenters[n]={x:Math.cos(c)*160,y:Math.sin(c)*160}}),C.current=t;let Re=()=>{let n=i.clientWidth,f=i.clientHeight;t.w=n,t.h=f,i.width=n*m,i.height=f*m,o.setTransform(m,0,0,m,0,0)};Re();let me=new ResizeObserver(Re);me.observe(i.parentElement);let ue=(n,f)=>n&&n.length>f?n.slice(0,f)+"\u2026":n,Ee=(n,f,c,h)=>{if(o.beginPath(),h==="rect")o.rect(n-c,f-c*.75,c*2,c*1.5);else if(h==="diamond")o.moveTo(n,f-c),o.lineTo(n+c,f),o.lineTo(n,f+c),o.lineTo(n-c,f),o.closePath();else if(h==="hexagon"){for(let y=0;y<6;y++){let k=Math.PI/3*y-Math.PI/2,I=n+c*Math.cos(k),l=f+c*Math.sin(k);y===0?o.moveTo(I,l):o.lineTo(I,l)}o.closePath()}else o.arc(n,f,c,0,Math.PI*2)},je=()=>{if(!t.nodes.length)return;let n=1/0,f=-1/0,c=1/0,h=-1/0;t.nodes.forEach(N=>{n=Math.min(n,N.x),f=Math.max(f,N.x),c=Math.min(c,N.y),h=Math.max(h,N.y)});let y=36,k=f-n+y*2,I=h-c+y*2,l=Math.min(t.w/k,t.h/I);t.zoom=Math.max(.45,Math.min(2.4,l*1.22)),t.panX=t.w/2-(n+f)/2*t.zoom,t.panY=t.h/2-(c+h)/2*t.zoom};M.current=je;let qe=()=>{o.clearRect(0,0,t.w,t.h),t.searchTerm=D.current,t.kindFilter=F.current,t.timeMode=fe.current;let n=t.kindFilter,f=l=>String(l||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");o.save(),o.strokeStyle=g()?"rgba(255,255,255,0.04)":"rgba(0,0,0,0.04)",o.lineWidth=.5;for(let l=0;l<t.w;l+=24)o.beginPath(),o.moveTo(l,0),o.lineTo(l,t.h),o.stroke();for(let l=0;l<t.h;l+=24)o.beginPath(),o.moveTo(0,l),o.lineTo(t.w,l),o.stroke();o.restore(),o.save(),o.translate(t.panX,t.panY),o.scale(t.zoom,t.zoom);let c={};t.nodes.forEach(l=>c[l.id]=l);let h=t.nodes.length>40,y=h?1.5:.55,k=t.selectedId||t.hoverId;t.edges.forEach(l=>{let N=c[l.sourceNodeId],z=c[l.targetNodeId];if(!N||!z)return;if(n!=="all"){let K=t.domainById[l.sourceNodeId]&&t.domainById[l.sourceNodeId].kind,Qe=t.domainById[l.targetNodeId]&&t.domainById[l.targetNodeId].kind;if(K!==n&&Qe!==n)return}let v=z.x-N.x,w=z.y-N.y,E=Math.sqrt(v*v+w*w)||1,P=h?12:18,W=-w/E*P,Z=v/E*P,Y=(N.x+z.x)/2+W,re=(N.y+z.y)/2+Z,Q=Se.colors[t.domainById[N.id]&&t.domainById[N.id].kind||"other"],de=k&&(l.sourceNodeId===k||l.targetNodeId===k),ke=k?de?.6:.08:h?.14:.24,Be=parseInt(Q.slice(1,3),16),De=parseInt(Q.slice(3,5),16),Ze=parseInt(Q.slice(5,7),16);o.strokeStyle="rgba("+Be+","+De+","+Ze+","+ke+")",o.lineWidth=de?1.6+(l.weight||1):.8+(l.weight||1)*.5,o.beginPath(),o.moveTo(N.x,N.y),o.quadraticCurveTo(Y,re,z.x,z.y),o.stroke()});let I=[];if(t.nodes.forEach(l=>{let N=t.domainById[l.id]||{},z=N.kind||"other",v=t.timeMode?zt(N.updated):Se.colors[z],w=Se.shapes[z],E=t.selectedId===l.id,P=t.hoverId===l.id,W=t.searchTerm||"",Z=!W||(l.name||"").toLowerCase().includes(W)||String(ve[z]||"").toLowerCase().includes(W)||String(z).toLowerCase().includes(W),Y=n==="all"||z===n,re=k&&l.id!==k&&!t.edges.some(K=>K.sourceNodeId===k&&K.targetNodeId===l.id||K.targetNodeId===k&&K.sourceNodeId===l.id)||W&&!Z||!Y;o.save(),o.globalAlpha=re?.08:1,(E||P)&&(o.shadowColor=v,o.shadowBlur=E?20:14),Ee(l.x,l.y,l.r,w);let Q=parseInt(v.slice(1,3),16),de=parseInt(v.slice(3,5),16),ke=parseInt(v.slice(5,7),16),Be=o.createRadialGradient(l.x-l.r*.3,l.y-l.r*.3,0,l.x,l.y,l.r*1.2);Be.addColorStop(0,"rgba("+Math.min(255,Q+70)+","+Math.min(255,de+70)+","+Math.min(255,ke+70)+",0.95)"),Be.addColorStop(1,v),o.fillStyle=Be,o.fill(),o.restore(),E?(o.save(),Ee(l.x,l.y,l.r+3,w),o.strokeStyle=v,o.lineWidth=3,o.shadowColor=v,o.shadowBlur=12,o.stroke(),o.restore()):P?(o.save(),Ee(l.x,l.y,l.r+2,w),o.strokeStyle=v,o.lineWidth=2,o.stroke(),o.restore()):W&&Z?(o.save(),Ee(l.x,l.y,l.r+2,w),o.strokeStyle="#e11d48",o.lineWidth=2,o.stroke(),o.restore()):t.multi.indexOf(l.id)>=0&&(o.save(),Ee(l.x,l.y,l.r+2,w),o.strokeStyle="#60a5fa",o.lineWidth=2,o.shadowColor="#60a5fa",o.shadowBlur=8,o.stroke(),o.restore());let De=ue(l.name,16);if((E||P||t.zoom>y||!h&&t.zoom>.6)&&Y){let K=1/t.zoom;o.font="500 "+(12*K).toFixed(1)+"px -apple-system,Segoe UI,sans-serif";let ge=o.measureText(De).width+16*K,Pe=18*K,Ce=l.y+l.r+8*K,et=!0;for(let Ne of I)if(l.x-ge/2<Ne.x+Ne.w&&l.x+ge/2>Ne.x&&Ce<Ne.y+Ne.h&&Ce+Pe>Ne.y){et=!1;break}if(!et&&!E&&!P)return;I.push({x:l.x-ge/2,y:Ce,w:ge,h:Pe}),o.fillStyle=g()?"rgba(30,30,35,0.92)":"rgba(255,255,255,0.92)",o.beginPath(),o.roundRect?o.roundRect(l.x-ge/2,Ce,ge,Pe,4*K):o.rect(l.x-ge/2,Ce,ge,Pe),o.fill(),o.strokeStyle=g()?"rgba(255,255,255,0.08)":"rgba(0,0,0,0.08)",o.lineWidth=1*K,o.stroke(),o.fillStyle=g()?E||P?"#eee":"#bbb":E||P?"#111":"#444",o.textAlign="center",o.fillText(De,l.x,Ce+13*K)}}),o.restore(),t.marquee){let l=t.marquee,N=Math.min(l.sx,l.ex),z=Math.min(l.sy,l.ey),v=Math.abs(l.ex-l.sx),w=Math.abs(l.ey-l.sy);o.save(),o.setLineDash([4,4]),o.strokeStyle="rgba(96,165,250,0.9)",o.lineWidth=1,o.fillStyle="rgba(96,165,250,0.12)",o.fillRect(N,z,v,w),o.strokeRect(N,z,v,w),o.restore()}if(t.hoverId&&te.current){let l=t.domainById[t.hoverId],N=t.nodes.find(function(z){return z.id===t.hoverId});if(l&&N){let z=t.edges.filter(function(E){return E.sourceNodeId===l.id||E.targetNodeId===l.id}).length,v=N.x*t.zoom+t.panX,w=N.y*t.zoom+t.panY;te.current.style.display="block",te.current.style.left=v+14+"px",te.current.style.top=w-6+"px",te.current.innerHTML="<b>"+f(l.title||l.name||"")+'</b><br/><span style="opacity:.8">'+f(l.kind||"")+" \xB7 "+z+" \u8FDE\u63A5</span>"}}else te.current&&(te.current.style.display="none")},ne=()=>{t.running=!0,t.raf||(t.raf=requestAnimationFrame(Xe))};t.wake=ne,t.render=qe;let Ue=()=>{let n=t.nodes.length;t.tickCount++;let c=.9-Math.min(.4,t.tickCount/1500),h=n>1e3?3e3:n>100?2e3:n>50?1200:800,y=n>100?.002:.005,k=n>1e3?.012:n>100?.005:.01,I=n>1e3?6:n>200?12:24,l={};t.nodes.forEach(v=>l[v.id]=v);for(let v=0;v<n;v++){if(t.dragNode===t.nodes[v])continue;let w=t.nodes[v],E=0,P=0;for(let Y=0;Y<n;Y++){if(v===Y)continue;let re=w.x-t.nodes[Y].x,Q=w.y-t.nodes[Y].y,de=Math.sqrt(re*re+Q*Q)||1,ke=h/(de*de);E+=re/de*ke,P+=Q/de*ke}if(t.clustered&&t.typeCenters[w.type]){let Y=t.typeCenters[w.type];E+=(Y.x-w.x)*.006,P+=(Y.y-w.y)*.006}else E-=w.x*k,P-=w.y*k;let W=(w.vx+E)*c,Z=(w.vy+P)*c;W=Math.max(-I,Math.min(I,W)),Z=Math.max(-I,Math.min(I,Z)),w.vx=W,w.vy=Z}t.edges.forEach(v=>{let w=l[v.sourceNodeId],E=l[v.targetNodeId];if(!w||!E)return;let P=E.x-w.x,W=E.y-w.y,Z=Math.sqrt(P*P+W*W)||1,Y=(Z-100)*y,re=P/Z*Y,Q=W/Z*Y;t.dragNode!==w&&(w.vx+=re,w.vy+=Q),t.dragNode!==E&&(E.vx-=re,E.vy-=Q)});let N=0;t.nodes.forEach(v=>{t.dragNode!==v&&(v.x+=v.vx,v.y+=v.vy,N+=v.vx*v.vx+v.vy*v.vy)}),t.autoFitPending&&t.tickCount>45&&(t.autoFitPending=!1,je()),(n?Math.sqrt(N/n):0)<.05&&t.tickCount>60&&!t.dragNode?t.quietTicks=(t.quietTicks|0)+1:t.quietTicks=0},Xe=()=>{if(t.running){try{Ue(),qe()}catch{}if(t.quietTicks>30){t.raf=0;return}t.raf=requestAnimationFrame(Xe)}},Fe=(n,f)=>{let c=i.getBoundingClientRect();return{x:(n-c.left-t.panX)/t.zoom,y:(f-c.top-t.panY)/t.zoom}},$e=(n,f,c)=>Math.max(f,Math.min(c,n)),Ke=n=>{t.mouseX=n.clientX,t.mouseY=n.clientY;let f=Fe(n.clientX,n.clientY),c=null;for(let h=t.nodes.length-1;h>=0;h--){let y=t.nodes[h];if(F.current!=="all"&&(t.domainById[y.id]&&t.domainById[y.id].kind)!==F.current)continue;let k=y.x-f.x,I=y.y-f.y;if(k*k+I*I<y.r*y.r+36){c=y.id;break}}c!==t.hoverId&&(t.hoverId=c,ne())},He=n=>{if(n.button!==0)return;let f=Fe(n.clientX,n.clientY),c=null;for(let h=t.nodes.length-1;h>=0;h--){let y=t.nodes[h];if(F.current!=="all"&&(t.domainById[y.id]&&t.domainById[y.id].kind)!==F.current)continue;let k=y.x-f.x,I=y.y-f.y;if(k*k+I*I<y.r*y.r+25){c=y;break}}if(n.shiftKey){t.marquee={sx:n.clientX,sy:n.clientY,ex:n.clientX,ey:n.clientY};return}t.dragStart={x:n.clientX,y:n.clientY,px:t.panX,py:t.panY},t.dragging=!1,t.dragNode=c||null,c&&(t.dragOffset={dx:c.x-f.x,dy:c.y-f.y})},Ge=n=>{if(t.marquee){t.marquee.ex=n.clientX,t.marquee.ey=n.clientY,ne();return}if(!t.dragStart)return;let f=n.clientX-t.dragStart.x,c=n.clientY-t.dragStart.y;if(Math.abs(f)+Math.abs(c)>3&&(t.dragging=!0),t.dragNode){let h=Fe(n.clientX,n.clientY);t.dragNode.x=h.x+t.dragOffset.dx,t.dragNode.y=h.y+t.dragOffset.dy,ne()}else t.dragging&&(t.panX=$e(t.dragStart.px+f,-1e5,1e5),t.panY=$e(t.dragStart.py+c,-1e5,1e5),ne())},Ve=n=>{if(t.marquee){let c=t.marquee;t.marquee=null;let h=i.getBoundingClientRect(),y=Math.min(c.sx,c.ex)-h.left,k=Math.max(c.sx,c.ex)-h.left,I=Math.min(c.sy,c.ey)-h.top,l=Math.max(c.sy,c.ey)-h.top,N=t.nodes.filter(z=>{let v=z.x*t.zoom+t.panX,w=z.y*t.zoom+t.panY;return v>=y&&v<=k&&w>=I&&w<=l}).map(z=>z.id);t.multi=N,se(N),ne();return}if(t.dragStart&&!t.dragging){let c=Fe(n.clientX,n.clientY),h=null;for(let y=t.nodes.length-1;y>=0;y--){let k=t.nodes[y];if(F.current!=="all"&&(t.domainById[k.id]&&t.domainById[k.id].kind)!==F.current)continue;let I=k.x-c.x,l=k.y-c.y;if(I*I+l*l<k.r*k.r+20){h=k;break}}h?(t.selectedId=h.id,x(h.id),ne()):(t.selectedId=null,t.hoverId=null,n.shiftKey||(t.multi=[],se([])),x(null),ne())}t.dragStart=null,t.dragging=!1,t.dragNode=null},_e=n=>{n.preventDefault();let f=Fe(n.clientX,n.clientY),c=null;for(let h=t.nodes.length-1;h>=0;h--){let y=t.nodes[h];if(F.current!=="all"&&(t.domainById[y.id]&&t.domainById[y.id].kind)!==F.current)continue;let k=y.x-f.x,I=y.y-f.y;if(k*k+I*I<y.r*y.r+25){c=y;break}}if(c){let h=t.domainById[c.id];h&&ee({x:n.clientX,y:n.clientY,id:c.id,path:h.id,title:h.title})}else ee(null)},Je=n=>{n.preventDefault();let f=i.getBoundingClientRect(),c=(n.clientX-f.left-t.panX)/t.zoom,h=(n.clientY-f.top-t.panY)/t.zoom,y=Math.max(.3,Math.min(3,t.zoom*(n.deltaY<0?1.1:1/1.1)));t.panX=$e(t.panX+c*(t.zoom-y),-1e5,1e5),t.panY=$e(t.panY+h*(t.zoom-y),-1e5,1e5),t.zoom=y,ne()};i.addEventListener("mousemove",Ke),i.addEventListener("mousedown",He),window.addEventListener("mousemove",Ge),window.addEventListener("mouseup",Ve),i.addEventListener("wheel",Je,{passive:!1}),i.addEventListener("contextmenu",_e),H.current=()=>{t.selectedId=null,t.hoverId=null,t.multi=[],se([]),je(),ne()};for(let n=0;n<200;n++)Ue();return je(),ne(),()=>{t.running=!1,t.raf&&cancelAnimationFrame(t.raf),me.disconnect(),i.removeEventListener("mousemove",Ke),i.removeEventListener("mousedown",He),window.removeEventListener("mousemove",Ge),window.removeEventListener("mouseup",Ve),i.removeEventListener("wheel",Je),i.removeEventListener("contextmenu",_e)}},[a,s]),(0,e.jsxs)("div",{className:"me-graph",ref:j,children:[(0,e.jsxs)("div",{className:"me-graph-toolbar",children:[(0,e.jsxs)("span",{className:"me-graph-count",children:[a.length," ",r("nodes")," \xB7 ",s.length," ",r("edges")]}),(0,e.jsx)("span",{className:"spacer",style:{flex:1}}),(0,e.jsx)("input",{type:"text",className:"mc-btn",style:{padding:"6px 10px",minWidth:140},placeholder:r("search"),value:X,onChange:i=>Ie(i.target.value)}),(0,e.jsx)("button",{type:"button",className:`mc-btn${A?" me-on":""}`,onClick:()=>ze(i=>!i),children:r("timeDim")}),(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{let i=S.current;if(i)try{i.toBlob(o=>{o&&le({url:URL.createObjectURL(o),blob:o})},"image/png")}catch{}},children:r("exportGraph")}),(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>M.current&&M.current(),children:r("fit")}),(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{x(null),$("all"),H.current&&H.current()},children:r("reset")})]}),(0,e.jsxs)("div",{className:"me-graph-canvas",style:{position:"relative",overflow:"hidden"},children:[(0,e.jsx)("canvas",{ref:S,style:{width:"100%",height:"100%",display:"block",cursor:"grab"}}),(0,e.jsx)("div",{className:"me-graph-tooltip",ref:te,style:{display:"none",position:"absolute",zIndex:4,pointerEvents:"none",background:"rgba(28,28,32,0.88)",backdropFilter:"blur(10px)",WebkitBackdropFilter:"blur(10px)",color:"#eee",borderRadius:8,padding:"7px 10px",fontSize:12,maxWidth:220,boxShadow:"0 8px 24px rgba(0,0,0,.2)"}}),(0,e.jsxs)("div",{className:"me-graph-legend",style:{position:"absolute",left:12,bottom:8,zIndex:2},children:[A?[{c:"#10b981",l:r("timeNew")},{c:"#f59e0b",l:r("timeRecent")},{c:"#f97316",l:r("timeMonth")},{c:"#94a3b8",l:r("timeOld")}].map(i=>(0,e.jsxs)("span",{className:"lg",children:[(0,e.jsx)("span",{className:"mc-kind",style:{background:i.c}}),i.l]},i.c)):Object.keys(We).map(i=>(0,e.jsxs)("button",{type:"button",className:`lg${O===i?" active":""}`,onClick:()=>$(o=>o===i?"all":i),children:[(0,e.jsx)("span",{className:"mc-kind",style:{background:Se.colors[i]||We[i]}}),r(ve[i])]},i)),!A&&O!=="all"&&(0,e.jsxs)("button",{type:"button",className:"lg lg-clear",onClick:()=>$("all"),children:[r("clearFilter")," \u2715"]})]}),Ye&&(0,e.jsx)("div",{className:"mc-empty",style:{position:"absolute",inset:0,zIndex:2,display:"flex",alignItems:"center",justifyContent:"center",backdropFilter:"blur(2px)",WebkitBackdropFilter:"blur(2px)"},children:r("noMatch")}),U&&(0,e.jsx)("div",{className:"me-graph-ctx-back",style:{position:"fixed",inset:0,zIndex:30},onMouseDown:()=>ee(null)}),U&&(0,e.jsxs)("div",{className:"me-graph-ctxmenu",style:{position:"fixed",left:U.x,top:U.y,zIndex:31},onMouseDown:i=>i.stopPropagation(),children:[(0,e.jsx)("button",{type:"button",onClick:()=>{G.current({path:U.path,title:U.title}),ee(null)},children:r("openCard")}),(0,e.jsx)("button",{type:"button",onClick:()=>{U.id&&(C.current.selectedId=U.id,x(U.id)),ee(null);let i=C.current;i&&i.wake&&i.wake()},children:r("focusNeighbors")}),(0,e.jsx)("button",{type:"button",onClick:()=>{try{navigator.clipboard&&navigator.clipboard.writeText(U.title||"")}catch{}ee(null),B(r("copied"))},children:r("copyName")}),(0,e.jsx)("button",{type:"button",style:{color:"#f87171"},onClick:()=>{let i=U.path;ee(null),window.confirm(r("deleteConfirm"))&&(async()=>{try{(ie.current?await ie.current(i):!1)?B(r("deleted")):B(r("deleteFail"),!1)}catch{B(r("deleteFail"),!1)}})()},children:r("delete")})]}),oe.length>0&&(0,e.jsxs)("div",{style:{position:"absolute",left:10,top:10,zIndex:3,display:"flex",gap:8,alignItems:"center",background:"rgba(28,28,32,0.92)",backdropFilter:"blur(10px)",WebkitBackdropFilter:"blur(10px)",color:"#eee",borderRadius:10,padding:"6px 10px",fontSize:12,boxShadow:"0 10px 30px rgba(0,0,0,.25)"},children:[(0,e.jsxs)("span",{style:{fontWeight:700},children:[oe.length," ",r("nodes")]}),(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{let o=oe.map(L=>_[L]).filter(Boolean).map(L=>L.title||L.name).join(`
|
|
113
|
+
`),m=new Blob([o],{type:"text/plain;charset=utf-8"}),g=URL.createObjectURL(m),T=document.createElement("a");T.href=g,T.download="memory-selected.txt",document.body.appendChild(T),T.click(),document.body.removeChild(T),setTimeout(()=>URL.revokeObjectURL(g),1e3),B(oe.length+r("exportedSel"))},children:r("exportSel")}),(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{if(oe.length<2){B(r("mergeNeed"),!1);return}let i=oe.map(m=>_[m]?_[m].id:m);if(!window.confirm(r("mergeConfirm")))return;se([]);let o=C.current;o&&(o.multi=[]),fetch(`${q}/merge?paths=${encodeURIComponent(i.join(","))}`).then(m=>m.json()).then(m=>{m&&m.ok?B(r("merged")):B(r("mergeFail"),!1)}).catch(()=>B(r("mergeFail"),!1))},children:r("merge")}),(0,e.jsx)("button",{type:"button",className:"mc-btn",style:{color:"#f87171"},onClick:()=>{if(!window.confirm(r("deleteConfirm")))return;let i=oe.map(m=>_[m]?_[m].id:m);se([]);let o=C.current;o&&(o.multi=[]),(async()=>{let m=0;for(let g of i)try{(ie.current?await ie.current(g):!1)&&m++}catch{}B(m+r("deletedSelected"),m>0)})()},children:r("deleteSelected")}),(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{let i=C.current;i&&(i.multi=[]),se([]);let o=C.current;o&&o.render&&o.render()},children:r("clearSelection")})]}),R&&(0,e.jsxs)("div",{className:"me-graph-sidebar",style:{position:"absolute",right:10,top:10,zIndex:3,maxWidth:240,background:"rgba(28,28,32,0.92)",backdropFilter:"blur(10px)",WebkitBackdropFilter:"blur(10px)",color:"#eee",borderRadius:10,padding:12,fontSize:12,boxShadow:"0 10px 30px rgba(0,0,0,.25)"},children:[(0,e.jsx)("div",{style:{fontWeight:700,fontSize:13,marginBottom:4},children:_[R]?_[R].title||_[R].name:R}),(0,e.jsx)("div",{style:{opacity:.8,marginBottom:8},children:_[R]?r(ve[_[R].kind]||"kindKnowledge"):"\u2014"}),(0,e.jsx)("div",{style:{fontSize:11,opacity:.65,letterSpacing:".04em",marginBottom:6},children:r("graphHint")}),(s.filter(i=>i.source===R||i.target===R)||[]).slice(0,8).map((i,o)=>{let m=i.source===R?i.target:i.source,g=_[m];return(0,e.jsx)("div",{style:{padding:"4px 6px",cursor:"pointer",borderRadius:6},onMouseDown:T=>{T.preventDefault();let L=C.current;L&&(L.selectedId=m,L.hoverId=m,L.wake&&L.wake()),x(m)},children:g?(g.title||g.name).slice(0,28):m},o)})]})]}),(0,e.jsx)("div",{className:"me-graph-tip",children:r("graphTip")}),pe&&(0,e.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:8,position:"fixed",left:"50%",transform:"translateX(-50%)",top:22,zIndex:70,background:"rgba(20,22,26,0.97)",backdropFilter:"blur(12px)",WebkitBackdropFilter:"blur(12px)",color:"#f5f5f5",borderRadius:12,padding:"10px 20px",fontSize:13.5,fontWeight:600,boxShadow:"0 14px 44px rgba(0,0,0,.5)",pointerEvents:"none",animation:"me-pop .22s ease",borderLeft:"4px solid "+(pe.ok?"#22c55e":"#ef4444"),maxWidth:"90vw"},children:[(0,e.jsx)("span",{style:{color:pe.ok?"#34d399":"#f87171",fontWeight:800,fontSize:15},children:pe.ok?"\u2713":"\u2715"}),(0,e.jsx)("span",{style:{wordBreak:"break-all"},children:pe.msg})]}),V&&(0,e.jsxs)("div",{className:"me-overlay",onClick:()=>{V.url&&URL.revokeObjectURL(V.url),le(null),ce(!1)},children:[(0,e.jsx)("style",{children:we}),(0,e.jsxs)("div",{className:"me-dialog",style:{maxWidth:J?"100vw":900,width:J?"100vw":"92vw",maxHeight:J?"100vh":"90vh",height:J?"100vh":void 0,borderRadius:J?0:14},onClick:i=>i.stopPropagation(),children:[(0,e.jsxs)("div",{className:"me-dialog-head",children:[(0,e.jsx)("h3",{children:r("exportGraph")}),(0,e.jsxs)("div",{style:{display:"flex",gap:8},children:[(0,e.jsx)("a",{className:"mc-btn",href:V.url,download:"memory-graph.png",onClick:()=>{ye("download"),B(r("downloaded")+" \xB7 "+r("defaultDownloads"))},style:{textDecoration:"none",display:"inline-flex",alignItems:"center"},children:r(ae==="download"?"done":"download")}),(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{ye("save");let i=V.blob;i&&typeof window.showSaveFilePicker=="function"?rt(i,"memory-graph.png",!0).then(o=>{o.ok?B(r("exportedTo")+"\uFF1A"+o.name):o.aborted?B(r("exportCancel")):B(r("exportFail"),!1)}):B(r("saveAsUnsupported"),!1)},children:r(ae==="save"?"done":"saveAs")}),(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{ye("full"),ce(i=>!i),B(r("fullscreen"))},children:r(ae==="full"?"done":J?"exitFull":"fullscreen")}),(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{let i=V.blob;i&&window.ClipboardItem&&navigator.clipboard?navigator.clipboard.write([new window.ClipboardItem({"image/png":i})]).then(()=>{ye("copy"),B(r("copied"))}).catch(()=>{B(r("copyFail"),!1)}):B(r("copyFail"),!1)},children:r(ae==="copy"?"done":"copyImage")}),(0,e.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{V.url&&URL.revokeObjectURL(V.url),le(null),ce(!1)},children:r("close")})]})]}),(0,e.jsx)("div",{className:"me-dialog-body",style:{display:"flex",justifyContent:"center",background:J?"var(--dsw-alias-bg-base, #0b0d10)":"var(--dsw-alias-bg-base, #f3f4f6)"},children:(0,e.jsx)("img",{src:V.url,alt:r("exportGraph"),style:{maxWidth:"100%",maxHeight:J?"calc(100vh - 64px)":"70vh",borderRadius:J?0:8,cursor:"zoom-in"},onClick:()=>ce(i=>!i)})})]})]})]})}function zt(a){let s=new Date(a||0).getTime();if(!s)return"#94a3b8";let p=(Date.now()-s)/864e5;return p<3?"#10b981":p<14?"#f59e0b":p<30?"#f97316":"#94a3b8"}function Tt(a){let s=new Date(a||0).getTime();return!!s&&(Date.now()-s)/864e5<3}async function rt(a,s,p){if(p&&typeof window.showSaveFilePicker=="function")try{let u=await window.showSaveFilePicker({suggestedName:s}),S=await u.createWritable();return await S.write(a),await S.close(),{ok:!0,picked:!0,name:u.name}}catch(u){return u&&u.name==="AbortError"?{ok:!1,aborted:!0}:{ok:!1,err:String(u.message||u)}}let b=URL.createObjectURL(a),r=document.createElement("a");return r.href=b,r.download=s,document.body.appendChild(r),r.click(),document.body.removeChild(r),setTimeout(()=>URL.revokeObjectURL(b),1e3),{ok:!0,picked:!1,name:s}}function ot(a,s){if(!s||!a)return a;let p=a.toLowerCase(),b=s.toLowerCase(),r=[],u=0;for(;u<a.length;){let S=p.indexOf(b,u);if(S<0){r.push(a.slice(u));break}S>u&&r.push(a.slice(u,S)),r.push((0,e.jsx)("mark",{className:"mc-hl",children:a.slice(S,S+b.length)},S)),u=S+b.length}return r}function Lt(a){if(!a)return"";let s=String(a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"),p=[];return s=s.replace(/```([\w-]*)\n([\s\S]*?)```/g,function(b,r,u){return p.push("<pre><code>"+u+"</code></pre>"),"\0"+(p.length-1)+"\0"}),s=s.replace(/^(#{1,3})\s+(.+)$/gm,function(b,r,u){return"<h"+r.length+">"+u+"</h"+r.length+">"}),s=s.replace(/\*\*([^*]+)\*\*/g,"<strong>$1</strong>"),s=s.replace(/`([^`\n]+)`/g,"<code>$1</code>"),s=s.replace(/(^|[^*])\*([^*]+)\*/g,"$1<em>$2</em>"),s=s.replace(/\[([^\]]+)\]\(([^)]+)\)/g,function(b,r,u){return/^(https?:|\/)/.test(u)?'<a href="'+u+'" target="_blank" rel="noopener noreferrer">'+r+"</a>":r}),s=s.replace(/^(-)\s+(.+)$/gm,"\u2022 $2"),s=s.replace(/^\s*(>)\s*(.+)$/gm,"<blockquote>$2</blockquote>"),s=s.replace(/\n/g,"<br/>"),s=s.replace(/\u0000(\d+)\u0000/g,function(b,r){return p[r]}),s}function it(a){if(!a)return"";let s=new Date(a);if(Number.isNaN(s.getTime()))return"";let p=new Date,b=s.toDateString()===p.toDateString(),r=S=>String(S).padStart(2,"0"),u=`${r(s.getHours())}:${r(s.getMinutes())}`;return b?u:`${s.getMonth()+1}-${s.getDate()} ${u}`}
|
|
114
114
|
|
|
115
115
|
return module.exports;
|
|
116
116
|
},
|
package/lib/vault.js
CHANGED
|
@@ -386,6 +386,58 @@ export async function graph(root) {
|
|
|
386
386
|
return { nodes, edges }
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
+
/** 统计(增强):overview 全量 + 今日/本周新增 + 今日新增卡片明细。 */
|
|
390
|
+
export async function stats(root) {
|
|
391
|
+
const cards = await listCards(root)
|
|
392
|
+
const byKind = {}
|
|
393
|
+
for (const c of cards) byKind[c.kind] = (byKind[c.kind] || 0) + 1
|
|
394
|
+
const now = Date.now()
|
|
395
|
+
const day = now - 86400000
|
|
396
|
+
const week = now - 7 * 86400000
|
|
397
|
+
const today = cards.filter((c) => c.mtime > day)
|
|
398
|
+
const tagSet = new Set()
|
|
399
|
+
for (const c of cards) for (const t of c.tags) tagSet.add(t)
|
|
400
|
+
return {
|
|
401
|
+
total: cards.length,
|
|
402
|
+
today: today.length,
|
|
403
|
+
week: cards.filter((c) => c.mtime > week).length,
|
|
404
|
+
tags: tagSet.size,
|
|
405
|
+
byKind,
|
|
406
|
+
todayCards: today.slice(0, 30).map((c) => ({ path: c.path, title: c.title, kind: c.kind, updated: c.updated, summary: c.summary.slice(0, 80) })),
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** 整理建议(非破坏性预览):高相似卡对 + 陈旧卡(>90 天未更新且无连线)。 */
|
|
411
|
+
export async function optimizeCandidates(root) {
|
|
412
|
+
const cards = await listCards(root)
|
|
413
|
+
const merge = []
|
|
414
|
+
const bodies = new Map()
|
|
415
|
+
for (const c of cards) {
|
|
416
|
+
try { bodies.set(c.path, (await fs.readFile(path.join(root, c.path), 'utf8')).slice(0, 4000)) } catch { bodies.set(c.path, '') }
|
|
417
|
+
}
|
|
418
|
+
for (let i = 0; i < cards.length; i++) for (let j = i + 1; j < cards.length; j++) {
|
|
419
|
+
const a = cards[i], b = cards[j]
|
|
420
|
+
if (a.kind !== b.kind) continue
|
|
421
|
+
const sim = textSimilarity(bodies.get(a.path) || '', bodies.get(b.path) || '')
|
|
422
|
+
if (sim >= 0.55) merge.push({ a: { path: a.path, title: a.title }, b: { path: b.path, title: b.title }, sim: Math.round(sim * 100) / 100 })
|
|
423
|
+
}
|
|
424
|
+
merge.sort((x, y) => y.sim - x.sim)
|
|
425
|
+
const old = Date.now() - 90 * 86400000
|
|
426
|
+
const stale = cards.filter((c) => c.mtime < old).slice(0, 30).map((c) => ({ path: c.path, title: c.title, updated: c.updated }))
|
|
427
|
+
return { merge: merge.slice(0, 40), stale }
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** 召回反馈记录:命中是否有用(供相关性调优)。文件存 vault 根 `.feedback.json`。 */
|
|
431
|
+
export async function readFeedback(root) {
|
|
432
|
+
try { return JSON.parse(await fs.readFile(path.join(root, '.feedback.json'), 'utf8')) } catch { return [] }
|
|
433
|
+
}
|
|
434
|
+
export async function addFeedback(root, rec) {
|
|
435
|
+
const list = await readFeedback(root)
|
|
436
|
+
list.unshift({ ts: Date.now(), ...rec })
|
|
437
|
+
await fs.writeFile(path.join(root, '.feedback.json'), JSON.stringify(list.slice(0, 500), null, 2), 'utf8')
|
|
438
|
+
return { ok: true }
|
|
439
|
+
}
|
|
440
|
+
|
|
389
441
|
/** 统计:按 kind 计数 + 最近 7 天更新数 + 总标签数。 */
|
|
390
442
|
export async function overview(root) {
|
|
391
443
|
const cards = await listCards(root)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-memory-eternal",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "记忆核心(Memory Eternal):自研的 DeepSeek Harness 记忆插件,不移植任何既有记忆框架——对话结束后自动沉淀知识卡到本地 Markdown Vault(自研去重、自研 CJK 检索、可 git 管理),设置页提供图形化知识库(统计 / 搜索 / 知识图谱 + 侧边栏一键弹窗),Agent 通过 memory_recall 工具按需召回历史上下文。零人工干预。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|