memory-eternal 0.12.0 → 0.13.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.
@@ -2,9 +2,9 @@
2
2
  "$schema": "urn:dsh-std:community-draft:dsh-plugin:0.15",
3
3
  "id": "memory-eternal-tui",
4
4
  "name": "memory-eternal-tui",
5
- "version": "0.12.0",
5
+ "version": "0.13.0",
6
6
  "manifestVersion": "0.15",
7
- "description": "Open the local Memory Atlas Web UI from dsh-TUI.",
7
+ "description": "Open Memory Atlas from dsh-TUI: native /audit scene plus browser launcher.",
8
8
  "facets": {
9
9
  "host": {
10
10
  "entry": "main.js",
@@ -26,6 +26,11 @@
26
26
  "id": "memory-eternal-tui.open",
27
27
  "title": "memory",
28
28
  "description": "Open Memory Atlas in the browser"
29
+ },
30
+ {
31
+ "id": "memory-eternal-tui.audit",
32
+ "title": "audit",
33
+ "description": "Open the native Memory Atlas audit scene"
29
34
  }
30
35
  ]
31
36
  },
@@ -3,12 +3,15 @@
3
3
  // 这里不做第二套 TUI UI:命令只负责把用户送到同一套 Memory Atlas Web UI。
4
4
  // 审批仍然在 Web 里由人完成,不新增 agent 工具,也不触碰审核守卫。
5
5
  import { spawn } from 'node:child_process'
6
+ import { AuditScene } from './scene.js'
6
7
 
7
8
  export const name = 'memory-eternal-tui'
8
9
  export const inject = ['agents']
9
10
 
10
11
  const URL = 'http://127.0.0.1:7999/'
11
12
  const COMMAND_ID = 'memory-eternal-tui.open'
13
+ const AUDIT_COMMAND_ID = 'memory-eternal-tui.audit'
14
+ const AUDIT_SCENE_ID = 'memory-eternal-tui-audit'
12
15
 
