dsh-memory-eternal 0.3.0 → 0.4.0

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 CHANGED
@@ -1,11 +1,18 @@
1
1
  # 记忆核心(dsh-memory-eternal)
2
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
+ </p>
9
+
3
10
  > 🧠 **给 DeepSeek Harness 装上「第二大脑」**:一款**自研独立**的 DSH 记忆插件,为任意 DeepSeek Harness 提供对话级长期记忆——对话结束后**自动**把值得长期复用的内容压缩成知识卡,写入本地 Markdown Vault(自研去重、自研 CJK 检索、可 git 管理);设置页提供**图形化知识库**:统计概览、CJK 检索、知识卡网格、自研知识图谱。Agent 通过 `memory_recall` 工具按需召回历史上下文。**一条命令安装,零人工干预,不改 dsh 源码。**
4
11
 
5
12
  ---
6
13
 
7
14
  <p align="center">
8
- <img src="assets/dsh-memory-eternal.gif" width="880" alt="记忆核心 DSH 插件效果演示(动态)" />
15
+ <img src="assets/screen/dsh-memory-eternal.gif" width="880" alt="记忆核心 DSH 插件效果演示(动态)" />
9
16
  <br/>
10
17
  <em>记忆核心 · 对话自动沉淀 + 图形化知识库 + 增强知识图谱(动态演示)</em>
11
18
  </p>
@@ -39,7 +46,7 @@
39
46
  | 📚 **本地 Markdown Vault** | 知识卡是带 frontmatter 的普通 `.md` 文件,默认落在 `~/.dsh/memory-vault`,可手动编辑、可 git 版本管理、可被任何工具读取,不锁进私有数据库 |
40
47
  | 🚫 **智能去重** | 双层去重:**自研词法 Jaccard bigram**(0.62 阈值)+ **语义去重**(把已有卡片索引喂给模型,由模型决定「新建 vs 追加到已有卡」,对 LLM 改写免疫) |
41
48
  | 🔍 **CJK 感知检索** | 中文整词 + 字符 bigram 命中,短查询「强化学习」「蒸馏」也能在长文里命中,无需全文搜索引擎 |
42
- | 🕸 **知识图谱** | 卡片间的 `[[wikilink]]` 与共享标签自动连线,SVG 图谱左键凸显关联、右键打开卡片阅读 |
49
+ | 🕸 **知识图谱** | 卡片间的 `[[wikilink]]`、共享标签与**相似度关联**自动连线;Canvas 图谱支持**图例过滤、时间维着色、右键菜单、Shift 框选多选、导出/删除/合并**,交互全面 |
43
50
  | 🧠 **自动召回** | 注入 system prompt 告知 Agent 它拥有记忆核心,并注册 `memory_recall` 工具——需要项目背景/历史决策/领域知识时自动检索 |
44
51
  | 🔒 **本地优先** | 所有内容留在你的本机 Vault;不设遥测、不传云端。模型请求仍走你已配置好的 provider |
45
52
  | ⚙️ **一键安装** | 与 `dsh-ui-three-body` 同款插件机制,`dsh plugin --profile web add dsh-memory-eternal` 即可 |
@@ -68,6 +75,19 @@ dsh plugin --profile web add F:/MyApp/eternal/dsh-memory-eternal
68
75
 
69
76
  装完**重启 dsh web**:设置 → 记忆 出现知识库页面;侧边栏底部也会出现「记忆」按钮,一键打开完整记忆库弹窗。此后每轮对话自动沉淀知识卡。
70
77
 
78
+ ### 🧭 插件发现 / 收录标准
79
+
80
+ 本插件面向 DSH 插件生态,遵循社区通用发现标准,可被以下机制检索/收录:
81
+
82
+ | 机制 | 说明 | 状态 |
83
+ | --- | --- | --- |
84
+ | **GitHub `dsh-plugin` topic** | 社区 `dsh-find-plugin` 按该 topic + 星数在会话内实时检索(已在 GitHub 打 `dsh-plugin` 标签) | ✅ |
85
+ | **dsh-find-plugin** | 会话内发现 DSH 插件:[awesome-dsh-plugin/dsh-find-plugin](https://github.com/awesome-dsh-plugin/dsh-find-plugin) | ✅ |
86
+ | **dsh-marketplace** | 安全、活跃的插件市场:[ouyangyipeng/dsh-marketplace](https://github.com/ouyangyipeng/dsh-marketplace) | 提交中 |
87
+ | **dsh-plugin-marketplace** | 实测/自主插件市场:[YELEBAI/dsh-plugin-marketplace](https://github.com/YELEBAI/dsh-plugin-marketplace) | 提交中 |
88
+
89
+ > 收录为**社区市场维护者的 PR 审核**,非发布动作;本插件已具备 `files` 白名单、`exports`、`cordis.patch.yml` 等标准元数据,符合接入前提。
90
+
71
91
  ---
72
92
 
73
93
  ## 🧠 自动沉淀(零人工干预)
@@ -126,7 +146,34 @@ Vault 目录结构:
126
146
  - **统计概览**:总卡数、近 7 天新增、标签数、知识卡数
127
147
  - **检索**:中文片段即时搜索(280ms 防抖)
128
148
  - **知识卡网格**:kind 筛选(全部/项目/知识/内容/提示词/业务/工具/教训),点击卡片阅读全文(frontmatter 一并展示)
129
- - **知识图谱(增强版)**:**多环径向 / 力导向**混合布局(按 kind 分扇区、枢纽在内圈、环间距不重叠,数量大也不乱);节点 = 知识卡(按 kind 渐变+发光着色、按连接数自动放大);连线 = `[[wikilink]]` 或共享标签(曲线渐变 + 流动动画,只保留**高影响力主干边**避免毛球);**默认只显示枢纽标签**、放大后全量显示;**几千节点不卡**(节点/连线上限 + 标签节流 + O(n) 布局);**滚轮缩放、按住左键拖动画布**、**左键凸显关联节点并淡化无关节点**、**右键打开卡片**、悬停高亮
149
+ - **知识图谱(增强版)**:**多环径向 / 力导向**混合布局(按 kind 分扇区、枢纽在内圈、环间距不重叠,数量大也不乱);节点 = 知识卡(按 kind 渐变+发光着色、按连接数自动放大);连线 = `[[wikilink]]`、共享标签或**相似度关联**(曲线渐变,只保留**高影响力主干边**避免毛球);**默认只显示枢纽标签**、放大后全量显示;**几千节点不卡**;**滚轮缩放、按住左键拖动画布**、**左键凸显关联节点并淡化无关节点**、悬停高亮
150
+
151
+ ### 🕸 图谱交互速览
152
+
153
+ | 操作 | 效果 |
154
+ | --- | --- |
155
+ | 左键点击节点 | 凸显该节点及关联(淡化无关) |
156
+ | **右键节点** | 弹出菜单:打开卡片 / 凸显关联 / 复制名称 / **删除** |
157
+ | **Shift + 拖拽** | 方形**框选多节点** → 浮出「导出选中 / 删除选中 / **合并** / 清空」 |
158
+ | 滚轮 | 以光标为中心缩放 |
159
+ | 左键拖空白 | 平移画布 |
160
+ | 点图例 | **按 kind 过滤**(再点取消) |
161
+ | **时间维** 开关 | 节点按最后更新时间着色:3 天内绿 / 2 周内琥珀 / 1 月内橙 / 更早灰蓝 |
162
+ | 搜索框 | 标题 + 类型名匹配,无结果有提示 |
163
+ | **导出** | 一键导出 PNG(预览弹窗:下载 / **另存为** / 复制 / 全屏) |
164
+
165
+ ### 📇 知识卡管理
166
+
167
+ - 卡片列表搜索命中,标题与摘要**关键词高亮**(`<mark>`)
168
+ - 3 天内更新的卡自动标「**新**」角标;排序支持 最近 / 标题 / 热点
169
+ - 打开长卡**默认折叠**,可「展开全部 / 收起」
170
+ - 卡片行右下角「✕」**删除记忆**(确认后删),阅读器内亦可删除
171
+ - 列表**无限滚动懒加载**(滚动到底自动加载下一批)
172
+ - 自定义**滚动条**配色,明暗主题都清晰可见
173
+ - 阅读全文走**轻量 Markdown 渲染**(标题/加粗/列表/代码块/引用/安全链接,先转义再白名单)
174
+ - 记忆库工具栏:**导入 / 导出 MD / 导出 JSON / 刷新**(导出可选**自选位置**另存为,或默认下载)
175
+ - 所有操作**顶部醒目 toast** 反馈:✓ 绿 = 成功、✕ 红 = 失败,并显示导出位置 / 文件名
176
+ - 导出分两类:**知识卡片**(MD/JSON)与**知识图谱**(PNG),均可选**另存为**自定位置
130
177
 
131
178
  ## 🔧 设置
132
179
 
@@ -170,12 +217,19 @@ dsh-memory-eternal/
170
217
  - [x] 图形化知识库:统计 / 检索 / 知识卡网格
171
218
  - [x] 侧边栏「记忆」按钮 + 完整记忆库弹窗
172
219
  - [x] 增强知识图谱:力导向/径向布局、滚轮缩放、左键拖拽、右键凸显关联、大规模抗压
220
+ - [x] 图谱交互增强:图例过滤、时间维着色、右键菜单、Shift 框选多选(导出/删除/合并)
221
+ - [x] 知识卡管理:Markdown 渲染、长卡折叠、新卡角标、搜索高亮、最近/标题/热点排序
222
+ - [x] 记忆库导出/导入(MD/JSON 备份,异地可迁移)
223
+ - [x] 知识卡删除 + 无限滚动懒加载 + 自定义滚动条
224
+ - [x] 导出可选默认下载 / 自选位置另存为(卡片 MD/JSON 与图谱 PNG)
173
225
  - [ ] 每日记忆回顾 + 用量统计
174
226
  - [ ] 多语言模板与自定义去重规则
175
- - [ ] 卡片批量导入 / 导出 / 拖拽归类
227
+ - [ ] 卡片拖拽归类 / 批量合并更智能(按相似度预分组)
228
+ - [ ] Market 收录:dsh-marketplace / dsh-plugin-marketplace(待社区审核)
176
229
 
177
230
  ## 📦 发布记录
178
231
 
232
+ - **v0.4.0**(已发布):图谱交互全面增强——**图例过滤**、**时间维着色**、**右键菜单**、**Shift 框选多选**、**单删/批量删除**、**多卡合并**、**一键导出 PNG**(预览:下载/另存为/复制/全屏);知识卡——**Markdown 渲染**、**长卡折叠**、**新卡角标**、**搜索命中高亮**、**卡片行删除**、**无限滚动懒加载**、最近/标题/热点排序、自定义滚动条;**相似度关联建议**(similar 边);**记忆库导出/导入**(MD/JSON 备份,导出可选**自选位置**);操作**顶部醒目绿/红 toast**(反馈结果 + 导出位置/文件名);默认视图与节点调大。
179
233
  - **v0.3.0**:知识图谱**科学布局**升级——多环径向/力导向混合布局(按 kind 分扇区、枢纽在内圈、环间距不重叠,数量大也不乱);只保留**高影响力主干边**(避免毛球);默认只显示**枢纽标签**、放大后全量;大图关闭节点发光提升性能。
180
234
  - **v0.2.9**:修复 **GitHub 源安装无法启动**——提交 `lib/client.js`(此前被忽略未入库,GitHub 拉取的包缺客户端 bundle,导致 DSH 启动失败)。
181
235
  - **v0.2.8**:安装说明去掉 `npx` 前缀,只保留跨平台 `dsh plugin --profile web add`。
Binary file
Binary file
Binary file
package/index.js CHANGED
@@ -19,7 +19,7 @@ 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 } from './lib/vault.js'
22
+ import { ensureVault, listCards, readCard, search, graph, overview, exportCards, deleteCard, writeCard, parseCard } from './lib/vault.js'
23
23
  import { summarizeTurn, extractLastTurn, sliceNewEvents, resolveRoute, captureCard, captureUpdate, pickNeighbors } from './lib/capture.js'
24
24
 
25
25
  export const name = 'memory-eternal'
@@ -267,6 +267,58 @@ async function handleApi(req, res, vaultRoot) {
267
267
  json(res, 200, { ok: true, ...(await graph(vaultRoot)) })
268
268
  return
269
269
  }
270
+ case '/export': {
271
+ await ensureVault(vaultRoot)
272
+ json(res, 200, { ok: true, cards: await exportCards(vaultRoot) })
273
+ return
274
+ }
275
+ case '/delete': {
276
+ const rel = query.get('path') || ''
277
+ if (!rel) return json(res, 400, { ok: false, error: '缺少 path' })
278
+ await deleteCard(vaultRoot, rel)
279
+ json(res, 200, { ok: true })
280
+ return
281
+ }
282
+ case '/import': {
283
+ let raw = ''
284
+ for await (const chunk of req) raw += chunk
285
+ if (raw.length > 20 * 1024 * 1024) return json(res, 413, { ok: false, error: '文件过大' })
286
+ let payload
287
+ try { payload = JSON.parse(raw || '{}') } catch { return json(res, 400, { ok: false, error: 'JSON 解析失败' }) }
288
+ const list = payload.cards || []
289
+ if (!Array.isArray(list)) return json(res, 400, { ok: false, error: '缺少 cards 数组' })
290
+ let imported = 0, skipped = 0
291
+ for (const c of list) {
292
+ const text = c.text || ''
293
+ let kind = c.kind || 'knowledge', title = c.title || '导入记忆', tags = [], body = text, source = ''
294
+ try { const p = parseCard(text); kind = p.meta.kind || kind; title = p.meta.title || title; tags = p.meta.tags || tags; body = p.body; source = p.meta.source } catch {}
295
+ const r = await writeCard(vaultRoot, { kind, title, tags, body, source })
296
+ if (r.ok) imported++; else skipped++
297
+ }
298
+ json(res, 200, { ok: true, imported, skipped })
299
+ return
300
+ }
301
+ case '/merge': {
302
+ const paths = (query.get('paths') || '').split(',').map((p) => p.trim()).filter(Boolean)
303
+ if (paths.length < 2) return json(res, 400, { ok: false, error: '至少选 2 张卡' })
304
+ const texts = []
305
+ for (const p of paths) { try { texts.push({ path: p, text: await readCard(vaultRoot, p) }) } catch { /* 跳过读不到的 */ } }
306
+ if (texts.length < 2) return json(res, 400, { ok: false, error: '读取失败' })
307
+ const first = parseCard(texts[0].text)
308
+ const tags = new Set(first.meta.tags || [])
309
+ let combined = ''
310
+ texts.forEach((t, i) => {
311
+ let body = t.text
312
+ try { const p = parseCard(t.text); body = p.body; (p.meta.tags || []).forEach((x) => tags.add(x)) } catch {}
313
+ combined += (i ? '\n\n---\n\n' : '') + body.trim()
314
+ })
315
+ const title = (first.meta.title || '合并记忆') + '(合并)'
316
+ const r = await writeCard(vaultRoot, { kind: first.meta.kind || 'knowledge', title, tags: [...tags], body: combined, source: 'merge' }, { dedup: false })
317
+ if (!r.ok) return json(res, 400, { ok: false, error: '写入失败' })
318
+ for (const t of texts) { try { await deleteCard(vaultRoot, t.path) } catch { /* 尽力删除 */ } }
319
+ json(res, 200, { ok: true })
320
+ return
321
+ }
270
322
  default:
271
323
  json(res, 404, { ok: false, error: '未知接口' })
272
324
  }
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 ae=Object.create;var q=Object.defineProperty;var re=Object.getOwnPropertyDescriptor;var ne=Object.getOwnPropertyNames;var oe=Object.getPrototypeOf,se=Object.prototype.hasOwnProperty;var ie=(e,r)=>{for(var n in r)q(e,n,{get:r[n],enumerable:!0})},V=(e,r,n,o)=>{if(r&&typeof r=="object"||typeof r=="function")for(let g of ne(r))!se.call(e,g)&&g!==n&&q(e,g,{get:()=>r[g],enumerable:!(o=re(r,g))||o.enumerable});return e};var le=(e,r,n)=>(n=e!=null?ae(oe(e)):{},V(r||!e||!e.__esModule?q(n,"default",{value:e,enumerable:!0}):n,e)),ce=e=>V(q({},"__esModule",{value:!0}),e);var je={};ie(je,{apply:()=>ue,inject:()=>de});module.exports=ce(je);var p=le(require("react"),1),t=require("react/jsx-runtime"),H="memory-eternal",U="/memory-eternal/api",de=["settingsScope","slots","locale","connection","remote"],pe={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\u5173\u8054\u8282\u70B9 \xB7 \u53F3\u952E\u6253\u5F00\u5361\u7247 \xB7 \u6EDA\u8F6E\u7F29\u653E \xB7 \u6309\u4F4F\u5DE6\u952E\u62D6\u52A8\u753B\u5E03",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"},me={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 focuses neighbors \xB7 right-click opens card \xB7 scroll to zoom \xB7 hold left to pan",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"},ge=["all","project","knowledge","content","prompt","business","tool","mistake"],L={project:"#3b82f6",knowledge:"#22c55e",content:"#f43f5e",prompt:"#06b6d4",business:"#eab308",tool:"#f97316",mistake:"#a855f7",other:"#64748b"},_={project:"kindProject",knowledge:"kindKnowledge",content:"kindContent",prompt:"kindPrompt",business:"kindBusiness",tool:"kindTool",mistake:"kindMistake",other:"kindKnowledge"},Y=`
6
+ var it=Object.create;var je=Object.defineProperty;var st=Object.getOwnPropertyDescriptor;var lt=Object.getOwnPropertyNames;var ct=Object.getPrototypeOf,dt=Object.prototype.hasOwnProperty;var pt=(n,l)=>{for(var m in l)je(n,m,{get:l[m],enumerable:!0})},et=(n,l,m,h)=>{if(l&&typeof l=="object"||typeof l=="function")for(let i of lt(l))!dt.call(n,i)&&i!==m&&je(n,i,{get:()=>l[i],enumerable:!(h=st(l,i))||h.enumerable});return n};var mt=(n,l,m)=>(m=n!=null?it(ct(n)):{},et(l||!n||!n.__esModule?je(m,"default",{value:n,enumerable:!0}):m,n)),ut=n=>et(je({},"__esModule",{value:!0}),n);var Et={};pt(Et,{apply:()=>xt,inject:()=>gt});module.exports=ut(Et);var c=mt(require("react"),1),o=require("react/jsx-runtime"),ke="memory-eternal",ee="/memory-eternal/api",gt=["settingsScope","slots","locale","connection","remote"],ft={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",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",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"},bt=["all","project","knowledge","content","prompt","business","tool","mistake"],Pe={project:"#3B82F6",knowledge:"#10B981",content:"#F59E0B",prompt:"#A855F7",business:"#EC4899",tool:"#06B6D4",mistake:"#EF4444",other:"#6B7280"},Ce={project:"kindProject",knowledge:"kindKnowledge",content:"kindContent",prompt:"kindPrompt",business:"kindBusiness",tool:"kindTool",mistake:"kindMistake",other:"kindKnowledge"},Ne=`
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; }
@@ -13,6 +13,9 @@ var ae=Object.create;var q=Object.defineProperty;var re=Object.getOwnPropertyDes
13
13
  .mc-toolbar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 12px; }
