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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.en.md +5 -4
- package/README.md +5 -4
- package/cursor-extension/package.json +1 -1
- package/dsh-tui-plugin/README.md +71 -0
- package/dsh-tui-plugin/demo-audit.mjs +535 -0
- package/dsh-tui-plugin/demo.mjs +743 -0
- package/dsh-tui-plugin/dsh-plugin.json +7 -2
- package/dsh-tui-plugin/main.js +43 -0
- package/dsh-tui-plugin/package.json +3 -1
- package/dsh-tui-plugin/scene.js +151 -0
- package/lib/client.js +1 -1
- package/orca-plugin/README.md +12 -0
- package/orca-plugin/orca-plugin.json +1 -1
- package/orca-plugin/panel.html +27 -12
- package/package.json +1 -1
- package/web/app.js +1 -1
|
@@ -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.
|
|
5
|
+
"version": "0.13.0",
|
|
6
6
|
"manifestVersion": "0.15",
|
|
7
|
-
"description": "Open
|
|
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
|
},
|
package/dsh-tui-plugin/main.js
CHANGED
|
@@ -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.
|
|
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
|
|
package/orca-plugin/README.md
CHANGED
|
@@ -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.
|
|
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",
|
package/orca-plugin/panel.html
CHANGED
|
@@ -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="
|
|
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
|
|
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
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
-
|
|
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:
|
|
192
|
+
text: text,
|
|
187
193
|
enter: true
|
|
188
194
|
})
|
|
189
|
-
setStatus(
|
|
195
|
+
setStatus(okText, 'ok')
|
|
190
196
|
} catch (err) {
|
|
191
197
|
setStatus('打开失败:' + err.message + '。请在终端手动执行 dsh-memory open。', 'warn')
|
|
192
198
|
} finally {
|
|
193
|
-
|
|
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.
|
|
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.
|
|
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
|
|