13
16
  function openUrl() {
14
17
  const cmd = process.platform === 'win32' ? 'rundll32' : process.platform === 'darwin' ? 'open' : 'xdg-open'
@@ -41,6 +44,32 @@ export function apply(ctx) {
41
44
  const host = ctx.get('tuiPluginHost', false)
42
45
  if (!host) return
43
46
 
47
+ // tuiScenes can mount after this activation on a cold boot. Soft-probe and
48
+ // retry briefly instead of silently leaving /audit dead for the session.
49
+ let sceneDisposer = null
50
+ let sceneTimer = null
51
+ let attempts = 0
52
+ const registerScene = () => {
53
+ sceneTimer = null
54
+ const scenes = ctx.get('tuiScenes', false)
55
+ if (scenes && !sceneDisposer) {
56
+ try {
57
+ sceneDisposer = scenes.register({ id: AUDIT_SCENE_ID, title: 'Memory Atlas / 待审', component: AuditScene }, ctx)
58
+ } catch (error) {
59
+ console.warn('[memory-eternal-tui] scene registration failed:', error)
60
+ }
61
+ }
62
+ if (!sceneDisposer && attempts < 40) {
63
+ attempts += 1
64
+ sceneTimer = setTimeout(registerScene, 50)
65
+ }
66
+ }
67
+ registerScene()
68
+ ctx.effect(() => {
69
+ if (sceneTimer) clearTimeout(sceneTimer)
70
+ if (sceneDisposer) sceneDisposer()
71
+ })
72
+
44
73
  const dispose = host.registerCommand(ctx, COMMAND_ID, {
45
74
  recordInput: false,
46
75
  handler: () => {
@@ -49,6 +78,20 @@ export function apply(ctx) {
49
78
  },
50
79
  })
51
80
  ctx.effect(() => dispose)
81
+
82
+ const disposeAudit = host.registerCommand(ctx, AUDIT_COMMAND_ID, {
83
+ recordInput: false,
84
+ handler: () => {
85
+ const runtime = ctx.get('tuiScenes', false)
86
+ if (!runtime) return { kind: 'error', text: '当前 dsh-tui 没有 scenes 运行时' }
87
+ if (runtime.active?.id === AUDIT_SCENE_ID) runtime.close()
88
+ const opened = runtime.open(AUDIT_SCENE_ID)
89
+ return opened
90
+ ? { kind: 'success', text: 'Memory Atlas / 待审 已打开' }
91
+ : { kind: 'error', text: '审核场景未注册' }
92
+ },
93
+ })
94
+ ctx.effect(() => disposeAudit)
52
95
  }
53
96
 
54
97
  export default { name, inject, apply }
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "memory-eternal-tui",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Open Memory Atlas from dsh-TUI with /memory.",
5
5
  "type": "module",
6
6
  "main": "main.js",
7
7
  "files": [
8
8
  "main.js",
9
+ "scene.js",
10
+ "demo.mjs",
9
11
  "dsh-plugin.json",
10
12
  "cordis.patch.yml",
11
13
  "README.md"
@@ -0,0 +1,151 @@
1
+ // dsh-tui 原生审核场景(最小闭环)
2
+ //
3
+ // 不复制 Web UI:只把「待审队列 + 全选 + 批准/驳回」做成 dsh-tui 原生场景。
4
+ // 日常浏览/长文仍可回到 Web 或外部 TUI 客户端。
5
+
6
+ const API = process.env.MEMORY_ETERNAL_API || 'http://127.0.0.1:7999/memory-eternal/api'
7
+
8
+ async function apiJson(path, options = {}) {
9
+ const res = await fetch(API + path, { ...options, signal: AbortSignal.timeout(10000) })
10
+ const data = await res.json().catch(() => null)
11
+ if (!res.ok || !data || data.ok === false) throw new Error((data && data.error) || `HTTP ${res.status}`)
12
+ return data
13
+ }
14
+
15
+ function timeOf(value) {
16
+ const d = new Date(value || Date.now())
17
+ return Number.isNaN(d.getTime()) ? '' : d.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false })
18
+ }
19
+
20
+ export function AuditScene(props) {
21
+ const { React, ui, close } = props
22
+ const { Box, Text, useInput, useTerminalSize } = ui
23
+ const { useState, useEffect } = React
24
+ const size = useTerminalSize ? useTerminalSize() : { columns: 110, rows: 34 }
25
+ const columns = size.columns || 110
26
+ const rows = size.rows || 34
27
+
28
+ const [cards, setCards] = useState([])
29
+ const [index, setIndex] = useState(0)
30
+ const [marked, setMarked] = useState(() => new Set())
31
+ const [status, setStatus] = useState('读取待审队列…')
32
+ const [loading, setLoading] = useState(true)
33
+
34
+ const load = async () => {
35
+ setLoading(true)
36
+ setStatus('读取待审队列…')
37
+ try {
38
+ const data = await apiJson('/cards?status=pending&limit=300')
39
+ setCards((data.cards || []).map((c) => ({
40
+ path: c.path,
41
+ kind: c.kind || 'knowledge',
42
+ title: c.title || '无标题',
43
+ summary: String(c.summary || '').replace(/^#{1,6}\s*/gm, '').replace(/\s+/g, ' ').slice(0, 260),
44
+ source: c.submittedBy || 'memory',
45
+ time: timeOf(c.updated || c.created),
46
+ })))
47
+ setIndex(0)
48
+ setMarked(new Set())
49
+ setStatus('')
50
+ } catch (err) {
51
+ setStatus('读取失败:' + err.message)
52
+ } finally {
53
+ setLoading(false)
54
+ }
55
+ }
56
+
57
+ useEffect(() => { void load() }, [])
58
+
59
+ const current = cards[index]
60
+ const visible = cards
61
+ const selected = marked.size
62
+ const apply = async (decision) => {
63
+ const targets = selected ? visible.filter((c) => marked.has(c.path)) : (current ? [current] : [])
64
+ if (!targets.length) return
65
+ setStatus(decision === 'a' ? `批准 ${targets.length} 张…` : `驳回 ${targets.length} 张…`)
66
+ try {
67
+ await Promise.all(targets.map((c) => apiJson('/audit/' + (decision === 'a' ? 'approve' : 'reject'), {
68
+ method: 'POST',
69
+ headers: { 'Content-Type': 'application/json' },
70
+ body: JSON.stringify({ path: c.path }),
71
+ })))
72
+ const ids = new Set(targets.map((c) => c.path))
73
+ setCards((prev) => prev.filter((c) => !ids.has(c.path)))
74
+ setIndex(0)
75
+ setMarked(new Set())
76
+ setStatus(`✓ ${decision === 'a' ? '已批准' : '已驳回'} ${targets.length} 张`)
77
+ } catch (err) {
78
+ setStatus('操作失败:' + err.message)
79
+ }
80
+ }
81
+
82
+ useInput((input, key) => {
83
+ if (key.escape || input === 'q') { close(); return }
84
+ if (input === 'r' && !key.ctrl) { void apply('r'); return }
85
+ if (input === 'a' && !key.ctrl) { void apply('a'); return }
86
+ if (input === 's' || (key.ctrl && input === 'a')) {
87
+ setMarked((prev) => {
88
+ const all = visible.length > 0 && visible.every((c) => prev.has(c.path))
89
+ return all ? new Set() : new Set(visible.map((c) => c.path))
90
+ })
91
+ return
92
+ }
93
+ if (key.upArrow) { setIndex((i) => Math.max(0, i - 1)); return }
94
+ if (key.downArrow) { setIndex((i) => Math.min(Math.max(0, visible.length - 1), i + 1)); return }
95
+ if (key.space || input === ' ') {
96
+ if (!current) return
97
+ setMarked((prev) => {
98
+ const next = new Set(prev)
99
+ if (next.has(current.path)) next.delete(current.path)
100
+ else next.add(current.path)
101
+ return next
102
+ })
103
+ }
104
+ })
105
+
106
+ const listH = Math.max(3, rows - 7)
107
+ const rowW = Math.max(36, Math.floor((columns - 3) * 0.42))
108
+ const previewW = Math.max(20, columns - rowW - 3)
109
+ const start = Math.max(0, Math.min(index - Math.floor(listH / 2), Math.max(0, visible.length - listH)))
110
+ const list = (visible || []).slice(start, start + listH)
111
+ const previewLines = []
112
+ if (current) {
113
+ previewLines.push(current.kind + ' · ' + current.time + ' · ' + current.source)
114
+ previewLines.push('')
115
+ previewLines.push(current.title)
116
+ previewLines.push('')
117
+ for (let i = 0; i < current.summary.length; i += Math.max(16, previewW - 4)) {
118
+ previewLines.push(current.summary.slice(i, i + Math.max(16, previewW - 4)))
119
+ }
120
+ } else {
121
+ previewLines.push(loading ? '读取中…' : '没有待审卡')
122
+ }
123
+
124
+ const h = React.createElement
125
+ return h(Box, { flexDirection: 'column', width: columns, height: rows },
126
+ h(Box, { flexShrink: 0 },
127
+ h(Text, { color: 'remember', bold: true }, '● MEMORY ATLAS'),
128
+ h(Text, { dimColor: true }, ` / 待审 ${cards.length} 张` + (selected ? ` 已选 ${selected}` : '')),
129
+ ),
130
+ h(Text, { dimColor: true }, '─'.repeat(Math.max(1, columns - 1))),
131
+ h(Box, { flexDirection: 'row', flexGrow: 1, overflow: 'hidden' },
132
+ h(Box, { flexDirection: 'column', width: rowW, borderStyle: 'round', flexShrink: 0 },
133
+ h(Text, { dimColor: true }, ` ${selected ? '▣' : '☐'} 全选 ${visible.length} s 全选`),
134
+ ...list.map((c, i) => {
135
+ const on = start + i === index
136
+ const mark = marked.has(c.path) ? '◉' : '○'
137
+ const label = ` ${on ? '▌' : ' '}${mark} [${c.kind}] ${c.title}`
138
+ return h(Text, { key: c.path, inverse: on, wrap: 'truncate' }, label.slice(0, rowW - 2))
139
+ }),
140
+ ),
141
+ h(Box, { flexDirection: 'column', width: previewW, borderStyle: 'round', flexGrow: 1 },
142
+ ...previewLines.slice(0, Math.max(1, rows - 4)).map((line, i) =>
143
+ h(Text, { key: i, wrap: 'truncate', color: i === 0 ? 'remember' : undefined }, ' ' + line)),
144
+ ),
145
+ ),
146
+ h(Text, { color: status.startsWith('✓') ? 'success' : status.startsWith('操作失败') || status.startsWith('读取失败') ? 'error' : undefined, dimColor: !status },
147
+ ' ' + (status || '↑↓ 移动 Space 多选 s 全选 a 批准 r 驳回 Esc 退出')),
148
+ )
149
+ }
150
+
151
+ export default AuditScene
package/lib/client.js CHANGED
@@ -701,7 +701,7 @@ Option A, because:
701
701
  .mc-audit-batchcard > .mc-btn { width: 100%; }
702
702
  .mc-audit-batchcard > div[style*="flex: 1"] { display: none; }
703
703
  }
704
- `;function Ea(t){t.effect(()=>t.locale.register(kt,{zh:da,en:ca}),"memory-eternal: locale");let a=t.locale.bind(kt);t.effect(()=>t.slots.inject("settings.section",()=>t.slots.register({name:"settings.section",id:kt,order:25,label:()=>a("nav"),locale:kt,inject:()=>({})},()=>o.default.createElement(Wa,{t:a}))),"memory-eternal: settings section"),t.effect(()=>t.slots.inject("sidebar.footer.action",()=>t.slots.register({name:"sidebar.footer.action",id:`${kt}:footer`,order:100,label:()=>a("nav"),locale:kt,inject:()=>({})},i=>o.default.createElement($a,{t:a,locale:t.locale,wide:!(i&&i.wide===!1)}))),"memory-eternal: sidebar footer action")}function Oa(){let[t,a]=(0,o.useState)($+"/ui/app");return(0,o.useEffect)(()=>{let i=!0,g=()=>fetch("/memory-eternal/api/web-info").then(c=>c.json()).then(c=>{i&&c&&c.ok&&c.alive!==!1&&c.url&&a(c.url.endsWith("/")?c.url:c.url+"/")}).catch(()=>{});return fetch($+"/ui/app",{method:"HEAD"}).then(c=>{if(i&&!c.ok)return g()}).catch(()=>i?g():void 0),()=>{i=!1}},[]),t}function Wa({t}){return(0,e.jsxs)("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0},children:[(0,e.jsx)("style",{children:Ue}),(0,e.jsx)(ua,{t,onReload:()=>{},version:0})]})}function $a({t,locale:a,wide:i}){let[g,c]=(0,o.useState)(!1);return(0,e.jsxs)("div",{className:`me-footer${i?"":" rail"}`,children:[(0,e.jsx)("style",{children:Ue}),(0,e.jsxs)("button",{type:"button",className:"me-footer-btn",onClick:()=>c(!0),"aria-label":t("nav"),title:t("nav"),children:[(0,e.jsx)("span",{className:"me-footer-ico","aria-hidden":"true",children:(0,e.jsx)(Ua,{})}),(0,e.jsx)("span",{className:"me-footer-label",children:t("nav")})]}),g&&(0,e.jsx)(_a,{t,locale:a,onClose:()=>c(!1)})]})}function _a({t,locale:a,onClose:i,tab:g}){let[c,s]=(0,o.useState)(!1),h=(0,o.useRef)(null),k=Oa(),S=(0,o.useCallback)(N=>a&&typeof a.subscribe=="function"?a.subscribe(N):()=>{},[a]),L=(0,o.useCallback)(()=>a&&typeof a.getSnapshot=="function"?a.getSnapshot():null,[a]),M=(0,o.useSyncExternalStore)(S,L),x=M&&(M.active==="zh"||M.active==="en")?M.active:null,m=(0,o.useRef)(null);m.current===null&&x&&(m.current=x);let z=(0,o.useMemo)(()=>{let N=m.current?`lang=${m.current}`:"";if(g==="config")return`${$}/ui/config?tab=config${N?`&${N}`:""}`;let P=k.replace(/\/$/,""),T=P.endsWith("/ui/app")?"?":"/?";return g?`${P}${T}tab=${g}${N?`&${N}`:""}`:N?`${P}${T}${N}`:k},[k,g]);return(0,o.useEffect)(()=>{let N=h.current&&h.current.contentWindow;N&&x&&N.postMessage({source:"memory-eternal",type:"locale",lang:x},"*")},[x]),(0,o.useEffect)(()=>{let N=P=>{P.key==="Escape"&&i()};return window.addEventListener("keydown",N),()=>window.removeEventListener("keydown",N)},[i]),(0,e.jsxs)("div",{className:"me-overlay-top",onClick:i,children:[(0,e.jsx)("style",{children:Ue}),(0,e.jsx)("div",{className:"me-modal"+(c?" me-modal-full":""),onClick:N=>N.stopPropagation(),children:(0,e.jsxs)("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0,position:"relative"},children:[(0,e.jsxs)("div",{style:{position:"absolute",top:8,right:8,zIndex:10,display:"flex",gap:4,padding:"4px 6px",background:"var(--dsw-alias-bg-overlay, rgba(17,24,39,0.45))",borderRadius:8,backdropFilter:"blur(4px)"},children:[(0,e.jsx)("button",{type:"button",onClick:()=>s(N=>!N),"aria-label":t(c?"exitFullscreen":"enterFullscreen"),title:t(c?"exitFullscreen":"enterFullscreen"),style:{width:26,height:26,borderRadius:6,border:"1px solid rgba(255,255,255,0.18)",background:"rgba(255,255,255,0.08)",color:"#fff",cursor:"pointer",fontSize:14,lineHeight:1,display:"flex",alignItems:"center",justifyContent:"center"},children:c?"\u2921":"\u2922"}),(0,e.jsx)("button",{type:"button",onClick:i,"aria-label":t("close"),title:t("close"),style:{width:26,height:26,borderRadius:6,border:"1px solid rgba(255,255,255,0.18)",background:"rgba(255,255,255,0.08)",color:"#fff",cursor:"pointer",fontSize:16,lineHeight:1,display:"flex",alignItems:"center",justifyContent:"center"},children:"\xD7"})]}),(0,e.jsx)("iframe",{ref:h,src:z,title:"memory-eternal",allow:"clipboard-write; clipboard-read",style:{width:"100%",flex:1,border:0,borderRadius:"inherit",background:"var(--dsw-alias-bg-base, #fff)"}})]})})]})}function Ua(){return(0,e.jsxs)("svg",{viewBox:"0 0 24 24","aria-hidden":"true",children:[(0,e.jsx)("defs",{children:(0,e.jsxs)("linearGradient",{id:"mg-brain",x1:"0",y1:"0",x2:"1",y2:"1",children:[(0,e.jsx)("stop",{offset:"0%",stopColor:"#FFC46B"}),(0,e.jsx)("stop",{offset:"50%",stopColor:"#F97316"}),(0,e.jsx)("stop",{offset:"100%",stopColor:"#EA580C"})]})}),(0,e.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 0-6.4.1 4.1 4.1 0 0 0-2.7 5.9 4.1 4.1 0 0 0 .6 6.7A4.2 4.2 0 0 0 12 18.2Z"}),(0,e.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 1 6.4.1 4.1 4.1 0 0 1 2.7 5.9 4.1 4.1 0 0 1-.6 6.7A4.2 4.2 0 0 1 12 18.2Z"}),(0,e.jsx)("path",{d:"M12 9.2c-.8-1.4-1.7-2-3.1-2.2M12 9.2c.8-1.4 1.7-2 3.1-2.2M12 12.4c-1.2 1-3.2 1.6-5 1.4M12 12.4c1.2 1 3.2 1.6 5 1.4M12 13.2v4",fill:"none",stroke:"rgba(255,255,255,0.62)",strokeWidth:"1",strokeLinecap:"round"}),(0,e.jsx)("circle",{cx:"12",cy:"8.6",r:"1.1",fill:"rgba(255,255,255,0.85)"})]})}var St="0.12.0";function Ga({t,inModal:a,onClose:i}){let g=typeof window<"u"&&(!!window.__DSH_BOOT__||String(location.pathname||"").includes("/memory-eternal/api/ui/app")),[c,s]=(0,o.useState)(null),[h,k]=(0,o.useState)([]),[S,L]=(0,o.useState)(!0),[M,x]=(0,o.useState)(null),[m,z]=(0,o.useState)(""),[N,P]=(0,o.useState)("all"),[T,E]=(0,o.useState)(""),[K,se]=(0,o.useState)("all"),[V,Z]=(0,o.useState)("all"),[te,G]=(0,o.useState)(()=>{try{return new Set(JSON.parse(localStorage.getItem("me-pinned-v5")||"[]"))}catch{return new Set}}),[fe,ue]=(0,o.useState)(!1),_=(0,o.useCallback)(b=>{G(r=>{let v=new Set(r);v.has(b)?v.delete(b):v.add(b);try{localStorage.setItem("me-pinned-v5",JSON.stringify([...v]))}catch{}return v})},[]),[Q,je]=(0,o.useState)(""),[O,Be]=(0,o.useState)(()=>{try{let b=new URLSearchParams(typeof location<"u"?location.search:"").get("tab"),r={cards:"cards",graph:"graph",usage:"stats",stats:"stats",audit:"audit",recycle:"optimize",optimize:"optimize",config:"config"};if(b&&r[b])return r[b]}catch{}return"cards"}),[f,F]=(0,o.useState)(()=>new Set([O])),W=(0,o.useCallback)(b=>{F(r=>r.has(b)?r:new Set(r).add(b)),Be(b)},[]),[B,Ce]=(0,o.useState)("recent"),[ze,pe]=(0,o.useState)(null),ve=(0,o.useRef)(null),y=(0,o.useRef)(null),[ae,X]=(0,o.useState)(!1),[Ne,ne]=(0,o.useState)(null),[xe,Ge]=(0,o.useState)(!1),[p,D]=(0,o.useState)(!1),[A,Y]=(0,o.useState)(!1),[Se,le]=(0,o.useState)("group"),[H,we]=(0,o.useState)(!1),be=(0,o.useCallback)(()=>{we(!1);try{localStorage.setItem("me-onboard-v2","1")}catch{}},[]),u=(0,o.useCallback)((b="group")=>{le(b),Y(!0)},[]);(0,o.useEffect)(()=>{try{if(localStorage.getItem("me-onboard-v2")||O!=="cards")return;let b=setTimeout(()=>we(!0),900);return()=>clearTimeout(b)}catch{}},[O]);let[d,J]=(0,o.useState)(!0),[he,Me]=(0,o.useState)(null),[Te,n]=(0,o.useState)(0),ot=(0,o.useCallback)(()=>n(b=>b+1),[]),ct=c?.quarantine?.pending??0,Ke=c?.quarantine?.deleted??0,Pe=(0,o.useRef)(null),[Xe,bt]=(0,o.useState)(0),[it,Re]=(0,o.useState)(!1),et=(0,o.useRef)(null),Fe=(0,o.useRef)(null),l=(0,o.useCallback)(async(b=N,r=T,v=V,C={})=>{let R=C.append===!0;try{R?Re(!0):(L(!0),z(""));let j=new URLSearchParams;b&&b!=="all"&&j.set("kind",b),r.trim()&&j.set("q",r.trim());let ee=C.tag!==void 0?C.tag:Q;ee&&j.set("tag",ee),j.set("status",v||"all"),j.set("limit",String(ja)),j.set("offset",String(C.offset||0)),B==="title"&&j.set("sort","title");let ge=await(await fetch(`${$}/cards?${j.toString()}`)).json();if(ge.ok){let Ae=ge.cards||[];k(De=>R?[...De,...Ae.filter(ye=>!De.some(ke=>ke.path===ye.path))]:Ae),bt(Number(ge.total)||Ae.length)}}catch(j){z(String(j&&j.message?j.message:j))}finally{R?Re(!1):L(!1)}},[N,T,V,B,Q]),I=(0,o.useCallback)(async()=>{try{let[b,r]=await Promise.all([fetch(`${$}/overview`).then(v=>v.json()),fetch(`${$}/capture-log`).then(v=>v.json()).catch(()=>null)]);b.ok&&s(b),x(r&&r.ok&&r.available&&r.health||null)}catch(b){z(String(b&&b.message?b.message:b))}finally{L(!1)}},[]),U=async b=>{try{let v=await(await fetch(`${$}/delete?path=${encodeURIComponent(b)}`)).json();v.ok?(pe({ok:!0,msg:t("deleted")}),await I(),ot()):pe({ok:!1,msg:v.error||t("deleteFail")})}catch{pe({ok:!1,msg:t("deleteFail")})}ve.current&&clearTimeout(ve.current),ve.current=setTimeout(()=>pe(null),2e3)};(0,o.useEffect)(()=>(I(),()=>{Pe.current&&clearTimeout(Pe.current)}),[I]);let oe=b=>{E(b),Pe.current&&clearTimeout(Pe.current),Pe.current=setTimeout(()=>l(N,b,V),280)},ie=b=>{P(b),l(b,T,V)},q=b=>{let r=String(b||"");je(r),ne(null),l(N,T,V,{tag:r})};(0,o.useEffect)(()=>{O==="cards"&&l(N,T,V)},[V,O,Te,B,Q]),(0,o.useEffect)(()=>{if(O!=="cards"||S||it||h.length>=Xe)return;let b=et.current,r=Fe.current;if(!b)return;let v=new IntersectionObserver(C=>{C[0]&&C[0].isIntersecting&&l(N,T,V,{append:!0,offset:h.length})},{root:r||null,rootMargin:"400px"});return v.observe(b),()=>v.disconnect()},[O,h,Xe,S,it,N,T,V,l]);let re=async b=>{try{let r=b.status;if(!r&&b.path)try{let ee=((await fetch(`${$}/cards?status=all&q=${encodeURIComponent(b.title||"")}&limit=50`).then(de=>de.json())).cards||[]).find(de=>de.path===b.path);ee&&(r=ee.status)}catch{}let v=r&&r!=="approved"?`&status=${encodeURIComponent(r)}`:"",C=await fetch(`${$}/card?path=${encodeURIComponent(b.path||b.id)}${v}`),R=await C.json();R.ok?ne({path:R.path,title:b.title,text:R.text}):z(R.error||`HTTP ${C.status}`)}catch(r){z(String(r&&r.message?r.message:r))}},He=async(b,r)=>{X(!0);try{let v=await fetch(`${$}/export`);if(!v.ok)throw new Error("HTTP "+v.status);let C=await v.json();if(!C.ok)throw new Error(C.error||"export failed");let R,j,ee;if(b==="json"){j="memory-vault.json",ee="application/json";let Ae=C.cards.map(De=>({path:De.path,title:De.title,kind:De.kind,status:De.status,store:De.store,text:De.text}));R=JSON.stringify({format:C.format||"memory-eternal-vault",formatVersion:C.formatVersion||1,exportedAt:C.exportedAt||new Date().toISOString(),count:Ae.length,cards:Ae},null,2)}else j="memory-vault.md",ee="text/markdown;charset=utf-8",R=C.cards.map(Ae=>Ae.text.trim()).filter(Boolean).join(`
704
+ `;function Ea(t){t.effect(()=>t.locale.register(kt,{zh:da,en:ca}),"memory-eternal: locale");let a=t.locale.bind(kt);t.effect(()=>t.slots.inject("settings.section",()=>t.slots.register({name:"settings.section",id:kt,order:25,label:()=>a("nav"),locale:kt,inject:()=>({})},()=>o.default.createElement(Wa,{t:a}))),"memory-eternal: settings section"),t.effect(()=>t.slots.inject("sidebar.footer.action",()=>t.slots.register({name:"sidebar.footer.action",id:`${kt}:footer`,order:100,label:()=>a("nav"),locale:kt,inject:()=>({})},i=>o.default.createElement($a,{t:a,locale:t.locale,wide:!(i&&i.wide===!1)}))),"memory-eternal: sidebar footer action")}function Oa(){let[t,a]=(0,o.useState)($+"/ui/app");return(0,o.useEffect)(()=>{let i=!0,g=()=>fetch("/memory-eternal/api/web-info").then(c=>c.json()).then(c=>{i&&c&&c.ok&&c.alive!==!1&&c.url&&a(c.url.endsWith("/")?c.url:c.url+"/")}).catch(()=>{});return fetch($+"/ui/app",{method:"HEAD"}).then(c=>{if(i&&!c.ok)return g()}).catch(()=>i?g():void 0),()=>{i=!1}},[]),t}function Wa({t}){return(0,e.jsxs)("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0},children:[(0,e.jsx)("style",{children:Ue}),(0,e.jsx)(ua,{t,onReload:()=>{},version:0})]})}function $a({t,locale:a,wide:i}){let[g,c]=(0,o.useState)(!1);return(0,e.jsxs)("div",{className:`me-footer${i?"":" rail"}`,children:[(0,e.jsx)("style",{children:Ue}),(0,e.jsxs)("button",{type:"button",className:"me-footer-btn",onClick:()=>c(!0),"aria-label":t("nav"),title:t("nav"),children:[(0,e.jsx)("span",{className:"me-footer-ico","aria-hidden":"true",children:(0,e.jsx)(Ua,{})}),(0,e.jsx)("span",{className:"me-footer-label",children:t("nav")})]}),g&&(0,e.jsx)(_a,{t,locale:a,onClose:()=>c(!1)})]})}function _a({t,locale:a,onClose:i,tab:g}){let[c,s]=(0,o.useState)(!1),h=(0,o.useRef)(null),k=Oa(),S=(0,o.useCallback)(N=>a&&typeof a.subscribe=="function"?a.subscribe(N):()=>{},[a]),L=(0,o.useCallback)(()=>a&&typeof a.getSnapshot=="function"?a.getSnapshot():null,[a]),M=(0,o.useSyncExternalStore)(S,L),x=M&&(M.active==="zh"||M.active==="en")?M.active:null,m=(0,o.useRef)(null);m.current===null&&x&&(m.current=x);let z=(0,o.useMemo)(()=>{let N=m.current?`lang=${m.current}`:"";if(g==="config")return`${$}/ui/config?tab=config${N?`&${N}`:""}`;let P=k.replace(/\/$/,""),T=P.endsWith("/ui/app")?"?":"/?";return g?`${P}${T}tab=${g}${N?`&${N}`:""}`:N?`${P}${T}${N}`:k},[k,g]);return(0,o.useEffect)(()=>{let N=h.current&&h.current.contentWindow;N&&x&&N.postMessage({source:"memory-eternal",type:"locale",lang:x},"*")},[x]),(0,o.useEffect)(()=>{let N=P=>{P.key==="Escape"&&i()};return window.addEventListener("keydown",N),()=>window.removeEventListener("keydown",N)},[i]),(0,e.jsxs)("div",{className:"me-overlay-top",onClick:i,children:[(0,e.jsx)("style",{children:Ue}),(0,e.jsx)("div",{className:"me-modal"+(c?" me-modal-full":""),onClick:N=>N.stopPropagation(),children:(0,e.jsxs)("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0,position:"relative"},children:[(0,e.jsxs)("div",{style:{position:"absolute",top:8,right:8,zIndex:10,display:"flex",gap:4,padding:"4px 6px",background:"var(--dsw-alias-bg-overlay, rgba(17,24,39,0.45))",borderRadius:8,backdropFilter:"blur(4px)"},children:[(0,e.jsx)("button",{type:"button",onClick:()=>s(N=>!N),"aria-label":t(c?"exitFullscreen":"enterFullscreen"),title:t(c?"exitFullscreen":"enterFullscreen"),style:{width:26,height:26,borderRadius:6,border:"1px solid rgba(255,255,255,0.18)",background:"rgba(255,255,255,0.08)",color:"#fff",cursor:"pointer",fontSize:14,lineHeight:1,display:"flex",alignItems:"center",justifyContent:"center"},children:c?"\u2921":"\u2922"}),(0,e.jsx)("button",{type:"button",onClick:i,"aria-label":t("close"),title:t("close"),style:{width:26,height:26,borderRadius:6,border:"1px solid rgba(255,255,255,0.18)",background:"rgba(255,255,255,0.08)",color:"#fff",cursor:"pointer",fontSize:16,lineHeight:1,display:"flex",alignItems:"center",justifyContent:"center"},children:"\xD7"})]}),(0,e.jsx)("iframe",{ref:h,src:z,title:"memory-eternal",allow:"clipboard-write; clipboard-read",style:{width:"100%",flex:1,border:0,borderRadius:"inherit",background:"var(--dsw-alias-bg-base, #fff)"}})]})})]})}function Ua(){return(0,e.jsxs)("svg",{viewBox:"0 0 24 24","aria-hidden":"true",children:[(0,e.jsx)("defs",{children:(0,e.jsxs)("linearGradient",{id:"mg-brain",x1:"0",y1:"0",x2:"1",y2:"1",children:[(0,e.jsx)("stop",{offset:"0%",stopColor:"#FFC46B"}),(0,e.jsx)("stop",{offset:"50%",stopColor:"#F97316"}),(0,e.jsx)("stop",{offset:"100%",stopColor:"#EA580C"})]})}),(0,e.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 0-6.4.1 4.1 4.1 0 0 0-2.7 5.9 4.1 4.1 0 0 0 .6 6.7A4.2 4.2 0 0 0 12 18.2Z"}),(0,e.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 1 6.4.1 4.1 4.1 0 0 1 2.7 5.9 4.1 4.1 0 0 1-.6 6.7A4.2 4.2 0 0 1 12 18.2Z"}),(0,e.jsx)("path",{d:"M12 9.2c-.8-1.4-1.7-2-3.1-2.2M12 9.2c.8-1.4 1.7-2 3.1-2.2M12 12.4c-1.2 1-3.2 1.6-5 1.4M12 12.4c1.2 1 3.2 1.6 5 1.4M12 13.2v4",fill:"none",stroke:"rgba(255,255,255,0.62)",strokeWidth:"1",strokeLinecap:"round"}),(0,e.jsx)("circle",{cx:"12",cy:"8.6",r:"1.1",fill:"rgba(255,255,255,0.85)"})]})}var St="0.13.0";function Ga({t,inModal:a,onClose:i}){let g=typeof window<"u"&&(!!window.__DSH_BOOT__||String(location.pathname||"").includes("/memory-eternal/api/ui/app")),[c,s]=(0,o.useState)(null),[h,k]=(0,o.useState)([]),[S,L]=(0,o.useState)(!0),[M,x]=(0,o.useState)(null),[m,z]=(0,o.useState)(""),[N,P]=(0,o.useState)("all"),[T,E]=(0,o.useState)(""),[K,se]=(0,o.useState)("all"),[V,Z]=(0,o.useState)("all"),[te,G]=(0,o.useState)(()=>{try{return new Set(JSON.parse(localStorage.getItem("me-pinned-v5")||"[]"))}catch{return new Set}}),[fe,ue]=(0,o.useState)(!1),_=(0,o.useCallback)(b=>{G(r=>{let v=new Set(r);v.has(b)?v.delete(b):v.add(b);try{localStorage.setItem("me-pinned-v5",JSON.stringify([...v]))}catch{}return v})},[]),[Q,je]=(0,o.useState)(""),[O,Be]=(0,o.useState)(()=>{try{let b=new URLSearchParams(typeof location<"u"?location.search:"").get("tab"),r={cards:"cards",graph:"graph",usage:"stats",stats:"stats",audit:"audit",recycle:"optimize",optimize:"optimize",config:"config"};if(b&&r[b])return r[b]}catch{}return"cards"}),[f,F]=(0,o.useState)(()=>new Set([O])),W=(0,o.useCallback)(b=>{F(r=>r.has(b)?r:new Set(r).add(b)),Be(b)},[]),[B,Ce]=(0,o.useState)("recent"),[ze,pe]=(0,o.useState)(null),ve=(0,o.useRef)(null),y=(0,o.useRef)(null),[ae,X]=(0,o.useState)(!1),[Ne,ne]=(0,o.useState)(null),[xe,Ge]=(0,o.useState)(!1),[p,D]=(0,o.useState)(!1),[A,Y]=(0,o.useState)(!1),[Se,le]=(0,o.useState)("group"),[H,we]=(0,o.useState)(!1),be=(0,o.useCallback)(()=>{we(!1);try{localStorage.setItem("me-onboard-v2","1")}catch{}},[]),u=(0,o.useCallback)((b="group")=>{le(b),Y(!0)},[]);(0,o.useEffect)(()=>{try{if(localStorage.getItem("me-onboard-v2")||O!=="cards")return;let b=setTimeout(()=>we(!0),900);return()=>clearTimeout(b)}catch{}},[O]);let[d,J]=(0,o.useState)(!0),[he,Me]=(0,o.useState)(null),[Te,n]=(0,o.useState)(0),ot=(0,o.useCallback)(()=>n(b=>b+1),[]),ct=c?.quarantine?.pending??0,Ke=c?.quarantine?.deleted??0,Pe=(0,o.useRef)(null),[Xe,bt]=(0,o.useState)(0),[it,Re]=(0,o.useState)(!1),et=(0,o.useRef)(null),Fe=(0,o.useRef)(null),l=(0,o.useCallback)(async(b=N,r=T,v=V,C={})=>{let R=C.append===!0;try{R?Re(!0):(L(!0),z(""));let j=new URLSearchParams;b&&b!=="all"&&j.set("kind",b),r.trim()&&j.set("q",r.trim());let ee=C.tag!==void 0?C.tag:Q;ee&&j.set("tag",ee),j.set("status",v||"all"),j.set("limit",String(ja)),j.set("offset",String(C.offset||0)),B==="title"&&j.set("sort","title");let ge=await(await fetch(`${$}/cards?${j.toString()}`)).json();if(ge.ok){let Ae=ge.cards||[];k(De=>R?[...De,...Ae.filter(ye=>!De.some(ke=>ke.path===ye.path))]:Ae),bt(Number(ge.total)||Ae.length)}}catch(j){z(String(j&&j.message?j.message:j))}finally{R?Re(!1):L(!1)}},[N,T,V,B,Q]),I=(0,o.useCallback)(async()=>{try{let[b,r]=await Promise.all([fetch(`${$}/overview`).then(v=>v.json()),fetch(`${$}/capture-log`).then(v=>v.json()).catch(()=>null)]);b.ok&&s(b),x(r&&r.ok&&r.available&&r.health||null)}catch(b){z(String(b&&b.message?b.message:b))}finally{L(!1)}},[]),U=async b=>{try{let v=await(await fetch(`${$}/delete?path=${encodeURIComponent(b)}`)).json();v.ok?(pe({ok:!0,msg:t("deleted")}),await I(),ot()):pe({ok:!1,msg:v.error||t("deleteFail")})}catch{pe({ok:!1,msg:t("deleteFail")})}ve.current&&clearTimeout(ve.current),ve.current=setTimeout(()=>pe(null),2e3)};(0,o.useEffect)(()=>(I(),()=>{Pe.current&&clearTimeout(Pe.current)}),[I]);let oe=b=>{E(b),Pe.current&&clearTimeout(Pe.current),Pe.current=setTimeout(()=>l(N,b,V),280)},ie=b=>{P(b),l(b,T,V)},q=b=>{let r=String(b||"");je(r),ne(null),l(N,T,V,{tag:r})};(0,o.useEffect)(()=>{O==="cards"&&l(N,T,V)},[V,O,Te,B,Q]),(0,o.useEffect)(()=>{if(O!=="cards"||S||it||h.length>=Xe)return;let b=et.current,r=Fe.current;if(!b)return;let v=new IntersectionObserver(C=>{C[0]&&C[0].isIntersecting&&l(N,T,V,{append:!0,offset:h.length})},{root:r||null,rootMargin:"400px"});return v.observe(b),()=>v.disconnect()},[O,h,Xe,S,it,N,T,V,l]);let re=async b=>{try{let r=b.status;if(!r&&b.path)try{let ee=((await fetch(`${$}/cards?status=all&q=${encodeURIComponent(b.title||"")}&limit=50`).then(de=>de.json())).cards||[]).find(de=>de.path===b.path);ee&&(r=ee.status)}catch{}let v=r&&r!=="approved"?`&status=${encodeURIComponent(r)}`:"",C=await fetch(`${$}/card?path=${encodeURIComponent(b.path||b.id)}${v}`),R=await C.json();R.ok?ne({path:R.path,title:b.title,text:R.text}):z(R.error||`HTTP ${C.status}`)}catch(r){z(String(r&&r.message?r.message:r))}},He=async(b,r)=>{X(!0);try{let v=await fetch(`${$}/export`);if(!v.ok)throw new Error("HTTP "+v.status);let C=await v.json();if(!C.ok)throw new Error(C.error||"export failed");let R,j,ee;if(b==="json"){j="memory-vault.json",ee="application/json";let Ae=C.cards.map(De=>({path:De.path,title:De.title,kind:De.kind,status:De.status,store:De.store,text:De.text}));R=JSON.stringify({format:C.format||"memory-eternal-vault",formatVersion:C.formatVersion||1,exportedAt:C.exportedAt||new Date().toISOString(),count:Ae.length,cards:Ae},null,2)}else j="memory-vault.md",ee="text/markdown;charset=utf-8",R=C.cards.map(Ae=>Ae.text.trim()).filter(Boolean).join(`
705
705
 
706
706
  ---
707
707
 
@@ -4,6 +4,18 @@
4
4
 
5
5
  ## 安装
6
6
 
7
+ ### 推荐:Git URL
8
+
9
+ Orca → Settings → Plugins → Install plugin → **Git URL**:
10
+
11
+ ```text
12
+ https://github.com/EternalNight996/memory-eternal-orca#v0.12.1
13
+ ```
14
+
15
+ Orca 会固定到 `v0.12.0` tag。
16
+
17
+ ### 本地目录
18
+
7
19
  1. 确认本机已安装并运行 `memory-eternal`(默认 Web 服务 `http://127.0.0.1:7999/`)。
8
20
  2. 确认终端能找到 CLI:
9
21
 
@@ -3,7 +3,7 @@
3
3
  "id": "memory-eternal",
4
4
  "publisher": "eternalnight996",
5
5
  "name": "memory-eternal",
6
- "version": "0.12.0",
6
+ "version": "0.13.0",
7
7
  "description": "Memory Atlas for Orca: open the local memory vault, pending review and knowledge graph from a sandboxed Orca panel.",
8
8
  "author": {
9
9
  "name": "EternalNight996",
@@ -64,6 +64,7 @@
64
64
  }
65
65
  button:hover { filter: brightness(1.06); }
66
66
  button:disabled { opacity: .45; cursor: default; }
67
+ button.secondary { margin-top: 7px; background: transparent; color: var(--foreground, #f3f5f8); border-color: var(--border, #3a404a); }
67
68
  .status { min-height: 1.6em; margin: 10px 0 0; font-size: 10.5px; color: var(--muted-foreground, #99a1ac); }
68
69
  .fallback { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border, #3a404a); font-size: 10.5px; color: var(--muted-foreground, #99a1ac); }
69
70
  .fallback code {
@@ -98,11 +99,12 @@
98
99
  <select id="terminal"><option value="">正在读取工作区…</option></select>
99
100
  </div>
100
101
 
101
- <button id="open" disabled>打开 Memory Atlas</button>
102
+ <button id="openOrca" disabled>在 Orca 浏览器打开</button>
103
+ <button id="openBrowser" class="secondary" disabled>外部浏览器打开</button>
102
104
  <p id="status" class="status"></p>
103
105
 
104
106
  <div class="fallback">
105
- 如果按钮无响应,在任意终端执行:
107
+ “在 Orca 浏览器打开”会执行 <code>orca tab create</code>,需要 <code>orca</code> CLI 在 PATH;失败时用外部浏览器:
106
108
  <code>dsh-memory open</code>
107
109
  <br/>Web 地址:<code>http://127.0.0.1:7999/</code>
108
110
  </div>
@@ -115,7 +117,8 @@
115
117
  var pending = new Map()
116
118
  var statusEl = document.getElementById('status')
117
119
  var terminalEl = document.getElementById('terminal')
118
- var openBtn = document.getElementById('open')
120
+ var openOrcaBtn = document.getElementById('openOrca')
121
+ var openBrowserBtn = document.getElementById('openBrowser')
119
122
 
120
123
  function setStatus(text, cls) {
121
124
  statusEl.textContent = text || ''
@@ -155,8 +158,9 @@
155
158
  opt.value = ''
156
159
  opt.textContent = '请先打开一个 Orca 终端'
157
160
  terminalEl.appendChild(opt)
158
- openBtn.disabled = true
159
- setStatus('需要一个终端来执行 dsh-memory open。', 'warn')
161
+ openOrcaBtn.disabled = true
162
+ openBrowserBtn.disabled = true
163
+ setStatus('需要一个终端来打开 Memory Atlas。', 'warn')
160
164
  return
161
165
  }
162
166
  terminals.forEach(function (term, index) {
@@ -165,33 +169,44 @@
165
169
  opt.textContent = '终端 ' + (index + 1)
166
170
  terminalEl.appendChild(opt)
167
171
  })
168
- openBtn.disabled = false
172
+ openOrcaBtn.disabled = false
173
+ openBrowserBtn.disabled = false
169
174
  setStatus('已就绪。')
170
175
  } catch (err) {
171
176
  setStatus('无法读取工作区终端:' + err.message, 'warn')
172
177
  }
173
178
  }
174
179
 
175
- openBtn.addEventListener('click', async function () {
180
+ async function sendCommand(text, okText) {
176
181
  var terminalId = terminalEl.value
177
182
  if (!terminalId) {
178
183
  setStatus('请先选择一个终端。', 'warn')
179
184
  return
180
185
  }
181
- openBtn.disabled = true
182
- setStatus('正在拉起 Memory Atlas…')
186
+ openOrcaBtn.disabled = true
187
+ openBrowserBtn.disabled = true
188
+ setStatus('正在打开 Memory Atlas…')
183
189
  try {
184
190
  await call('terminal.sendText', {
185
191
  terminalId: terminalId,
186
- text: 'dsh-memory open',
192
+ text: text,
187
193
  enter: true
188
194
  })
189
- setStatus('已发送 dsh-memory open。浏览器/WebView 打开后即可使用。', 'ok')
195
+ setStatus(okText, 'ok')
190
196
  } catch (err) {
191
197
  setStatus('打开失败:' + err.message + '。请在终端手动执行 dsh-memory open。', 'warn')
192
198
  } finally {
193
- openBtn.disabled = false
199
+ openOrcaBtn.disabled = false
200
+ openBrowserBtn.disabled = false
194
201
  }
202
+ }
203
+
204
+ openOrcaBtn.addEventListener('click', function () {
205
+ sendCommand('orca tab create --url http://127.0.0.1:7999/', '已发送 orca tab create。若 Orca 内置浏览器打开,即可在 Orca 内使用 Memory Atlas。')
206
+ })
207
+
208
+ openBrowserBtn.addEventListener('click', function () {
209
+ sendCommand('dsh-memory open', '已发送 dsh-memory open。浏览器打开后即可使用 Memory Atlas。')
195
210
  })
196
211
 
197
212
  loadTerminals()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-eternal",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "记忆核心(Memory Eternal):自研的 DeepSeek Harness 记忆插件,不移植任何既有记忆框架——对话结束后自动沉淀知识卡到本地 Markdown Vault(自研去重、自研 CJK 检索、可 git 管理),设置页提供图形化知识库(统计 / 搜索 / 知识图谱 + 侧边栏一键弹窗),Agent 通过 memory_recall 工具按需召回历史上下文。零人工干预。",
5
5
  "icon": "assets/icon.png",
6
6
  "license": "MIT",
package/web/app.js CHANGED
@@ -703,7 +703,7 @@ Option A, because:
703
703
  .mc-audit-batchcard > .mc-btn { width: 100%; }
704
704
  .mc-audit-batchcard > div[style*="flex: 1"] { display: none; }
705
705
  }
706
- `;var ro="0.12.0";function mf({t:e,inModal:t,onClose:n}){let r=typeof window<"u"&&(!!window.__DSH_BOOT__||String(location.pathname||"").includes("/memory-eternal/api/ui/app")),[a,i]=(0,g.useState)(null),[l,s]=(0,g.useState)([]),[d,p]=(0,g.useState)(!0),[v,h]=(0,g.useState)(null),[u,S]=(0,g.useState)(""),[N,M]=(0,g.useState)("all"),[_,f]=(0,g.useState)(""),[c,y]=(0,g.useState)("all"),[C,L]=(0,g.useState)("all"),[j,P]=(0,g.useState)(()=>{try{return new Set(JSON.parse(localStorage.getItem("me-pinned-v5")||"[]"))}catch{return new Set}}),[H,ne]=(0,g.useState)(!1),B=(0,g.useCallback)(E=>{P(x=>{let D=new Set(x);D.has(E)?D.delete(E):D.add(E);try{localStorage.setItem("me-pinned-v5",JSON.stringify([...D]))}catch{}return D})},[]),[J,Ve]=(0,g.useState)(""),[X,Ye]=(0,g.useState)(()=>{try{let E=new URLSearchParams(typeof location<"u"?location.search:"").get("tab"),x={cards:"cards",graph:"graph",usage:"stats",stats:"stats",audit:"audit",recycle:"optimize",optimize:"optimize",config:"config"};if(E&&x[E])return x[E]}catch{}return"cards"}),[T,Y]=(0,g.useState)(()=>new Set([X])),q=(0,g.useCallback)(E=>{Y(x=>x.has(E)?x:new Set(x).add(E)),Ye(E)},[]),[U,De]=(0,g.useState)("recent"),[Be,ye]=(0,g.useState)(null),Ce=(0,g.useRef)(null),R=(0,g.useRef)(null),[le,ae]=(0,g.useState)(!1),[_e,se]=(0,g.useState)(null),[Ne,kt]=(0,g.useState)(!1),[k,W]=(0,g.useState)(!1),[$,re]=(0,g.useState)(!1),[Ee,fe]=(0,g.useState)("group"),[Q,ze]=(0,g.useState)(!1),be=(0,g.useCallback)(()=>{ze(!1);try{localStorage.setItem("me-onboard-v2","1")}catch{}},[]),z=(0,g.useCallback)((E="group")=>{fe(E),re(!0)},[]);(0,g.useEffect)(()=>{try{if(localStorage.getItem("me-onboard-v2")||X!=="cards")return;let E=setTimeout(()=>ze(!0),900);return()=>clearTimeout(E)}catch{}},[X]);let[w,oe]=(0,g.useState)(!0),[ke,Ie]=(0,g.useState)(null),[je,m]=(0,g.useState)(0),gn=(0,g.useCallback)(()=>m(E=>E+1),[]),Ln=a?.quarantine?.pending??0,St=a?.quarantine?.deleted??0,et=(0,g.useRef)(null),[Gt,wr]=(0,g.useState)(0),[hn,Fe]=(0,g.useState)(!1),on=(0,g.useRef)(null),tt=(0,g.useRef)(null),b=(0,g.useCallback)(async(E=N,x=_,D=C,F={})=>{let O=F.append===!0;try{O?Fe(!0):(p(!0),S(""));let K=new URLSearchParams;E&&E!=="all"&&K.set("kind",E),x.trim()&&K.set("q",x.trim());let ie=F.tag!==void 0?F.tag:J;ie&&K.set("tag",ie),K.set("status",D||"all"),K.set("limit",String(ah)),K.set("offset",String(F.offset||0)),U==="title"&&K.set("sort","title");let xe=await(await fetch(`${ee}/cards?${K.toString()}`)).json();if(xe.ok){let Qe=xe.cards||[];s(Ue=>O?[...Ue,...Qe.filter(Se=>!Ue.some(Te=>Te.path===Se.path))]:Qe),wr(Number(xe.total)||Qe.length)}}catch(K){S(String(K&&K.message?K.message:K))}finally{O?Fe(!1):p(!1)}},[N,_,C,U,J]),V=(0,g.useCallback)(async()=>{try{let[E,x]=await Promise.all([fetch(`${ee}/overview`).then(D=>D.json()),fetch(`${ee}/capture-log`).then(D=>D.json()).catch(()=>null)]);E.ok&&i(E),h(x&&x.ok&&x.available&&x.health||null)}catch(E){S(String(E&&E.message?E.message:E))}finally{p(!1)}},[]),Z=async E=>{try{let D=await(await fetch(`${ee}/delete?path=${encodeURIComponent(E)}`)).json();D.ok?(ye({ok:!0,msg:e("deleted")}),await V(),gn()):ye({ok:!1,msg:D.error||e("deleteFail")})}catch{ye({ok:!1,msg:e("deleteFail")})}Ce.current&&clearTimeout(Ce.current),Ce.current=setTimeout(()=>ye(null),2e3)};(0,g.useEffect)(()=>(V(),()=>{et.current&&clearTimeout(et.current)}),[V]);let ce=E=>{f(E),et.current&&clearTimeout(et.current),et.current=setTimeout(()=>b(N,E,C),280)},ue=E=>{M(E),b(E,_,C)},te=E=>{let x=String(E||"");Ve(x),se(null),b(N,_,C,{tag:x})};(0,g.useEffect)(()=>{X==="cards"&&b(N,_,C)},[C,X,je,U,J]),(0,g.useEffect)(()=>{if(X!=="cards"||d||hn||l.length>=Gt)return;let E=on.current,x=tt.current;if(!E)return;let D=new IntersectionObserver(F=>{F[0]&&F[0].isIntersecting&&b(N,_,C,{append:!0,offset:l.length})},{root:x||null,rootMargin:"400px"});return D.observe(E),()=>D.disconnect()},[X,l,Gt,d,hn,N,_,C,b]);let pe=async E=>{try{let x=E.status;if(!x&&E.path)try{let ie=((await fetch(`${ee}/cards?status=all&q=${encodeURIComponent(E.title||"")}&limit=50`).then(me=>me.json())).cards||[]).find(me=>me.path===E.path);ie&&(x=ie.status)}catch{}let D=x&&x!=="approved"?`&status=${encodeURIComponent(x)}`:"",F=await fetch(`${ee}/card?path=${encodeURIComponent(E.path||E.id)}${D}`),O=await F.json();O.ok?se({path:O.path,title:E.title,text:O.text}):S(O.error||`HTTP ${F.status}`)}catch(x){S(String(x&&x.message?x.message:x))}},nt=async(E,x)=>{ae(!0);try{let D=await fetch(`${ee}/export`);if(!D.ok)throw new Error("HTTP "+D.status);let F=await D.json();if(!F.ok)throw new Error(F.error||"export failed");let O,K,ie;if(E==="json"){K="memory-vault.json",ie="application/json";let Qe=F.cards.map(Ue=>({path:Ue.path,title:Ue.title,kind:Ue.kind,status:Ue.status,store:Ue.store,text:Ue.text}));O=JSON.stringify({format:F.format||"memory-eternal-vault",formatVersion:F.formatVersion||1,exportedAt:F.exportedAt||new Date().toISOString(),count:Qe.length,cards:Qe},null,2)}else K="memory-vault.md",ie="text/markdown;charset=utf-8",O=F.cards.map(Qe=>Qe.text.trim()).filter(Boolean).join(`
706
+ `;var ro="0.13.0";function mf({t:e,inModal:t,onClose:n}){let r=typeof window<"u"&&(!!window.__DSH_BOOT__||String(location.pathname||"").includes("/memory-eternal/api/ui/app")),[a,i]=(0,g.useState)(null),[l,s]=(0,g.useState)([]),[d,p]=(0,g.useState)(!0),[v,h]=(0,g.useState)(null),[u,S]=(0,g.useState)(""),[N,M]=(0,g.useState)("all"),[_,f]=(0,g.useState)(""),[c,y]=(0,g.useState)("all"),[C,L]=(0,g.useState)("all"),[j,P]=(0,g.useState)(()=>{try{return new Set(JSON.parse(localStorage.getItem("me-pinned-v5")||"[]"))}catch{return new Set}}),[H,ne]=(0,g.useState)(!1),B=(0,g.useCallback)(E=>{P(x=>{let D=new Set(x);D.has(E)?D.delete(E):D.add(E);try{localStorage.setItem("me-pinned-v5",JSON.stringify([...D]))}catch{}return D})},[]),[J,Ve]=(0,g.useState)(""),[X,Ye]=(0,g.useState)(()=>{try{let E=new URLSearchParams(typeof location<"u"?location.search:"").get("tab"),x={cards:"cards",graph:"graph",usage:"stats",stats:"stats",audit:"audit",recycle:"optimize",optimize:"optimize",config:"config"};if(E&&x[E])return x[E]}catch{}return"cards"}),[T,Y]=(0,g.useState)(()=>new Set([X])),q=(0,g.useCallback)(E=>{Y(x=>x.has(E)?x:new Set(x).add(E)),Ye(E)},[]),[U,De]=(0,g.useState)("recent"),[Be,ye]=(0,g.useState)(null),Ce=(0,g.useRef)(null),R=(0,g.useRef)(null),[le,ae]=(0,g.useState)(!1),[_e,se]=(0,g.useState)(null),[Ne,kt]=(0,g.useState)(!1),[k,W]=(0,g.useState)(!1),[$,re]=(0,g.useState)(!1),[Ee,fe]=(0,g.useState)("group"),[Q,ze]=(0,g.useState)(!1),be=(0,g.useCallback)(()=>{ze(!1);try{localStorage.setItem("me-onboard-v2","1")}catch{}},[]),z=(0,g.useCallback)((E="group")=>{fe(E),re(!0)},[]);(0,g.useEffect)(()=>{try{if(localStorage.getItem("me-onboard-v2")||X!=="cards")return;let E=setTimeout(()=>ze(!0),900);return()=>clearTimeout(E)}catch{}},[X]);let[w,oe]=(0,g.useState)(!0),[ke,Ie]=(0,g.useState)(null),[je,m]=(0,g.useState)(0),gn=(0,g.useCallback)(()=>m(E=>E+1),[]),Ln=a?.quarantine?.pending??0,St=a?.quarantine?.deleted??0,et=(0,g.useRef)(null),[Gt,wr]=(0,g.useState)(0),[hn,Fe]=(0,g.useState)(!1),on=(0,g.useRef)(null),tt=(0,g.useRef)(null),b=(0,g.useCallback)(async(E=N,x=_,D=C,F={})=>{let O=F.append===!0;try{O?Fe(!0):(p(!0),S(""));let K=new URLSearchParams;E&&E!=="all"&&K.set("kind",E),x.trim()&&K.set("q",x.trim());let ie=F.tag!==void 0?F.tag:J;ie&&K.set("tag",ie),K.set("status",D||"all"),K.set("limit",String(ah)),K.set("offset",String(F.offset||0)),U==="title"&&K.set("sort","title");let xe=await(await fetch(`${ee}/cards?${K.toString()}`)).json();if(xe.ok){let Qe=xe.cards||[];s(Ue=>O?[...Ue,...Qe.filter(Se=>!Ue.some(Te=>Te.path===Se.path))]:Qe),wr(Number(xe.total)||Qe.length)}}catch(K){S(String(K&&K.message?K.message:K))}finally{O?Fe(!1):p(!1)}},[N,_,C,U,J]),V=(0,g.useCallback)(async()=>{try{let[E,x]=await Promise.all([fetch(`${ee}/overview`).then(D=>D.json()),fetch(`${ee}/capture-log`).then(D=>D.json()).catch(()=>null)]);E.ok&&i(E),h(x&&x.ok&&x.available&&x.health||null)}catch(E){S(String(E&&E.message?E.message:E))}finally{p(!1)}},[]),Z=async E=>{try{let D=await(await fetch(`${ee}/delete?path=${encodeURIComponent(E)}`)).json();D.ok?(ye({ok:!0,msg:e("deleted")}),await V(),gn()):ye({ok:!1,msg:D.error||e("deleteFail")})}catch{ye({ok:!1,msg:e("deleteFail")})}Ce.current&&clearTimeout(Ce.current),Ce.current=setTimeout(()=>ye(null),2e3)};(0,g.useEffect)(()=>(V(),()=>{et.current&&clearTimeout(et.current)}),[V]);let ce=E=>{f(E),et.current&&clearTimeout(et.current),et.current=setTimeout(()=>b(N,E,C),280)},ue=E=>{M(E),b(E,_,C)},te=E=>{let x=String(E||"");Ve(x),se(null),b(N,_,C,{tag:x})};(0,g.useEffect)(()=>{X==="cards"&&b(N,_,C)},[C,X,je,U,J]),(0,g.useEffect)(()=>{if(X!=="cards"||d||hn||l.length>=Gt)return;let E=on.current,x=tt.current;if(!E)return;let D=new IntersectionObserver(F=>{F[0]&&F[0].isIntersecting&&b(N,_,C,{append:!0,offset:l.length})},{root:x||null,rootMargin:"400px"});return D.observe(E),()=>D.disconnect()},[X,l,Gt,d,hn,N,_,C,b]);let pe=async E=>{try{let x=E.status;if(!x&&E.path)try{let ie=((await fetch(`${ee}/cards?status=all&q=${encodeURIComponent(E.title||"")}&limit=50`).then(me=>me.json())).cards||[]).find(me=>me.path===E.path);ie&&(x=ie.status)}catch{}let D=x&&x!=="approved"?`&status=${encodeURIComponent(x)}`:"",F=await fetch(`${ee}/card?path=${encodeURIComponent(E.path||E.id)}${D}`),O=await F.json();O.ok?se({path:O.path,title:E.title,text:O.text}):S(O.error||`HTTP ${F.status}`)}catch(x){S(String(x&&x.message?x.message:x))}},nt=async(E,x)=>{ae(!0);try{let D=await fetch(`${ee}/export`);if(!D.ok)throw new Error("HTTP "+D.status);let F=await D.json();if(!F.ok)throw new Error(F.error||"export failed");let O,K,ie;if(E==="json"){K="memory-vault.json",ie="application/json";let Qe=F.cards.map(Ue=>({path:Ue.path,title:Ue.title,kind:Ue.kind,status:Ue.status,store:Ue.store,text:Ue.text}));O=JSON.stringify({format:F.format||"memory-eternal-vault",formatVersion:F.formatVersion||1,exportedAt:F.exportedAt||new Date().toISOString(),count:Qe.length,cards:Qe},null,2)}else K="memory-vault.md",ie="text/markdown;charset=utf-8",O=F.cards.map(Qe=>Qe.text.trim()).filter(Boolean).join(`
707
707
 
708
708
  ---
709
709