14
14
  .mc-toolbar input[type=text] { flex: 1; min-width: 180px; padding: 7px 10px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-base, #f9fafb); color: inherit; font-size: 13px; }
15
15
  .mc-btn { border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: transparent; color: inherit; border-radius: 8px; padding: 6px 12px; font-size: 12px; cursor: pointer; }
16
+ .mc-btn.me-on { background: var(--dsw-alias-accent, #2563eb) !important; color: #fff !important; border-color: transparent !important; }
17
+ .mc-new { display: inline-block; margin-left: 7px; padding: 1px 7px; border-radius: 999px; font-size: 10.5px; line-height: 16px; font-weight: 600; color: #fff; background: linear-gradient(135deg, #f97316, #ef4444); vertical-align: middle; }
18
+ .mc-hl { background: rgba(245,158,11,0.35); color: inherit; border-radius: 2px; padding: 0 1px; }
16
19
  .mc-btn:hover { opacity: 0.85; }
17
20
  .mc-tabs { display: inline-flex; gap: 4px; padding: 3px; background: var(--dsw-alias-bg-base, #f3f4f6); border-radius: 999px; }
18
21
  .mc-tab { border: 0; background: transparent; color: inherit; border-radius: 999px; padding: 5px 14px; font-size: 12px; cursor: pointer; opacity: 0.7; }
@@ -26,6 +29,15 @@ var ae=Object.create;var q=Object.defineProperty;var re=Object.getOwnPropertyDes
26
29
  .mc-cardrow h4 { margin: 0; font-size: 14px; line-height: 1.35; }
27
30
  .mc-cardrow p { margin: 0; font-size: 12px; opacity: 0.7; line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
28
31
  .mc-cardrow footer { margin-top: auto; display: flex; justify-content: space-between; align-items: center; font-size: 11px; opacity: 0.55; }
32
+ .mc-card-del { border: none; background: transparent; color: inherit; cursor: pointer; font-size: 13px; line-height: 1; padding: 2px 5px; border-radius: 5px; opacity: 0.45; }
33
+ .mc-cardrow:hover .mc-card-del { opacity: 0.9; }
34
+ .mc-card-del:hover { color: #ef4444; background: rgba(239,68,68,0.12); }
35
+ /* \u77E5\u8BC6\u5E93\u6EDA\u52A8\u6761\uFF1A\u6E05\u6670\u53EF\u89C1 */
36
+ .memory-eternal-root * { scrollbar-width: thin; scrollbar-color: var(--dsw-alias-label-secondary, #94a3b8) transparent; }
37
+ .memory-eternal-root ::-webkit-scrollbar { width: 10px; height: 10px; }
38
+ .memory-eternal-root ::-webkit-scrollbar-thumb { background: var(--dsw-alias-label-secondary, #94a3b8); border-radius: 6px; }
39
+ .memory-eternal-root ::-webkit-scrollbar-thumb:hover { background: var(--dsw-alias-label-primary, #64748b); }
40
+ .memory-eternal-root ::-webkit-scrollbar-track { background: rgba(127,127,127,0.08); }
29
41
  .mc-tags { display: flex; gap: 4px; flex-wrap: wrap; }
30
42
  .mc-tag { font-size: 10px; padding: 1px 7px; border-radius: 999px; background: var(--dsw-alias-border-l1, #e5e7eb); opacity: 0.85; }
31
43
  .mc-kind { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 6px; }
@@ -45,12 +57,14 @@ var ae=Object.create;var q=Object.defineProperty;var re=Object.getOwnPropertyDes
45
57
 
46
58
  /* ---- full library modal ---- */
47
59
  .me-overlay-top { position: fixed; inset: 0; z-index: 1001; background: rgba(0,0,0,0.55); backdrop-filter: blur(6px); display: flex; align-items: center; justify-content: center; padding: 28px; }
48
- .me-modal { width: min(1120px, 96vw); height: min(88vh, 940px); display: flex; flex-direction: column; background: var(--dsw-alias-bg-overlay, #fff); color: var(--dsw-alias-label-primary, #111); border: 1px solid var(--dsw-alias-border-l1, #e5e7eb); border-radius: 18px; box-shadow: 0 34px 90px rgba(0,0,0,0.5); overflow: hidden; animation: me-pop 0.22s cubic-bezier(0.2,0.8,0.2,1); }
60
+ .me-modal { width: min(1340px, 97vw); height: min(96vh, 1020px); display: flex; flex-direction: column; background: var(--dsw-alias-bg-overlay, #fff); color: var(--dsw-alias-label-primary, #111); border: 1px solid var(--dsw-alias-border-l1, #e5e7eb); border-radius: 18px; box-shadow: 0 34px 90px rgba(0,0,0,0.5); overflow: hidden; animation: me-pop 0.22s cubic-bezier(0.2,0.8,0.2,1); }
49
61
  @keyframes me-pop { from { opacity: 0; transform: translateY(12px) scale(0.985); } }
50
62
  .me-modal-head { display: flex; align-items: center; gap: 12px; padding: 16px 20px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e5e7eb); }
51
- .me-modal-title { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
52
- .me-modal-title h2 { margin: 0; font-size: 17px; font-weight: 700; }
53
- .me-modal-title span { font-size: 12px; opacity: 0.55; }
63
+ .me-modal-title { display: flex; align-items: center; gap: 10px; min-width: 0; }
64
+ .me-modal-title h2 { margin: 0; font-size: 17px; font-weight: 700; white-space: nowrap; }
65
+ .me-modal-title span { font-size: 12px; opacity: 0.55; white-space: nowrap; }
66
+ .me-modal-title .me-wicon { width: 26px; height: 26px; flex: none; }
67
+ .me-modal-title .me-wicon svg { display: block; width: 100%; height: 100%; }
54
68
  .me-modal-head .spacer { flex: 1; }
55
69
  .me-modal-close { border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: transparent; color: inherit; border-radius: 8px; width: 30px; height: 30px; line-height: 1; font-size: 16px; cursor: pointer; display: flex; align-items: center; justify-content: center; }
56
70
  .me-modal-close:hover { background: var(--dsw-alias-bg-layer-1, #f3f4f6); }
@@ -60,6 +74,15 @@ var ae=Object.create;var q=Object.defineProperty;var re=Object.getOwnPropertyDes
60
74
  .me-dialog-head { display: flex; justify-content: space-between; align-items: center; padding: 12px 18px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e5e7eb); }
61
75
  .me-dialog-head h3 { margin: 0; font-size: 15px; }
62
76
  .me-dialog-body { padding: 14px 18px; overflow: auto; font-size: 13px; line-height: 1.7; white-space: pre-wrap; word-break: break-word; }
77
+ .me-dialog-body h3 { font-size: 15px; margin: 10px 0 6px; color: var(--dsw-alias-label-primary, #111); }
78
+ .me-dialog-body h4 { font-size: 13px; margin: 8px 0 4px; color: var(--dsw-alias-label-primary, #111); }
79
+ .me-dialog-body b, .me-dialog-body strong { font-weight: 700; }
80
+ .me-dialog-body code { background: var(--dsw-alias-bg-layer-1, rgba(127,127,127,0.14)); border-radius: 4px; padding: 1px 5px; font-size: 0.9em; font-family: ui-monospace, SFMono-Regular, monospace; }
81
+ .me-dialog-body pre { background: var(--dsw-alias-bg-layer-1, rgba(127,127,127,0.1)); border-radius: 8px; padding: 10px; overflow: auto; margin: 8px 0; }
82
+ .me-dialog-body pre code { background: none; padding: 0; }
83
+ .me-dialog-body ul, .me-dialog-body ol { margin: 6px 0; padding-left: 20px; }
84
+ .me-dialog-body a { color: var(--dsw-alias-accent, #2563eb); }
85
+ .me-dialog-body blockquote { border-left: 3px solid var(--dsw-alias-border-l2, #d1d5db); margin: 6px 0; padding: 2px 12px; opacity: 0.85; }
63
86
 
64
87
  /* ---- enhanced graph ---- */
65
88
  .me-graph { display: flex; flex-direction: column; gap: 8px; height: 100%; min-height: 320px; }
@@ -69,26 +92,25 @@ var ae=Object.create;var q=Object.defineProperty;var re=Object.getOwnPropertyDes
69
92
  .me-graph-canvas.dragging { cursor: grabbing; }
70
93
  .me-graph-tip { font-size: 11px; opacity: 0.6; }
71
94
  .me-graph-canvas svg { width: 100%; height: 100%; display: block; }
72
- .me-graph-edge { fill: none; stroke: var(--dsw-alias-border-l2, #cbd5e1); stroke-width: 1.3; opacity: 0.5; stroke-dasharray: 4 5; animation: me-dash 1.6s linear infinite; }
73
- @keyframes me-dash { to { stroke-dashoffset: -16; } }
95
+ .me-graph-edge { fill: none; stroke-width: 1; }
74
96
  .me-graph-node { cursor: pointer; }
75
- .me-graph-node circle.inner { transition: r 0.18s; }
76
- .me-graph-node:hover circle.inner { stroke: var(--dsw-alias-brand-primary, #6366f1); stroke-width: 2.5; }
77
- .me-graph-node text { font-size: 11px; fill: var(--dsw-alias-label-primary, #334); pointer-events: none; }
78
- .me-labels text { font-size: 11px; fill: var(--dsw-alias-label-primary, #334); pointer-events: none; transition: opacity .12s; }
79
- .me-labels text.minor { opacity: 0; }
80
- .me-labels.all text { opacity: 0.9; }
81
- .me-labels text.dim { opacity: 0.06; }
82
- .me-graph-node.dim { opacity: 0.16; transition: opacity .18s; }
83
- .me-graph-node.focus circle.inner { stroke: #fff; stroke-width: 3; }
84
- .me-graph-edge.dim { opacity: 0.06; }
85
- .me-graph-edge.focus { opacity: 1; }
86
- .me-graph-node.in { animation: me-node-in 0.5s cubic-bezier(0.2,0.8,0.2,1) backwards; }
87
- @keyframes me-node-in { from { opacity: 0; transform: scale(0.4); } }
97
+ .me-nodelabel { font-size: 11px; fill: var(--dsw-alias-label-primary, #334); pointer-events: none; transition: opacity .12s; }
98
+ .me-nodelabel.core { font-weight: 650; }
88
99
  .me-graph-legend { display: flex; gap: 12px; flex-wrap: wrap; padding: 2px 2px 0; font-size: 11px; opacity: 0.85; }
89
- .me-graph-legend .lg { display: inline-flex; align-items: center; gap: 5px; }
100
+ .me-graph-legend .lg { display: inline-flex; align-items: center; gap: 5px; border: none; background: transparent; color: inherit; font: inherit; font-size: 11px; padding: 3px 6px; border-radius: 7px; cursor: pointer; }
101
+ .me-graph-legend .lg:hover { background: var(--dsw-alias-bg-layer-1, rgba(255,255,255,0.08)); }
102
+ .me-graph-legend .lg.active { background: var(--dsw-alias-bg-layer-1, rgba(255,255,255,0.14)); box-shadow: 0 0 0 1px var(--dsw-alias-border-l1, #d1d5db); }
103
+ .me-graph-legend .lg.lg-clear { opacity: 0.85; }
104
+ .me-graph-ctxmenu { min-width: 156px; padding: 6px; background: rgba(28,28,32,0.95); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); color: #eee; border: 1px solid rgba(255,255,255,0.08); border-radius: 10px; box-shadow: 0 12px 34px rgba(0,0,0,.3); font-size: 12px; }
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
+ .me-graph-ctxmenu button:hover { background: rgba(255,255,255,0.1); }
90
107
  .me-graph-hint { font-size: 11px; opacity: 0.6; margin-left: auto; }
91
- `;function ue(e){e.effect(()=>e.locale.register(H,{zh:pe,en:me}),"memory-eternal: locale");let r=e.locale.bind(H);e.effect(()=>e.slots.inject("settings.section",()=>e.slots.register({name:"settings.section",id:H,order:25,label:()=>r("nav"),locale:H,inject:()=>({})},()=>p.default.createElement(W,{t:r,inModal:!1}))),"memory-eternal: settings section"),e.effect(()=>e.slots.inject("sidebar.footer.action",()=>e.slots.register({name:"sidebar.footer.action",id:`${H}:footer`,order:100,label:()=>r("nav"),locale:H,inject:()=>({})},n=>p.default.createElement(fe,{t:r,wide:!(n&&n.wide===!1)}))),"memory-eternal: sidebar footer action")}function fe({t:e,wide:r}){let[n,o]=(0,p.useState)(!1);return(0,t.jsxs)("div",{className:`me-footer${r?"":" rail"}`,children:[(0,t.jsx)("style",{children:Y}),(0,t.jsxs)("button",{type:"button",className:"me-footer-btn",onClick:()=>o(!0),"aria-label":e("nav"),title:e("nav"),children:[(0,t.jsx)("span",{className:"me-footer-ico","aria-hidden":"true",children:(0,t.jsx)(Z,{})}),(0,t.jsx)("span",{className:"me-footer-label",children:e("nav")})]}),n&&(0,t.jsx)(he,{t:e,onClose:()=>o(!1)})]})}function Z(){return(0,t.jsxs)("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:[(0,t.jsx)("ellipse",{cx:"12",cy:"5.5",rx:"8",ry:"3"}),(0,t.jsx)("path",{d:"M4 5.5v6c0 1.66 3.58 3 8 3s8-1.34 8-3v-6"}),(0,t.jsx)("path",{d:"M4 11.5v6c0 1.66 3.58 3 8 3s8-1.34 8-3v-6"})]})}function he({t:e,onClose:r}){return(0,p.useEffect)(()=>{let n=o=>{o.key==="Escape"&&r()};return window.addEventListener("keydown",n),()=>window.removeEventListener("keydown",n)},[r]),(0,t.jsxs)("div",{className:"me-overlay-top",onClick:r,children:[(0,t.jsx)("style",{children:Y}),(0,t.jsx)("div",{className:"me-modal",onClick:n=>n.stopPropagation(),children:(0,t.jsx)(W,{t:e,inModal:!0,onClose:r})})]})}function W({t:e,inModal:r,onClose:n}){let[o,g]=(0,p.useState)(null),[z,C]=(0,p.useState)([]),[d,u]=(0,p.useState)(!0),[j,T]=(0,p.useState)(""),[N,E]=(0,p.useState)("all"),[S,h]=(0,p.useState)(""),[x,m]=(0,p.useState)("cards"),[k,O]=(0,p.useState)(null),v=(0,p.useRef)(null),P=(0,p.useCallback)(async(c=N,w=S)=>{try{u(!0),T("");let y=new URLSearchParams;c&&c!=="all"&&y.set("kind",c),w.trim()&&y.set("q",w.trim());let a=await(await fetch(`${U}/cards?${y.toString()}`)).json();a.ok&&C(a.cards||[])}catch(y){T(String(y&&y.message?y.message:y))}finally{u(!1)}},[N,S]),D=(0,p.useCallback)(async()=>{try{let[c,w]=await Promise.all([fetch(`${U}/overview`).then(y=>y.json()),fetch(`${U}/cards`).then(y=>y.json())]);c.ok&&g(c),w.ok&&C(w.cards||[])}catch(c){T(String(c&&c.message?c.message:c))}finally{u(!1)}},[]);(0,p.useEffect)(()=>(D(),()=>{v.current&&clearTimeout(v.current)}),[D]);let f=c=>{h(c),v.current&&clearTimeout(v.current),v.current=setTimeout(()=>P(N,c),280)},i=c=>{E(c),P(c,S)},b=async c=>{try{let y=await(await fetch(`${U}/card?path=${encodeURIComponent(c.path||c.id)}`)).json();y.ok&&O({path:y.path,title:c.title,text:y.text})}catch(w){T(String(w&&w.message?w.message:w))}};return(0,t.jsxs)("div",{className:"memory-eternal-root",style:{height:"100%",display:"flex",flexDirection:"column"},children:[(0,t.jsx)("style",{children:Y}),r&&(0,t.jsxs)("div",{className:"me-modal-head",children:[(0,t.jsxs)("div",{className:"me-modal-title",children:[(0,t.jsx)(Z,{}),(0,t.jsx)("h2",{children:e("memoryTitle")}),(0,t.jsx)("span",{children:e("memoryHint")})]}),(0,t.jsx)("div",{className:"spacer"}),(0,t.jsx)("button",{type:"button",className:"me-modal-close",onClick:n,"aria-label":e("close"),children:"\u2715"})]}),(0,t.jsxs)("div",{className:"me-modal-body",children:[(0,t.jsxs)("div",{className:"mc-stats",children:[(0,t.jsx)(X,{label:e("total"),value:o?o.total:"\u2014"}),(0,t.jsx)(X,{label:e("recent"),value:o?o.recent:"\u2014"}),(0,t.jsx)(X,{label:e("tags"),value:o?o.tags:"\u2014"}),(0,t.jsx)(X,{label:e("cardCount"),value:o?o.byKind?.knowledge??0:"\u2014"})]}),(0,t.jsxs)("div",{className:"mc-toolbar",children:[(0,t.jsx)("input",{type:"text",placeholder:e("searchPlaceholder"),value:S,onChange:c=>f(c.target.value)}),(0,t.jsxs)("div",{className:"mc-tabs",children:[(0,t.jsx)("button",{type:"button",className:`mc-tab${x==="cards"?" active":""}`,onClick:()=>m("cards"),children:e("cardsTab")}),(0,t.jsx)("button",{type:"button",className:`mc-tab${x==="graph"?" active":""}`,onClick:()=>m("graph"),children:e("graphTab")})]}),(0,t.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>D(),children:e("refresh")})]}),x==="cards"&&(0,t.jsx)("div",{className:"mc-chips",children:ge.map(c=>(0,t.jsx)("button",{type:"button",className:`mc-chip${N===c?" active":""}`,onClick:()=>i(c),children:e(c==="all"?"all":_[c])},c))}),j&&(0,t.jsxs)("div",{className:"mc-empty",children:[e("error"),"\uFF1A",j]}),x==="cards"?d&&!z.length?(0,t.jsx)("div",{className:"mc-empty",children:e("loading")}):z.length===0?(0,t.jsx)("div",{className:"mc-empty",children:e("empty")}):(0,t.jsx)("div",{className:"mc-grid",children:z.map(c=>(0,t.jsx)(be,{card:c,t:e,onOpen:b},c.path))}):(0,t.jsx)(xe,{t:e,onOpen:b}),k&&(0,t.jsx)(ye,{t:e,card:k,onClose:()=>O(null)})]})]})}var X=({label:e,value:r})=>(0,t.jsxs)("div",{className:"mc-stat mc-card",children:[(0,t.jsx)("b",{children:r}),(0,t.jsx)("span",{children:e})]}),be=({card:e,t:r,onOpen:n})=>(0,t.jsxs)("article",{className:"mc-cardrow mc-card",onClick:()=>n(e),children:[(0,t.jsxs)("h4",{children:[(0,t.jsx)("span",{className:"mc-kind",style:{background:L[e.kind]||L.other}}),e.title]}),(0,t.jsx)("p",{children:e.summary||""}),e.tags.length>0&&(0,t.jsx)("div",{className:"mc-tags",children:e.tags.slice(0,4).map(o=>(0,t.jsx)("span",{className:"mc-tag",children:o},o))}),(0,t.jsxs)("footer",{children:[(0,t.jsx)("span",{children:r(_[e.kind])}),(0,t.jsx)("span",{children:ze(e.updated)})]})]});function ye({t:e,card:r,onClose:n}){return(0,p.useEffect)(()=>{let o=g=>{g.key==="Escape"&&n()};return window.addEventListener("keydown",o),()=>window.removeEventListener("keydown",o)},[n]),(0,t.jsxs)("div",{className:"me-overlay",onClick:n,children:[(0,t.jsx)("style",{children:Y}),(0,t.jsxs)("div",{className:"me-dialog",onClick:o=>o.stopPropagation(),children:[(0,t.jsxs)("div",{className:"me-dialog-head",children:[(0,t.jsx)("h3",{children:r.title}),(0,t.jsx)("button",{type:"button",className:"mc-btn",onClick:n,children:e("close")})]}),(0,t.jsx)("div",{className:"me-dialog-body",children:r.text})]})]})}function xe({t:e,onOpen:r}){let[n,o]=(0,p.useState)(null),[g,z]=(0,p.useState)("");return(0,p.useEffect)(()=>{let C=!0;return fetch(`${U}/graph`).then(d=>d.json()).then(d=>{C&&o(d.ok?d:null)}).catch(d=>{C&&z(String(d&&d.message?d.message:d))}),()=>{C=!1}},[]),(0,t.jsxs)("div",{className:"me-graph",children:[(0,t.jsx)("style",{children:Y}),g&&(0,t.jsxs)("div",{className:"mc-empty",children:[e("error"),"\uFF1A",g]}),!n&&!g&&(0,t.jsx)("div",{className:"mc-empty",children:e("loading")}),n&&n.nodes.length===0&&(0,t.jsx)("div",{className:"mc-empty",children:e("emptyGraph")}),n&&n.nodes.length>0&&(0,t.jsx)(we,{nodes:n.nodes,edges:n.edges,onOpen:r,t:e,countLabel:`${n.nodes.length} ${e("nodes")} \xB7 ${ve(n.edges)} ${e("edges")}`})]})}function ve(e){let r=new Set;for(let n of e||[])r.add([n.source,n.target].sort().join("|"));return r.size}function we({nodes:e,edges:r,onOpen:n,t:o,countLabel:g}){let d=(0,p.useRef)(null),u=(0,p.useRef)(null),j=(0,p.useRef)(null),T=(0,p.useRef)(null),N=(0,p.useRef)({x:0,y:0,k:1}),E=(0,p.useRef)(null),S=(0,p.useRef)(!1),[h,x]=(0,p.useState)(null),m=(0,p.useMemo)(()=>{let a={};r.forEach(M=>{a[M.source]=(a[M.source]||0)+1,a[M.target]=(a[M.target]||0)+1});let l=420,s=e,$=!1;e.length>l&&(s=[...e].sort((M,G)=>(a[G.id]||0)-(a[M.id]||0)).slice(0,l),$=!0);let I=new Set(s.map(M=>M.id)),R=new Set,K=[];r.forEach(M=>{if(!I.has(M.source)||!I.has(M.target))return;let G=[M.source,M.target].sort().join("|");R.has(G)||(R.add(G),K.push(M))}),K.sort((M,G)=>{let te=(a[M.source]||0)+(a[M.target]||0);return(a[G.source]||0)+(a[G.target]||0)-te});let A=Math.min(K.length,s.length<=40?Math.round(s.length*3):Math.max(80,Math.round(s.length*2.2))),F=s.length<=16?s.length:Math.max(8,Math.min(16,Math.round(s.length*.22))),J=s.map(M=>a[M.id]||0).sort((M,G)=>G-M),ee=F>0?J[F-1]:1/0;return{list:s,es:K.slice(0,A),capped:$,maxDeg:Math.max(1,...Object.values(a)),deg:a,hubMin:ee}},[e,r]),k=(0,p.useMemo)(()=>ke(m.list,m.es,980,620),[m.list,m.es,980,620]),O=(0,p.useMemo)(()=>m.list.reduce((a,l,s)=>(a[l.id]=s,a),{}),[m.list]),v=(0,p.useMemo)(()=>{let a={};return m.list.forEach(l=>{a[l.id]=new Set}),m.es.forEach(l=>{a[l.source]&&a[l.source].add(l.target),a[l.target]&&a[l.target].add(l.source)}),a},[m.list,m.es]),P=(0,p.useCallback)(()=>{let a=j.current;if(!a)return;let{x:l,y:s,k:$}=N.current;a.setAttribute("transform",`translate(${l} ${s}) scale(${$})`),T.current&&T.current.classList.toggle("all",$>=1.4)},[]);(0,p.useEffect)(()=>{P()},[P,k]);let D=(0,p.useCallback)((a,l)=>{let s=u.current;if(!s||typeof s.getScreenCTM!="function")return{x:a,y:l};let $=s.getScreenCTM();if(!$)return{x:a,y:l};let I=new DOMPoint(a,l).matrixTransform($.inverse());return{x:I.x,y:I.y}},[]),f=(0,p.useCallback)((a,l)=>{let s=N.current,$=Math.min(5,Math.max(.25,s.k*l));N.current={k:$,x:a.x-$/s.k*(a.x-s.x),y:a.y-$/s.k*(a.y-s.y)},P()},[P]);(0,p.useEffect)(()=>{let a=d.current;if(!a)return;let l=s=>{s.preventDefault(),f(D(s.clientX,s.clientY),s.deltaY<0?1.12:1/1.12)};return a.addEventListener("wheel",l,{passive:!1}),()=>a.removeEventListener("wheel",l)},[D,f]);let i=a=>{if(a.button!==0)return;let l=D(a.clientX,a.clientY);E.current={px:l.x,py:l.y,tx:N.current.x,ty:N.current.y,started:!1,id:a.pointerId},S.current=!1},b=a=>{let l=E.current;if(!l)return;let s=D(a.clientX,a.clientY);Math.abs(s.x-l.px)+Math.abs(s.y-l.py)>1.5&&(S.current=!0,!l.started&&d.current&&d.current.setPointerCapture&&(d.current.setPointerCapture(a.pointerId),l.started=!0),d.current&&d.current.classList.add("dragging"),N.current={...N.current,x:l.tx+(s.x-l.px),y:l.ty+(s.y-l.py)},P())},c=()=>{E.current=null,d.current&&d.current.classList.remove("dragging")},w=a=>{n({path:a.id,title:a.title})},y=a=>{S.current||x(a.id)},B=a=>7+Math.min(9,(m.deg[a.id]||0)/m.maxDeg*9);return(0,t.jsxs)("div",{className:"me-graph",children:[(0,t.jsxs)("div",{className:"me-graph-toolbar",children:[(0,t.jsx)("span",{className:"me-graph-count",children:g}),h&&(0,t.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>x(null),children:o("clearFocus")}),(0,t.jsx)("span",{className:"spacer",style:{flex:1}}),(0,t.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>f({x:980/2,y:620/2},1/1.25),"aria-label":o("zoomOut"),children:"\u2212"}),(0,t.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{N.current={x:0,y:0,k:1},P()},"aria-label":o("fit"),children:o("fit")}),(0,t.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>f({x:980/2,y:620/2},1.25),"aria-label":o("zoomIn"),children:"+"})]}),(0,t.jsxs)("div",{className:"me-graph-canvas",ref:d,onPointerDown:i,onPointerMove:b,onPointerUp:c,onPointerLeave:c,onContextMenu:a=>{a.preventDefault(),x(null)},onClick:a=>{S.current||x(null)},children:[(0,t.jsxs)("svg",{ref:u,viewBox:"0 0 980 620",role:"img",children:[(0,t.jsxs)("defs",{children:[Object.keys(L).map(a=>(0,t.jsxs)("radialGradient",{id:`me-grad-${a}`,cx:"35%",cy:"30%",r:"75%",children:[(0,t.jsx)("stop",{offset:"0%",stopColor:$e(L[a],.55)}),(0,t.jsx)("stop",{offset:"60%",stopColor:L[a]}),(0,t.jsx)("stop",{offset:"100%",stopColor:Ce(L[a],.25)})]},a)),Object.keys(L).map(a=>(0,t.jsxs)("linearGradient",{id:`me-edge-${a}`,gradientUnits:"userSpaceOnUse",children:[(0,t.jsx)("stop",{offset:"0%",stopColor:L[a],stopOpacity:"0.4"}),(0,t.jsx)("stop",{offset:"100%",stopColor:L[a],stopOpacity:"0.85"})]},`e-${a}`)),(0,t.jsxs)("filter",{id:"me-glow",x:"-60%",y:"-60%",width:"220%",height:"220%",children:[(0,t.jsx)("feGaussianBlur",{stdDeviation:"3.2",result:"b"}),(0,t.jsxs)("feMerge",{children:[(0,t.jsx)("feMergeNode",{in:"b"}),(0,t.jsx)("feMergeNode",{in:"SourceGraphic"})]})]})]}),(0,t.jsxs)("g",{ref:j,transform:"translate(0 0) scale(1)",children:[m.es.map((a,l)=>{let s=O[a.source],$=O[a.target];if(s===void 0||$===void 0)return null;let I=k[s],R=k[$];if(!I||!R)return null;let K=m.list[s].kind||"other",A=h&&a.source!==h&&a.target!==h,F=h&&(a.source===h||a.target===h);return(0,t.jsx)("path",{d:Ne(I,R),className:`me-graph-edge${A?" dim":""}${F?" focus":""}`,style:{stroke:`url(#me-edge-${K})`,animationDelay:`${l%8*.12}s`}},`e${l}`)}),m.list.map((a,l)=>{let s=k[l];if(!s)return null;let $=a.kind||"other",I=B(a),R=h&&a.id!==h&&!v[a.id].has(h),K=h&&(a.id===h||v[a.id].has(h));return(0,t.jsxs)("g",{className:`me-graph-node in${K?" focus":""}${R?" dim":""}`,style:{animationDelay:`${l*.02}s`},transform:`translate(${s.x} ${s.y})`,onClick:A=>{A.stopPropagation(),y(a)},onContextMenu:A=>{A.preventDefault(),A.stopPropagation(),w(a)},children:[(0,t.jsx)("circle",{className:"inner",r:I,fill:`url(#me-grad-${$})`,stroke:L[$],strokeOpacity:"0.9",filter:m.list.length<=120?"url(#me-glow)":void 0}),(0,t.jsx)("circle",{r:Math.max(2.4,I*.2),fill:"rgba(255,255,255,0.85)"})]},a.id)}),(0,t.jsx)("g",{ref:T,className:"me-labels",children:m.list.map((a,l)=>{let s=k[l];if(!s)return null;let $=B(a),I=(m.deg[a.id]||0)>=m.hubMin,R=h&&a.id!==h&&!v[a.id].has(h);return(0,t.jsx)("text",{className:`${I?"me-nodelabel":"me-nodelabel minor"}${R?" dim":""}`,x:s.x+$+7,y:s.y+4,children:String(a.title||"").slice(0,16)},`l${a.id}`)})})]})]}),(0,t.jsxs)("div",{className:"me-graph-legend",children:[Object.keys(L).map(a=>(0,t.jsxs)("span",{className:"lg",children:[(0,t.jsx)("span",{className:"mc-kind",style:{background:L[a]}}),o(_[a])]},a)),(0,t.jsx)("span",{className:"me-graph-hint",children:o("graphHint")})]})]}),(0,t.jsxs)("div",{className:"me-graph-tip",children:[m.capped?`${o("capped")} ${m.list.length} / ${e.length} ${o("nodes")} \xB7 `:"",o("graphTip")]})]})}function ke(e,r,n,o){let g=e.length;if(g===0)return[];if(g>24)return Me(e,r,n,o);let z=320,C=Math.max(3,Math.sqrt(g)*2.4),d=e.map((f,i)=>{let b=i/g*Math.PI*2-Math.PI/2;return{x:C*Math.cos(b),y:C*Math.sin(b)}}),u=d.map(()=>({x:0,y:0})),j=e.map(()=>[]);r.forEach(f=>{let i=e.findIndex(c=>c.id===f.source),b=e.findIndex(c=>c.id===f.target);i>=0&&b>=0&&i!==b&&(j[i].push(b),j[b].push(i))});let T=2.8,N=.02,E=.006,S=.85;for(let f=0;f<z;f++){for(let i=0;i<g;i++)for(let b=i+1;b<g;b++){let c=d[i].x-d[b].x,w=d[i].y-d[b].y,y=c*c+w*w||.01,B=Math.sqrt(y),a=T/y,l=c/B*a,s=w/B*a;u[i].x+=l,u[i].y+=s,u[b].x-=l,u[b].y-=s}for(let i=0;i<g;i++)for(let b of j[i])u[i].x+=(d[b].x-d[i].x)*N,u[i].y+=(d[b].y-d[i].y)*N;for(let i=0;i<g;i++)u[i].x-=d[i].x*E,u[i].y-=d[i].y*E;for(let i=0;i<g;i++)u[i].x=Math.max(-.35,Math.min(.35,u[i].x*S)),u[i].y=Math.max(-.35,Math.min(.35,u[i].y*S)),d[i].x+=u[i].x,d[i].y+=u[i].y}let h=1/0,x=-1/0,m=1/0,k=-1/0;d.forEach(f=>{h=Math.min(h,f.x),x=Math.max(x,f.x),m=Math.min(m,f.y),k=Math.max(k,f.y)});let O=60,v=Math.min((n-2*O)/(x-h||1),(o-2*O)/(k-m||1)),P=(h+x)/2,D=(m+k)/2;return d.map(f=>({x:n/2+(f.x-P)*v,y:o/2+(f.y-D)*v}))}function Me(e,r,n,o){let g=e.length,z=n/2,C=o/2,d=Math.min(n,o)/2-56,u={};r.forEach(x=>{u[x.source]=(u[x.source]||0)+1,u[x.target]=(u[x.target]||0)+1});let j={};e.forEach((x,m)=>{let k=x.kind||"other";(j[k]=j[k]||[]).push(m)});let T=Object.keys(j),N=new Array(g),E=-Math.PI/2,S=46,h=56;for(let x of T){let m=j[x].sort((f,i)=>(u[e[i].id]||0)-(u[e[f].id]||0)),k=m.length/g*Math.PI*2,O=E,v=0,P=0;for(;v<m.length&&P<60;){let f=Math.min(64+P*h,d),i=f*k,b=Math.max(1,Math.floor(i/S)),c=Math.min(b,m.length-v);for(let w=0;w<c;w++){let y=O+k*(v+w+.5)/m.length;N[m[v+w]]={x:z+f*Math.cos(y),y:C+f*Math.sin(y)}}v+=c,P++}let D=0;for(;v<m.length&&D<m.length;){let f=O+k*(v+.5)/m.length;N[m[v]]={x:z+d*Math.cos(f),y:C+d*Math.sin(f)},v++,D++}E+=k}return N}function Ne(e,r){let n=(e.x+r.x)/2,o=(e.y+r.y)/2,g=r.x-e.x,z=r.y-e.y,C=Math.sqrt(g*g+z*z)||1,d=Math.min(38,C*.18),u=n-z/C*d,j=o+g/C*d;return`M ${e.x} ${e.y} Q ${u} ${j} ${r.x} ${r.y}`}function Q(e){let r=e.replace("#",""),n=r.length===3?r.split("").map(o=>o+o).join(""):r;return[parseInt(n.slice(0,2),16),parseInt(n.slice(2,4),16),parseInt(n.slice(4,6),16)]}function $e(e,r){let[n,o,g]=Q(e);return`rgb(${Math.round(n+(255-n)*r)}, ${Math.round(o+(255-o)*r)}, ${Math.round(g+(255-g)*r)})`}function Ce(e,r){let[n,o,g]=Q(e);return`rgb(${Math.round(n*(1-r))}, ${Math.round(o*(1-r))}, ${Math.round(g*(1-r))})`}function ze(e){if(!e)return"";let r=new Date(e);if(Number.isNaN(r.getTime()))return"";let n=new Date,o=r.toDateString()===n.toDateString(),g=C=>String(C).padStart(2,"0"),z=`${g(r.getHours())}:${g(r.getMinutes())}`;return o?z:`${r.getMonth()+1}-${r.getDate()} ${z}`}
108
+ `;function xt(n){n.effect(()=>n.locale.register(ke,{zh:ft,en:ht}),"memory-eternal: locale");let l=n.locale.bind(ke);n.effect(()=>n.slots.inject("settings.section",()=>n.slots.register({name:"settings.section",id:ke,order:25,label:()=>l("nav"),locale:ke,inject:()=>({})},()=>c.default.createElement(nt,{t:l,inModal:!1}))),"memory-eternal: settings section"),n.effect(()=>n.slots.inject("sidebar.footer.action",()=>n.slots.register({name:"sidebar.footer.action",id:`${ke}:footer`,order:100,label:()=>l("nav"),locale:ke,inject:()=>({})},m=>c.default.createElement(yt,{t:l,wide:!(m&&m.wide===!1)}))),"memory-eternal: sidebar footer action")}function yt({t:n,wide:l}){let[m,h]=(0,c.useState)(!1);return(0,o.jsxs)("div",{className:`me-footer${l?"":" rail"}`,children:[(0,o.jsx)("style",{children:Ne}),(0,o.jsxs)("button",{type:"button",className:"me-footer-btn",onClick:()=>h(!0),"aria-label":n("nav"),title:n("nav"),children:[(0,o.jsx)("span",{className:"me-footer-ico","aria-hidden":"true",children:(0,o.jsx)(ot,{})}),(0,o.jsx)("span",{className:"me-footer-label",children:n("nav")})]}),m&&(0,o.jsx)(wt,{t:n,onClose:()=>h(!1)})]})}function ot(){return(0,o.jsxs)("svg",{viewBox:"0 0 24 24","aria-hidden":"true",children:[(0,o.jsx)("defs",{children:(0,o.jsxs)("linearGradient",{id:"mg-brain",x1:"0",y1:"0",x2:"1",y2:"1",children:[(0,o.jsx)("stop",{offset:"0%",stopColor:"#FFC46B"}),(0,o.jsx)("stop",{offset:"50%",stopColor:"#F97316"}),(0,o.jsx)("stop",{offset:"100%",stopColor:"#EA580C"})]})}),(0,o.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,o.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,o.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,o.jsx)("circle",{cx:"12",cy:"8.6",r:"1.1",fill:"rgba(255,255,255,0.85)"})]})}function wt({t:n,onClose:l}){return(0,c.useEffect)(()=>{let m=h=>{h.key==="Escape"&&l()};return window.addEventListener("keydown",m),()=>window.removeEventListener("keydown",m)},[l]),(0,o.jsxs)("div",{className:"me-overlay-top",onClick:l,children:[(0,o.jsx)("style",{children:Ne}),(0,o.jsx)("div",{className:"me-modal",onClick:m=>m.stopPropagation(),children:(0,o.jsx)(nt,{t:n,inModal:!0,onClose:l})})]})}function nt({t:n,inModal:l,onClose:m}){let[h,i]=(0,c.useState)(null),[b,T]=(0,c.useState)([]),[W,C]=(0,c.useState)(!0),[E,G]=(0,c.useState)(""),[ie,pe]=(0,c.useState)("all"),[q,Oe]=(0,c.useState)(""),[B,se]=(0,c.useState)("cards"),[me,ue]=(0,c.useState)("recent"),[oe,z]=(0,c.useState)(null),j=(0,c.useRef)(null),Me=(0,c.useRef)(null),[ge,O]=(0,c.useState)(!1),[V,Ye]=(0,c.useState)(!1),[_,J]=(0,c.useState)(null),X=(0,c.useRef)(null),[A,fe]=(0,c.useState)(24),U=(0,c.useRef)(null);(0,c.useEffect)(()=>{if(B!=="cards")return;let d=U.current;if(!d)return;let a=new IntersectionObserver(t=>{t[0]&&t[0].isIntersecting&&fe(y=>y+24)},{rootMargin:"400px"});return a.observe(d),()=>a.disconnect()},[B,b,ie,q]);let he=(0,c.useCallback)(async(d=ie,a=q)=>{try{C(!0),G("");let t=new URLSearchParams;d&&d!=="all"&&t.set("kind",d),a.trim()&&t.set("q",a.trim());let N=await(await fetch(`${ee}/cards?${t.toString()}`)).json();N.ok&&(T(N.cards||[]),fe(24))}catch(t){G(String(t&&t.message?t.message:t))}finally{C(!1)}},[ie,q]),Z=(0,c.useCallback)(async()=>{try{let[d,a]=await Promise.all([fetch(`${ee}/overview`).then(t=>t.json()),fetch(`${ee}/cards`).then(t=>t.json())]);d.ok&&i(d),a.ok&&(T(a.cards||[]),fe(24))}catch(d){G(String(d&&d.message?d.message:d))}finally{C(!1)}},[]),Ie=async d=>{try{let t=await(await fetch(`${ee}/delete?path=${encodeURIComponent(d)}`)).json();t.ok?(z({ok:!0,msg:n("deleted")}),await Z()):z({ok:!1,msg:t.error||n("deleteFail")})}catch{z({ok:!1,msg:n("deleteFail")})}j.current&&clearTimeout(j.current),j.current=setTimeout(()=>z(null),2e3)};(0,c.useEffect)(()=>(Z(),()=>{X.current&&clearTimeout(X.current)}),[Z]);let xe=d=>{Oe(d),X.current&&clearTimeout(X.current),X.current=setTimeout(()=>he(ie,d),280)},Ee=d=>{pe(d),he(d,q)},le=async d=>{try{let t=await(await fetch(`${ee}/card?path=${encodeURIComponent(d.path||d.id)}`)).json();t.ok&&J({path:t.path,title:d.title,text:t.text})}catch(a){G(String(a&&a.message?a.message:a))}},ce=async(d,a)=>{O(!0);try{let t=await fetch(`${ee}/export`);if(!t.ok)throw new Error("HTTP "+t.status);let y=await t.json();if(!y.ok)throw new Error(y.error||"export failed");let N,R,D;d==="json"?(R="memory-vault.json",D="application/json",N=JSON.stringify(y.cards.map(ae=>({path:ae.path,title:ae.title,kind:ae.kind,text:ae.text})),null,2)):(R="memory-vault.md",D="text/markdown;charset=utf-8",N=y.cards.map(ae=>ae.text.trim()).filter(Boolean).join(`
109
+
110
+ ---
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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");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
+ `),y=new Blob([t],{type:"text/plain;charset=utf-8"}),N=URL.createObjectURL(y),R=document.createElement("a");R.href=N,R.download="memory-selected.txt",document.body.appendChild(R),R.click(),document.body.removeChild(R),setTimeout(()=>URL.revokeObjectURL(N),1e3),F(J.length+i("exportedSel"))},children:i("exportSel")}),(0,o.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{if(J.length<2){F(i("mergeNeed"),!1);return}let a=J.map(y=>d[y]?d[y].id:y);if(!window.confirm(i("mergeConfirm")))return;X([]);let t=C.current;t&&(t.multi=[]),fetch(`${ee}/merge?paths=${encodeURIComponent(a.join(","))}`).then(y=>y.json()).then(y=>{y&&y.ok?F(i("merged")):F(i("mergeFail"),!1)}).catch(()=>F(i("mergeFail"),!1))},children:i("merge")}),(0,o.jsx)("button",{type:"button",className:"mc-btn",style:{color:"#f87171"},onClick:()=>{if(!window.confirm(i("deleteConfirm")))return;let a=J.map(y=>d[y]?d[y].id:y);X([]);let t=C.current;t&&(t.multi=[]),(async()=>{let y=0;for(let N of a)try{(pe.current?await pe.current(N):!1)&&y++}catch{}F(y+i("deletedSelected"),y>0)})()},children:i("deleteSelected")}),(0,o.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{let a=C.current;a&&(a.multi=[]),X([]);let t=C.current;t&&t.render&&t.render()},children:i("clearSelection")})]}),B&&(0,o.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,o.jsx)("div",{style:{fontWeight:700,fontSize:13,marginBottom:4},children:d[B]?d[B].title||d[B].name:B}),(0,o.jsx)("div",{style:{opacity:.8,marginBottom:8},children:d[B]?i(Ce[d[B].kind]||"kindKnowledge"):"\u2014"}),(0,o.jsx)("div",{style:{fontSize:11,opacity:.65,letterSpacing:".04em",marginBottom:6},children:i("graphHint")}),(l.filter(a=>a.source===B||a.target===B)||[]).slice(0,8).map((a,t)=>{let y=a.source===B?a.target:a.source,N=d[y];return(0,o.jsx)("div",{style:{padding:"4px 6px",cursor:"pointer",borderRadius:6},onMouseDown:R=>{R.preventDefault();let D=C.current;D&&(D.selectedId=y,D.hoverId=y,D.wake&&D.wake()),se(y)},children:N?(N.title||N.name).slice(0,28):y},t)})]})]}),(0,o.jsx)("div",{className:"me-graph-tip",children:i("graphTip")}),Z&&(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 "+(Z.ok?"#22c55e":"#ef4444"),maxWidth:"90vw"},children:[(0,o.jsx)("span",{style:{color:Z.ok?"#34d399":"#f87171",fontWeight:800,fontSize:15},children:Z.ok?"\u2713":"\u2715"}),(0,o.jsx)("span",{style:{wordBreak:"break-all"},children:Z.msg})]}),A&&(0,o.jsxs)("div",{className:"me-overlay",onClick:()=>{A.url&&URL.revokeObjectURL(A.url),fe(null),he(!1)},children:[(0,o.jsx)("style",{children:Ne}),(0,o.jsxs)("div",{className:"me-dialog",style:{maxWidth:U?"100vw":900,width:U?"100vw":"92vw",maxHeight:U?"100vh":"90vh",height:U?"100vh":void 0,borderRadius:U?0:14},onClick:a=>a.stopPropagation(),children:[(0,o.jsxs)("div",{className:"me-dialog-head",children:[(0,o.jsx)("h3",{children:i("exportGraph")}),(0,o.jsxs)("div",{style:{display:"flex",gap:8},children:[(0,o.jsx)("a",{className:"mc-btn",href:A.url,download:"memory-graph.png",onClick:()=>{be("download"),F(i("downloaded")+" \xB7 "+i("defaultDownloads"))},style:{textDecoration:"none",display:"inline-flex",alignItems:"center"},children:i(xe==="download"?"done":"download")}),(0,o.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{be("save");let a=A.blob;a&&typeof window.showSaveFilePicker=="function"?at(a,"memory-graph.png",!0).then(t=>{t.ok?F(i("exportedTo")+"\uFF1A"+t.name):t.aborted?F(i("exportCancel")):F(i("exportFail"),!1)}):F(i("saveAsUnsupported"),!1)},children:i(xe==="save"?"done":"saveAs")}),(0,o.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{be("full"),he(a=>!a),F(i("fullscreen"))},children:i(xe==="full"?"done":U?"exitFull":"fullscreen")}),(0,o.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{let a=A.blob;a&&window.ClipboardItem&&navigator.clipboard?navigator.clipboard.write([new window.ClipboardItem({"image/png":a})]).then(()=>{be("copy"),F(i("copied"))}).catch(()=>{F(i("copyFail"),!1)}):F(i("copyFail"),!1)},children:i(xe==="copy"?"done":"copyImage")}),(0,o.jsx)("button",{type:"button",className:"mc-btn",onClick:()=>{A.url&&URL.revokeObjectURL(A.url),fe(null),he(!1)},children:i("close")})]})]}),(0,o.jsx)("div",{className:"me-dialog-body",style:{display:"flex",justifyContent:"center",background:U?"var(--dsw-alias-bg-base, #0b0d10)":"var(--dsw-alias-bg-base, #f3f4f6)"},children:(0,o.jsx)("img",{src:A.url,alt:i("exportGraph"),style:{maxWidth:"100%",maxHeight:U?"calc(100vh - 64px)":"70vh",borderRadius:U?0:8,cursor:"zoom-in"},onClick:()=>he(a=>!a)})})]})]})]})}function It(n){let l=new Date(n||0).getTime();if(!l)return"#94a3b8";let m=(Date.now()-l)/864e5;return m<3?"#10b981":m<14?"#f59e0b":m<30?"#f97316":"#94a3b8"}function St(n){let l=new Date(n||0).getTime();return!!l&&(Date.now()-l)/864e5<3}async function at(n,l,m){if(m&&typeof window.showSaveFilePicker=="function")try{let b=await window.showSaveFilePicker({suggestedName:l}),T=await b.createWritable();return await T.write(n),await T.close(),{ok:!0,picked:!0,name:b.name}}catch(b){return b&&b.name==="AbortError"?{ok:!1,aborted:!0}:{ok:!1,err:String(b.message||b)}}let h=URL.createObjectURL(n),i=document.createElement("a");return i.href=h,i.download=l,document.body.appendChild(i),i.click(),document.body.removeChild(i),setTimeout(()=>URL.revokeObjectURL(h),1e3),{ok:!0,picked:!1,name:l}}function tt(n,l){if(!l||!n)return n;let m=n.toLowerCase(),h=l.toLowerCase(),i=[],b=0;for(;b<n.length;){let T=m.indexOf(h,b);if(T<0){i.push(n.slice(b));break}T>b&&i.push(n.slice(b,T)),i.push((0,o.jsx)("mark",{className:"mc-hl",children:n.slice(T,T+h.length)},T)),b=T+h.length}return i}function Tt(n){if(!n)return"";let l=String(n).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),m=[];return l=l.replace(/```([\w-]*)\n([\s\S]*?)```/g,function(h,i,b){return m.push("<pre><code>"+b+"</code></pre>"),"\0"+(m.length-1)+"\0"}),l=l.replace(/^(#{1,3})\s+(.+)$/gm,function(h,i,b){return"<h"+i.length+">"+b+"</h"+i.length+">"}),l=l.replace(/\*\*([^*]+)\*\*/g,"<strong>$1</strong>"),l=l.replace(/`([^`\n]+)`/g,"<code>$1</code>"),l=l.replace(/(^|[^*])\*([^*]+)\*/g,"$1<em>$2</em>"),l=l.replace(/\[([^\]]+)\]\(([^)]+)\)/g,function(h,i,b){return/^(https?:|\/)/.test(b)?'<a href="'+b+'" target="_blank" rel="noopener noreferrer">'+i+"</a>":i}),l=l.replace(/^(-)\s+(.+)$/gm,"\u2022 $2"),l=l.replace(/^\s*(&gt;)\s*(.+)$/gm,"<blockquote>$2</blockquote>"),l=l.replace(/\n/g,"<br/>"),l=l.replace(/\u0000(\d+)\u0000/g,function(h,i){return m[i]}),l}function zt(n){if(!n)return"";let l=new Date(n);if(Number.isNaN(l.getTime()))return"";let m=new Date,h=l.toDateString()===m.toDateString(),i=T=>String(T).padStart(2,"0"),b=`${i(l.getHours())}:${i(l.getMinutes())}`;return h?b:`${l.getMonth()+1}-${l.getDate()} ${b}`}
92
114
 
93
115
  return module.exports;
94
116
  },
package/lib/vault.js CHANGED
@@ -191,6 +191,16 @@ export async function readCard(root, rel) {
191
191
  return fs.readFile(target, 'utf8')
192
192
  }
193
193
 
194
+ /** 删除卡片文件并清理空目录。 */
195
+ export async function deleteCard(root, rel) {
196
+ const target = resolveInside(root, rel)
197
+ if (!target) throw new Error('路径越界')
198
+ await fs.unlink(target)
199
+ const dir = path.dirname(target)
200
+ try { await fs.rmdir(dir) } catch { /* 目录非空则保留 */ }
201
+ return true
202
+ }
203
+
194
204
  /** 原子写卡;先去重(target 目录内),命中返回 {duplicate}。 */
195
205
  export async function writeCard(root, { kind, title, tags = [], body, source = '' }, { threshold = 0.62, dedup = true } = {}) {
196
206
  const kindRoot = KIND_ROOTS[kind] || KIND_ROOTS.knowledge
@@ -288,6 +298,16 @@ export async function search(root, query, { limit = 30 } = {}) {
288
298
  return out.slice(0, limit)
289
299
  }
290
300
 
301
+ /** 导出全部卡片原始 Markdown。 */
302
+ export async function exportCards(root) {
303
+ const cards = await listCards(root)
304
+ const out = []
305
+ for (const c of cards) {
306
+ try { out.push({ path: c.path, title: c.title, kind: c.kind, text: await fs.readFile(path.join(root, c.path), 'utf8') }) } catch { /* 跳过坏文件 */ }
307
+ }
308
+ return out
309
+ }
310
+
291
311
  /** 图谱:节点 = 卡片;边 = [[wikilink]](同 vault 命中)或共享标签。 */
292
312
  export async function graph(root) {
293
313
  const cards = await listCards(root)
@@ -298,9 +318,15 @@ export async function graph(root) {
298
318
  kind: c.kind,
299
319
  tags: c.tags,
300
320
  summary: c.summary.slice(0, 80),
321
+ updated: c.updated,
322
+ mtime: c.mtime,
323
+ linkCount: 0,
301
324
  }))
325
+ const nodeById = new Map(nodes.map((n) => [n.id, n]))
326
+ const addDegree = (p) => { const n = nodeById.get(p); if (n) n.linkCount++ }
302
327
  const edges = []
303
328
  const seen = new Set()
329
+ const bodies = new Map()
304
330
  const addEdge = (a, b, type) => {
305
331
  const key = [a, b].sort().join('|') + '|' + type
306
332
  if (seen.has(key)) return
@@ -314,6 +340,7 @@ export async function graph(root) {
314
340
  } catch {
315
341
  continue
316
342
  }
343
+ bodies.set(card.path, text)
317
344
  // wikilinks:[[目标路径]] 或 [[标题]]
318
345
  for (const m of text.matchAll(/\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g)) {
319
346
  const raw = m[1].trim().replace(/\.md$/, '')
@@ -324,14 +351,14 @@ export async function graph(root) {
324
351
  ]
325
352
  for (const cand of candidates) {
326
353
  if (cand !== card.path && byPath.has(cand)) {
327
- addEdge(card.path, cand, 'link')
354
+ addEdge(card.path, cand, 'link'); addDegree(card.path); addDegree(cand)
328
355
  break
329
356
  }
330
357
  }
331
358
  // 也允许按标题匹配
332
359
  for (const other of cards) {
333
360
  if (other.path !== card.path && (other.title === raw || other.path.endsWith(`/${raw}.md`))) {
334
- addEdge(card.path, other.path, 'link')
361
+ addEdge(card.path, other.path, 'link'); addDegree(card.path); addDegree(other.path)
335
362
  break
336
363
  }
337
364
  }
@@ -343,6 +370,19 @@ export async function graph(root) {
343
370
  }
344
371
  }
345
372
  }
373
+ // 相似度关联建议:仅在同 kind 或共享标签的候选间计算 Jaccard,>0.42 连一条「similar」边,每节点最多 1 条,避免稠密发丝球。
374
+ const similarCount = {}
375
+ const simCandidates = []
376
+ for (const c of cards) simCandidates.push(c.path)
377
+ for (let i = 0; i < simCandidates.length; i++) for (let j = i + 1; j < simCandidates.length; j++) {
378
+ const a = simCandidates[i], b = simCandidates[j]
379
+ const ca = byPath.get(a), cb = byPath.get(b)
380
+ if (!ca || !cb) continue
381
+ if (ca.kind !== cb.kind && !ca.tags.some((t) => cb.tags.includes(t))) continue
382
+ if ((similarCount[a] || 0) >= 1 || (similarCount[b] || 0) >= 1) continue
383
+ const sim = textSimilarity(bodies.get(a) || '', bodies.get(b) || '')
384
+ if (sim >= 0.42) { addEdge(a, b, 'similar'); addDegree(a); addDegree(b); similarCount[a] = (similarCount[a] || 0) + 1; similarCount[b] = (similarCount[b] || 0) + 1 }
385
+ }
346
386
  return { nodes, edges }
347
387
  }
348
388
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-memory-eternal",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "记忆核心(Memory Eternal):自研的 DeepSeek Harness 记忆插件,不移植任何既有记忆框架——对话结束后自动沉淀知识卡到本地 Markdown Vault(自研去重、自研 CJK 检索、可 git 管理),设置页提供图形化知识库(统计 / 搜索 / 知识图谱 + 侧边栏一键弹窗),Agent 通过 memory_recall 工具按需召回历史上下文。零人工干预。",
5
5
  "license": "MIT",
6
6
  "repository": {
Binary file
@@ -1,111 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="zh">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <title>记忆核心 · 完整记忆库弹窗</title>
7
- <style>
8
- * { box-sizing: border-box; margin:0; padding:0; }
9
- body { font-family:-apple-system,"Segoe UI","Microsoft YaHei",sans-serif; background:#e9ebee; color:#1f2937; }
10
- .backdrop { position:fixed; inset:0; background:rgba(10,15,30,.55); backdrop-filter:blur(6px); display:flex; align-items:center; justify-content:center; padding:36px; }
11
- .modal { width:min(1080px,96vw); height:min(86vh,760px); display:flex; flex-direction:column; background:#fff; border:1px solid #e5e7eb; border-radius:18px; box-shadow:0 34px 90px rgba(0,0,0,.5); overflow:hidden; }
12
- .mhead { display:flex; align-items:center; gap:12px; padding:16px 20px; border-bottom:1px solid #eef0f3; }
13
- .mhead .logo { width:26px;height:26px;border-radius:8px;background:#6366f1;color:#fff;display:flex;align-items:center;justify-content:center;font-size:14px; }
14
- .mhead h2 { font-size:17px; font-weight:700; }
15
- .mhead .sub { font-size:12px; color:#6b7280; }
16
- .mhead .sp { flex:1; }
17
- .mhead .x { border:1px solid #d1d5db; background:#fff; width:30px;height:30px;border-radius:8px;cursor:pointer;font-size:15px; }
18
- .body { flex:1; padding:18px 20px; overflow:auto; }
19
- .stats { display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin-bottom:14px; }
20
- .stat { text-align:center; }
21
- .stat b { display:block; font-size:24px; }
22
- .stat span { font-size:12px; color:#6b7280; }
23
- .toolbar { display:flex; gap:8px; align-items:center; margin-bottom:12px; }
24
- .toolbar input { flex:1; padding:8px 12px; border-radius:8px; border:1px solid #d1d5db; font-size:13px; }
25
- .tabs { display:inline-flex; gap:4px; padding:3px; background:#eef0f3; border-radius:999px; }
26
- .tab { border:0; background:transparent; border-radius:999px; padding:6px 16px; font-size:12px; color:#6b7280; cursor:pointer; }
27
- .tab.active { background:#fff; color:#111; font-weight:600; box-shadow:0 1px 4px rgba(0,0,0,.12); }
28
- .btn { border:1px solid #d1d5db; background:#fff; border-radius:8px; padding:6px 14px; font-size:12px; cursor:pointer; }
29
- .graphwrap { border:1px solid #e5e7eb; border-radius:14px; overflow:hidden; background:radial-gradient(120% 120% at 50% 35%, #f8fafc 0%, #eef2f7 100%); }
30
- .graphwrap svg { width:100%; height:100%; display:block; }
31
- .count { font-size:12px; color:#6b7280; }
32
- .legend { display:flex; gap:12px; flex-wrap:wrap; margin-top:10px; font-size:11px; color:#6b7280; }
33
- .dot { width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:5px; }
34
- .edge { fill:none; stroke-width:1.6; stroke-dasharray:4 5; animation:dash 1.6s linear infinite; }
35
- @keyframes dash { to { stroke-dashoffset:-16; } }
36
- .node text { font-size:12px; fill:#334155; paint-order:stroke; stroke:rgba(255,255,255,.85); stroke-width:3px; pointer-events:none; }
37
- </style>
38
- </head>
39
- <body>
40
- <div class="backdrop">
41
- <div class="modal">
42
- <div class="mhead">
43
- <span class="logo">◆</span>
44
- <h2>记忆库</h2>
45
- <span class="sub">你的本地第二大脑 · 原创自研</span>
46
- <span class="sp"></span>
47
- <button class="x">✕</button>
48
- </div>
49
- <div class="body">
50
- <div class="stats">
51
- <div class="stat"><b style="color:#3b82f6">42</b><span>知识卡</span></div>
52
- <div class="stat"><b style="color:#22c55e">9</b><span>近 7 天新增</span></div>
53
- <div class="stat"><b style="color:#eab308">31</b><span>标签</span></div>
54
- <div class="stat"><b style="color:#8b5cf6">37</b><span>知识卡</span></div>
55
- </div>
56
- <div class="toolbar">
57
- <input type="text" placeholder="搜索记忆(支持中文片段)…" value="B+树 索引">
58
- <div class="tabs"><button class="tab">知识卡</button><button class="tab active">知识图谱</button></div>
59
- <button class="btn">刷新</button>
60
- </div>
61
- <div class="graphwrap" id="graphwrap" style="height:380px"></div>
62
- <div class="legend" id="legend"></div>
63
- </div>
64
- </div>
65
- </div>
66
-
67
- <script>
68
- const W=980,H=380;
69
- const KIND={project:'#3b82f6',knowledge:'#22c55e',content:'#f43f5e',prompt:'#06b6d4',business:'#eab308',other:'#8b5cf6'};
70
- const LBL={project:'项目',knowledge:'知识',content:'内容',prompt:'提示词',business:'业务',other:'其他'};
71
- const nodes=[
72
- {id:'a',title:'数据库索引原理与B+树',kind:'knowledge'},
73
- {id:'b',title:'Redis缓存雪崩防护',kind:'knowledge'},
74
- {id:'c',title:'项目极光 · 技术选型',kind:'project'},
75
- {id:'d',title:'DSH 插件发布范式',kind:'prompt'},
76
- {id:'e',title:'知识图谱可视化方案',kind:'content'},
77
- {id:'f',title:'插件市场上架流程',kind:'business'},
78
- {id:'g',title:'B+树磁盘IO模型',kind:'knowledge'},
79
- {id:'h',title:'覆盖索引回表优化',kind:'knowledge'},
80
- ];
81
- const edges=[{s:'a',t:'g'},{s:'a',t:'h'},{s:'a',t:'e'},{s:'b',t:'e'},{s:'c',t:'f'},{s:'c',t:'d'},{s:'f',t:'d'},{s:'e',t:'d'},{s:'g',t:'a'}];
82
- function hexToRgb(h){const x=h.replace('#','');const f=x.length===3?x.split('').map(c=>c+c).join(''):x;return[parseInt(f.slice(0,2),16),parseInt(f.slice(2,4),16),parseInt(f.slice(4,6),16)];}
83
- function lighten(h,a){const[r,g,b]=hexToRgb(h);return`rgb(${Math.round(r+(255-r)*a)},${Math.round(g+(255-g)*a)},${Math.round(b+(255-b)*a)})`;}
84
- function darken(h,a){const[r,g,b]=hexToRgb(h);return`rgb(${Math.round(r*(1-a))},${Math.round(g*(1-a))},${Math.round(b*(1-a))})`;}
85
- function layout(nodes,edges,w,h){
86
- const n=nodes.length,pos=nodes.map((_,i)=>{const a=(i/n)*Math.PI*2-Math.PI/2;return{x:Math.cos(a),y:Math.sin(a)};});
87
- const vel=pos.map(()=>({x:0,y:0}));const adj=nodes.map(()=>[]);
88
- edges.forEach(e=>{const si=nodes.findIndex(x=>x.id===e.s),ti=nodes.findIndex(x=>x.id===e.t);if(si>=0&&ti>=0&&si!==ti){adj[si].push(ti);adj[ti].push(si);}});
89
- const rep=1.35,att=0.04,cen=0.03,dam=0.84;
90
- for(let it=0;it<300;it++){
91
- for(let i=0;i<n;i++)for(let j=i+1;j<n;j++){let dx=pos[i].x-pos[j].x,dy=pos[i].y-pos[j].y;const d2=dx*dx+dy*dy||0.01,d=Math.sqrt(d2),f=rep/d2,fx=(dx/d)*f,fy=(dy/d)*f;vel[i].x+=fx;vel[i].y+=fy;vel[j].x-=fx;vel[j].y-=fy;}
92
- for(let i=0;i<n;i++)for(const j of adj[i]){vel[i].x+=(pos[j].x-pos[i].x)*att;vel[i].y+=(pos[j].y-pos[i].y)*att;}
93
- for(let i=0;i<n;i++){vel[i].x-=pos[i].x*cen;vel[i].y-=pos[i].y*cen;}
94
- for(let i=0;i<n;i++){vel[i].x=Math.max(-0.5,Math.min(0.5,vel[i].x*dam));vel[i].y=Math.max(-0.5,Math.min(0.5,vel[i].y*dam));pos[i].x+=vel[i].x;pos[i].y+=vel[i].y;}
95
- }
96
- let mi=Infinity,ma=-Infinity,mj=Infinity,mb=-Infinity;pos.forEach(p=>{mi=Math.min(mi,p.x);ma=Math.max(ma,p.x);mj=Math.min(mj,p.y);mb=Math.max(mb,p.y);});
97
- const pad=70,s=Math.min((w-2*pad)/(ma-mi||1),(h-2*pad)/(mb-mj||1)),cx=(mi+ma)/2,cy=(mj+mb)/2;
98
- return pos.map(p=>({x:w/2+(p.x-cx)*s,y:h/2+(p.y-cy)*s}));
99
- }
100
- function path(a,b){const mx=(a.x+b.x)/2,my=(a.y+b.y)/2,dx=b.x-a.x,dy=b.y-a.y,dist=Math.sqrt(dx*dx+dy*dy)||1,bend=Math.min(38,dist*0.18),cx=mx-(dy/dist)*bend,cy=my+(dx/dist)*bend;return`M ${a.x} ${a.y} Q ${cx} ${cy} ${b.x} ${b.y}`;}
101
- const l=layout(nodes,edges,W,H),deg={};edges.forEach(e=>{deg[e.s]=(deg[e.s]||0)+1;deg[e.t]=(deg[e.t]||0)+1;});
102
- const idx=nodes.reduce((m,nd,i)=>(m[nd.id]=i,m),{});const seen=new Set();const shown=edges.filter(e=>{const k=[e.s,e.t].sort().join('|');if(seen.has(k))return false;seen.add(k);return true;});
103
- let defs='';Object.keys(KIND).forEach(k=>{defs+=`<radialGradient id="g-${k}" cx="35%" cy="30%" r="75%"><stop offset="0%" stop-color="${lighten(KIND[k],0.55)}"/><stop offset="60%" stop-color="${KIND[k]}"/><stop offset="100%" stop-color="${darken(KIND[k],0.25)}"/></radialGradient><linearGradient id="e-${k}" gradientUnits="userSpaceOnUse"><stop offset="0%" stop-color="${KIND[k]}" stop-opacity="0.4"/><stop offset="100%" stop-color="${KIND[k]}" stop-opacity="0.85"/></linearGradient>`;});
104
- defs+=`<filter id="glow" x="-60%" y="-60%" width="220%" height="220%"><feGaussianBlur stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>`;
105
- let es=shown.map((e,i)=>{const si=idx[e.s],ti=idx[e.t];if(si===undefined||ti===undefined)return'';const a=l[si],b=l[ti],kind=nodes[si].kind||'other';return`<path d="${path(a,b)}" class="edge" style="stroke:url(#e-${kind});animation-delay:${(i%8)*0.12}s"/>`;}).join('');
106
- let ns=nodes.map((nd,i)=>{const p=l[i],kind=nd.kind||'other',r=8+Math.min(9,(deg[nd.id]||0)*1.4);return`<g transform="translate(${p.x} ${p.y})"><circle r="${r}" fill="url(#g-${kind})" stroke="${KIND[kind]}" stroke-opacity="0.9" filter="url(#glow)"/><circle r="${Math.max(2.4,r*0.2)}" fill="rgba(255,255,255,.85)"/><text x="${r+8}" y="3">${nd.title.slice(0,14)}</text></g>`;}).join('');
107
- document.getElementById('graphwrap').innerHTML=`<svg viewBox="0 0 ${W} ${H}"><defs>${defs}</defs>${es}${ns}</svg>`;
108
- document.getElementById('legend').innerHTML=Object.keys(KIND).map(k=>`<span><span class="dot" style="background:${KIND[k]}"></span>${LBL[k]}</span>`).join('')+`<span style="opacity:.7;margin-left:auto">节点=知识卡;连线=[[链接]]或共享标签 · 点击节点阅读卡片</span>`;
109
- </script>
110
- </body>
111
- </html>
@@ -1,132 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="zh">
3
- <head>
4
- <meta charset="utf-8">
5
- <title>记忆核心 · 新设置主界面</title>
6
- <style>
7
- * { box-sizing: border-box; margin: 0; padding: 0; }
8
- body { font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif; background: var(--bg,#f4f5f7); color: #1f2937; }
9
- .app { display: grid; grid-template-columns: 216px 1fr; min-height: 100vh; }
10
- /* sidebar */
11
- .nav { background: #0f172a; color: #cbd5e1; padding: 18px 12px; }
12
- .nav .brand { display:flex; align-items:center; gap:8px; font-weight:700; color:#fff; font-size:15px; padding:0 8px 16px; }
13
- .nav .brand .mark { width:24px;height:24px;border-radius:7px;background:#6366f1;display:inline-flex;align-items:center;justify-content:center;color:#fff;font-size:13px; }
14
- .nav a { display:flex; align-items:center; gap:10px; padding:9px 10px; border-radius:8px; color:#cbd5e1; font-size:13.5px; text-decoration:none; margin-bottom:2px; }
15
- .nav a .i { width:16px;height:16px;flex:none;opacity:.85; }
16
- .nav a.active { background:#6366f1; color:#fff; font-weight:600; }
17
- .nav .grp { font-size:11px;color:#64748b;padding:12px 10px 6px;letter-spacing:.06em; }
18
- /* main */
19
- .main { padding: 26px 30px 40px; }
20
- .pagehead { display:flex; align-items:center; gap:12px; margin-bottom:6px; }
21
- .pagehead h1 { font-size:19px; }
22
- .pagehead .sub { font-size:12px;color:#6b7280; }
23
- .stats { display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin:16px 0 14px; }
24
- .stat { text-align:center; }
25
- .stat b { display:block; font-size:26px; }
26
- .stat span { font-size:12px; color:#6b7280; }
27
- .toolbar { display:flex; gap:8px; align-items:center; margin-bottom:12px; }
28
- .toolbar input { flex:1; padding:8px 12px; border-radius:8px; border:1px solid #d1d5db; font-size:13px; }
29
- .tabs { display:inline-flex; gap:4px; padding:3px; background:#eef0f3; border-radius:999px; }
30
- .tab { border:0; background:transparent; border-radius:999px; padding:6px 16px; font-size:12px; color:#6b7280; cursor:pointer; }
31
- .tab.active { background:#fff; color:#111; font-weight:600; box-shadow:0 1px 4px rgba(0,0,0,.12); }
32
- .btn { border:1px solid #d1d5db; background:#fff; border-radius:8px; padding:6px 14px; font-size:12px; cursor:pointer; }
33
- .chips { display:flex; gap:6px; margin-bottom:14px; flex-wrap:wrap; }
34
- .chip { border:1px solid #d1d5db; background:#fff; border-radius:999px; padding:4px 12px; font-size:12px; color:#6b7280; }
35
- .chip.active { color:#111; font-weight:600; border-color:#111; }
36
- .grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
37
- .kcard { border:1px solid #e5e7eb; border-radius:12px; padding:14px 16px; display:flex; flex-direction:column; gap:6px; background:#fff; cursor:pointer; }
38
- .kcard h4 { font-size:14px; }
39
- .kcard p { font-size:12px; color:#6b7280; line-height:1.5; display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; }
40
- .kcard footer { margin-top:auto; display:flex; justify-content:space-between; font-size:11px; color:#9ca3af; }
41
- .dot { width:8px; height:8px; border-radius:50%; display:inline-block; margin-right:6px; }
42
- .tags { display:flex; gap:4px; }
43
- .tag { font-size:10px; padding:1px 7px; border-radius:999px; background:#f3f4f6; color:#6b7280; }
44
- .well { background:#fff; border:1px solid #e5e7eb; border-radius:12px; padding:14px 16px; }
45
- </style>
46
- </head>
47
- <body>
48
- <div class="app">
49
- <div class="nav">
50
- <div class="brand"><span class="mark">◈</span> deepseek<small style="margin-left:4px;font-weight:500;opacity:.8">HARNESS</small></div>
51
- <div class="grp">设置</div>
52
- <a href="#"><span class="i">⚙</span> 通用设置</a>
53
- <a href="#"><span class="i">▦</span> 模型</a>
54
- <a href="#"><span class="i">◧</span> 插件</a>
55
- <a href="#"><span class="i">◫</span> Agent 预设</a>
56
- <a href="#"><span class="i">◈</span> 三体</a>
57
- <a href="#"><span class="i">▤</span> 像素办公室</a>
58
- <a href="#" class="active"><span class="i">◆</span> 记忆</a>
59
- <a href="#"><span class="i">◒</span> 插件市场</a>
60
- <a href="#"><span class="i">▣</span> Vision Router</a>
61
- </div>
62
- <div class="main">
63
- <div class="pagehead">
64
- <h1>🧠 记忆</h1>
65
- <span class="sub">记忆核心 · 对话自动沉淀知识卡到本地 Markdown Vault · 原创自研</span>
66
- </div>
67
- <div class="well">
68
- <div class="stats">
69
- <div class="stat"><b style="color:#3b82f6">42</b><span>知识卡</span></div>
70
- <div class="stat"><b style="color:#22c55e">9</b><span>近 7 天新增</span></div>
71
- <div class="stat"><b style="color:#eab308">31</b><span>标签</span></div>
72
- <div class="stat"><b style="color:#8b5cf6">37</b><span>知识卡</span></div>
73
- </div>
74
- <div class="toolbar">
75
- <input type="text" placeholder="搜索记忆(支持中文片段)…" value="B+树 索引">
76
- <div class="tabs">
77
- <button class="tab active">知识卡</button>
78
- <button class="tab">知识图谱</button>
79
- </div>
80
- <button class="btn">刷新</button>
81
- </div>
82
- <div class="chips">
83
- <button class="chip active">全部</button>
84
- <button class="chip">项目</button>
85
- <button class="chip">知识</button>
86
- <button class="chip">内容</button>
87
- <button class="chip">提示词</button>
88
- <button class="chip">业务</button>
89
- </div>
90
- </div>
91
- <div class="grid" style="margin-top:14px">
92
- <div class="kcard">
93
- <h4><span class="dot" style="background:#22c55e"></span>数据库索引原理与B+树加速机制</h4>
94
- <p>索引是数据库为加速查找而额外维护的数据结构…复杂度从 O(n) 降到 O(log n),一次查询只需 3~4 次磁盘 I/O。</p>
95
- <div class="tags"><span class="tag">数据库</span><span class="tag">索引</span><span class="tag">B+树</span></div>
96
- <footer><span>知识</span><span>08-21 20:04</span></footer>
97
- </div>
98
- <div class="kcard">
99
- <h4><span class="dot" style="background:#22c55e"></span>Redis 缓存雪崩:定义与预防方案</h4>
100
- <p>缓存热点数据 TTL 10 分钟,穿透用空值缓存,随机过期防雪崩,大 key 分段缓存降低序列化开销…</p>
101
- <div class="tags"><span class="tag">Redis</span><span class="tag">缓存</span><span class="tag">高可用</span></div>
102
- <footer><span>知识</span><span>08-21 20:41</span></footer>
103
- </div>
104
- <div class="kcard">
105
- <h4><span class="dot" style="background:#3b82f6"></span>项目极光 · 技术选型</h4>
106
- <p>项目代号:极光(Aurora)。数据库最终选用 PostgreSQL;前端用 Vite 构建。迁移成本可控…</p>
107
- <div class="tags"><span class="tag">项目</span><span class="tag">PostgreSQL</span></div>
108
- <footer><span>项目</span><span>08-21 19:59</span></footer>
109
- </div>
110
- <div class="kcard">
111
- <h4><span class="dot" style="background:#06b6d4"></span>DSH 插件发布范式</h4>
112
- <p>npm keywords 带 dsh-plugin + GitHub 打 topic → 市场自动收录;prepublishOnly 自动 build client…</p>
113
- <div class="tags"><span class="tag">DSH</span><span class="tag">插件</span></div>
114
- <footer><span>提示词</span><span>08-20 22:11</span></footer>
115
- </div>
116
- <div class="kcard">
117
- <h4><span class="dot" style="background:#f43f5e"></span>知识图谱可视化方案</h4>
118
- <p>力导向布局按 kind 着色;wikilink 与共享标签连线;点击节点打开卡片阅读全文…</p>
119
- <div class="tags"><span class="tag">可视化</span><span class="tag">图谱</span></div>
120
- <footer><span>内容</span><span>08-19 15:02</span></footer>
121
- </div>
122
- <div class="kcard">
123
- <h4><span class="dot" style="background:#eab308"></span>插件市场选品与上架流程</h4>
124
- <p>关键词覆盖 + 截图质量 + 自动化测试是拿 Star 的关键;发布前跑通 npm test 与 build…</p>
125
- <div class="tags"><span class="tag">业务</span><span class="tag">上架</span></div>
126
- <footer><span>业务</span><span>08-18 10:12</span></footer>
127
- </div>
128
- </div>
129
- </div>
130
- </div>
131
- </body>
132
- </html>
@@ -1,54 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="zh">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <title>记忆核心 · 侧边栏入口</title>
7
- <style>
8
- * { box-sizing:border-box; margin:0; padding:0; }
9
- body { font-family:-apple-system,"Segoe UI","Microsoft YaHei",sans-serif; background:#eceef1; color:#1f2937; padding:36px; }
10
- .phone { width:300px; background:#101827; border-radius:14px; overflow:hidden; box-shadow:0 24px 60px rgba(0,0,0,.35); margin:0 auto; }
11
- .brand { display:flex; align-items:center; gap:8px; color:#fff; font-weight:700; font-size:14px; padding:16px 14px 10px; }
12
- .brand .mk { width:24px;height:24px;border-radius:7px;background:#6366f1;display:flex;align-items:center;justify-content:center;font-size:13px; }
13
- .brand small { font-weight:500; opacity:.8; }
14
- .new { margin:6px 10px 12px; background:#111c2f; border:1px solid #1e293b; color:#e2e8f0; border-radius:10px; padding:9px; font-size:12.5px; display:flex; align-items:center; justify-content:center; gap:6px; }
15
- .wshead { display:flex; align-items:center; justify-content:space-between; padding:6px 14px; color:#64748b; font-size:11px; }
16
- .ws { padding:8px 14px; display:flex; align-items:center; gap:9px; color:#cbd5e1; font-size:13px; border-radius:8px; margin:0 8px; }
17
- .ws.active { background:#1e2a3f; color:#fff; }
18
- .ws .fc { width:18px;height:18px;border-radius:5px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;color:#94a3b8;border:1px solid #243247; }
19
- .ws .sp { flex:1; }
20
- .ws .mt { font-size:11px; color:#64748b; }
21
- .foot { margin-top:14px; border-top:1px solid #162033; padding:8px; }
22
- .footbtn { display:flex; align-items:center; gap:10px; padding:9px 12px; border-radius:8px; color:#94a3b8; font-size:13px; }
23
- .footbtn:hover { background:#1a2639; color:#fff; }
24
- .footbtn.hot { color:#e2e8f0; }
25
- .footbtn .ic { width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;font-size:14px; }
26
- .mark { position:absolute; right:-8px; top:-8px; background:#6366f1; color:#fff; font-size:11px; padding:2px 8px; border-radius:999px; font-weight:600; box-shadow:0 6px 16px rgba(99,102,241,.5); }
27
- .tag { display:inline-block; margin-top:10px; font-size:12px; color:#6b7280; text-align:center; width:100%; }
28
- </style>
29
- </head>
30
- <body>
31
- <div style="position:relative;max-width:300px;margin:0 auto">
32
- <div class="phone">
33
- <div class="brand"><span class="mk">◈</span> deepseek<small>HARNESS</small></div>
34
- <div class="new">+ 新会话</div>
35
- <div class="wshead"><span>工作区</span><span style="opacity:.7">🔍 ⚙ +</span></div>
36
- <div class="ws active"><span class="fc">▤</span> dsh-memory-eternal<span class="sp"></span></div>
37
- <div class="ws"><span class="fc">▤</span> shj-platform</div>
38
- <div class="ws"><span class="fc">▤</span> ai-job-seeker</div>
39
- <div class="ws"><span class="fc">▤</span> dsh-desktop</div>
40
- <div class="ws"><span class="fc">○</span> e-test</div>
41
- <div class="ws"><span class="fc">○</span> os-active</div>
42
- <div class="ws"><span class="fc">▤</span> dsh-ui-three-body</div>
43
- <div class="ws"><span class="fc">▤</span> dsh-ui-agents-pixe</div>
44
- <div class="foot">
45
- <div class="footbtn"><span class="ic">⚙</span> 设置</div>
46
- <div class="footbtn"><span class="ic">↻</span> 更新配置</div>
47
- <div class="footbtn hot"><span class="ic">◆</span> 记忆<span style="margin-left:auto;font-size:10px;opacity:.7">42 卡</span></div>
48
- </div>
49
- </div>
50
- <span class="mark">新增</span>
51
- </div>
52
- <div class="tag">侧边栏底部新增「记忆」入口 · 一键打开完整记忆库</div>
53
- </body>
54
- </html>