@memtensor/memos-local-openclaw-plugin 0.1.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/.env.example +11 -0
- package/README.md +251 -0
- package/SKILL.md +43 -0
- package/dist/capture/index.d.ts +16 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +80 -0
- package/dist/capture/index.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +96 -0
- package/dist/config.js.map +1 -0
- package/dist/embedding/index.d.ts +12 -0
- package/dist/embedding/index.d.ts.map +1 -0
- package/dist/embedding/index.js +75 -0
- package/dist/embedding/index.js.map +1 -0
- package/dist/embedding/local.d.ts +3 -0
- package/dist/embedding/local.d.ts.map +1 -0
- package/dist/embedding/local.js +65 -0
- package/dist/embedding/local.js.map +1 -0
- package/dist/embedding/providers/cohere.d.ts +4 -0
- package/dist/embedding/providers/cohere.d.ts.map +1 -0
- package/dist/embedding/providers/cohere.js +57 -0
- package/dist/embedding/providers/cohere.js.map +1 -0
- package/dist/embedding/providers/gemini.d.ts +3 -0
- package/dist/embedding/providers/gemini.d.ts.map +1 -0
- package/dist/embedding/providers/gemini.js +31 -0
- package/dist/embedding/providers/gemini.js.map +1 -0
- package/dist/embedding/providers/mistral.d.ts +3 -0
- package/dist/embedding/providers/mistral.d.ts.map +1 -0
- package/dist/embedding/providers/mistral.js +25 -0
- package/dist/embedding/providers/mistral.js.map +1 -0
- package/dist/embedding/providers/openai.d.ts +3 -0
- package/dist/embedding/providers/openai.d.ts.map +1 -0
- package/dist/embedding/providers/openai.js +35 -0
- package/dist/embedding/providers/openai.js.map +1 -0
- package/dist/embedding/providers/voyage.d.ts +3 -0
- package/dist/embedding/providers/voyage.d.ts.map +1 -0
- package/dist/embedding/providers/voyage.js +25 -0
- package/dist/embedding/providers/voyage.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +75 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest/chunker.d.ts +15 -0
- package/dist/ingest/chunker.d.ts.map +1 -0
- package/dist/ingest/chunker.js +193 -0
- package/dist/ingest/chunker.js.map +1 -0
- package/dist/ingest/dedup.d.ts +11 -0
- package/dist/ingest/dedup.d.ts.map +1 -0
- package/dist/ingest/dedup.js +29 -0
- package/dist/ingest/dedup.js.map +1 -0
- package/dist/ingest/providers/anthropic.d.ts +3 -0
- package/dist/ingest/providers/anthropic.d.ts.map +1 -0
- package/dist/ingest/providers/anthropic.js +33 -0
- package/dist/ingest/providers/anthropic.js.map +1 -0
- package/dist/ingest/providers/bedrock.d.ts +8 -0
- package/dist/ingest/providers/bedrock.d.ts.map +1 -0
- package/dist/ingest/providers/bedrock.js +41 -0
- package/dist/ingest/providers/bedrock.js.map +1 -0
- package/dist/ingest/providers/gemini.d.ts +3 -0
- package/dist/ingest/providers/gemini.d.ts.map +1 -0
- package/dist/ingest/providers/gemini.js +31 -0
- package/dist/ingest/providers/gemini.js.map +1 -0
- package/dist/ingest/providers/index.d.ts +9 -0
- package/dist/ingest/providers/index.d.ts.map +1 -0
- package/dist/ingest/providers/index.js +68 -0
- package/dist/ingest/providers/index.js.map +1 -0
- package/dist/ingest/providers/openai.d.ts +3 -0
- package/dist/ingest/providers/openai.d.ts.map +1 -0
- package/dist/ingest/providers/openai.js +41 -0
- package/dist/ingest/providers/openai.js.map +1 -0
- package/dist/ingest/worker.d.ts +21 -0
- package/dist/ingest/worker.d.ts.map +1 -0
- package/dist/ingest/worker.js +111 -0
- package/dist/ingest/worker.js.map +1 -0
- package/dist/recall/engine.d.ts +23 -0
- package/dist/recall/engine.d.ts.map +1 -0
- package/dist/recall/engine.js +153 -0
- package/dist/recall/engine.js.map +1 -0
- package/dist/recall/mmr.d.ts +17 -0
- package/dist/recall/mmr.d.ts.map +1 -0
- package/dist/recall/mmr.js +51 -0
- package/dist/recall/mmr.js.map +1 -0
- package/dist/recall/recency.d.ts +20 -0
- package/dist/recall/recency.d.ts.map +1 -0
- package/dist/recall/recency.js +26 -0
- package/dist/recall/recency.js.map +1 -0
- package/dist/recall/rrf.d.ts +16 -0
- package/dist/recall/rrf.d.ts.map +1 -0
- package/dist/recall/rrf.js +15 -0
- package/dist/recall/rrf.js.map +1 -0
- package/dist/storage/sqlite.d.ts +34 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +274 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/storage/vector.d.ts +13 -0
- package/dist/storage/vector.d.ts.map +1 -0
- package/dist/storage/vector.js +33 -0
- package/dist/storage/vector.js.map +1 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +10 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/memory-get.d.ts +4 -0
- package/dist/tools/memory-get.d.ts.map +1 -0
- package/dist/tools/memory-get.js +59 -0
- package/dist/tools/memory-get.js.map +1 -0
- package/dist/tools/memory-search.d.ts +4 -0
- package/dist/tools/memory-search.d.ts.map +1 -0
- package/dist/tools/memory-search.js +36 -0
- package/dist/tools/memory-search.js.map +1 -0
- package/dist/tools/memory-timeline.d.ts +4 -0
- package/dist/tools/memory-timeline.d.ts.map +1 -0
- package/dist/tools/memory-timeline.js +64 -0
- package/dist/tools/memory-timeline.js.map +1 -0
- package/dist/types.d.ts +158 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +25 -0
- package/dist/types.js.map +1 -0
- package/dist/viewer/html.d.ts +2 -0
- package/dist/viewer/html.d.ts.map +1 -0
- package/dist/viewer/html.js +686 -0
- package/dist/viewer/html.js.map +1 -0
- package/dist/viewer/server.d.ts +48 -0
- package/dist/viewer/server.d.ts.map +1 -0
- package/dist/viewer/server.js +470 -0
- package/dist/viewer/server.js.map +1 -0
- package/index.ts +357 -0
- package/openclaw.plugin.json +57 -0
- package/package.json +57 -0
- package/src/capture/index.ts +92 -0
- package/src/config.ts +67 -0
- package/src/embedding/index.ts +76 -0
- package/src/embedding/local.ts +35 -0
- package/src/embedding/providers/cohere.ts +69 -0
- package/src/embedding/providers/gemini.ts +41 -0
- package/src/embedding/providers/mistral.ts +32 -0
- package/src/embedding/providers/openai.ts +42 -0
- package/src/embedding/providers/voyage.ts +32 -0
- package/src/index.ts +106 -0
- package/src/ingest/chunker.ts +217 -0
- package/src/ingest/dedup.ts +37 -0
- package/src/ingest/providers/anthropic.ts +41 -0
- package/src/ingest/providers/bedrock.ts +50 -0
- package/src/ingest/providers/gemini.ts +41 -0
- package/src/ingest/providers/index.ts +67 -0
- package/src/ingest/providers/openai.ts +48 -0
- package/src/ingest/worker.ts +130 -0
- package/src/recall/engine.ts +182 -0
- package/src/recall/mmr.ts +60 -0
- package/src/recall/recency.ts +27 -0
- package/src/recall/rrf.ts +31 -0
- package/src/storage/sqlite.ts +305 -0
- package/src/storage/vector.ts +39 -0
- package/src/tools/index.ts +3 -0
- package/src/tools/memory-get.ts +68 -0
- package/src/tools/memory-search.ts +36 -0
- package/src/tools/memory-timeline.ts +73 -0
- package/src/types.ts +214 -0
- package/src/viewer/html.ts +682 -0
- package/src/viewer/server.ts +464 -0
- package/www/index.html +606 -0
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
export const viewerHTML = `<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>OpenClaw Memory - Powered by MemOS</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
10
|
+
<style>
|
|
11
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
12
|
+
:root{
|
|
13
|
+
--bg:#050510;--bg-card:rgba(255,255,255,.04);--bg-card-hover:rgba(255,255,255,.07);
|
|
14
|
+
--border:rgba(255,255,255,.08);--border-glow:rgba(0,187,238,.25);
|
|
15
|
+
--text:#f0f4f8;--text-sec:#8b95a5;--text-muted:#5a6373;
|
|
16
|
+
--pri:#00bbee;--pri-glow:rgba(0,187,238,.15);--pri-dark:#0088aa;
|
|
17
|
+
--pri-grad:linear-gradient(135deg,#00bbee,#00a0cc);
|
|
18
|
+
--accent:#e63946;--accent-glow:rgba(230,57,70,.15);
|
|
19
|
+
--green:#10b981;--green-bg:rgba(16,185,129,.12);
|
|
20
|
+
--amber:#f59e0b;--amber-bg:rgba(245,158,11,.12);
|
|
21
|
+
--violet:#8b5cf6;--rose:#f43f5e;--rose-bg:rgba(244,63,94,.12);
|
|
22
|
+
--shadow-sm:0 1px 2px rgba(0,0,0,.2);--shadow:0 4px 12px rgba(0,0,0,.25);
|
|
23
|
+
--shadow-lg:0 20px 40px rgba(0,0,0,.35);
|
|
24
|
+
--radius:12px;--radius-lg:14px;--radius-xl:18px;
|
|
25
|
+
}
|
|
26
|
+
body{font-family:'Inter',-apple-system,BlinkMacSystemFont,sans-serif;background:var(--bg);color:var(--text);line-height:1.6}
|
|
27
|
+
button{cursor:pointer;font-family:inherit;font-size:inherit}
|
|
28
|
+
input,textarea,select{font-family:inherit;font-size:inherit}
|
|
29
|
+
|
|
30
|
+
/* ─── Auth (Linkify 配色: globals.css .dark + 蓝紫渐变) ─── */
|
|
31
|
+
.auth-screen{display:flex;align-items:center;justify-content:center;min-height:100vh;padding:20px;background:linear-gradient(135deg,rgb(36,0,255) 0%,rgb(0,135,255) 35%,rgb(108,39,157) 70%,rgb(105,30,255) 100%);position:relative;overflow:hidden}
|
|
32
|
+
.auth-card{background:hsl(0 0% 100%);border:none;border-radius:8px;padding:48px 40px;width:100%;max-width:420px;box-shadow:0 25px 50px -12px rgba(0,0,0,.25);text-align:center;position:relative;z-index:1}
|
|
33
|
+
.auth-card .logo{width:56px;height:56px;margin:0 auto 20px;display:flex;align-items:center;justify-content:center;font-size:48px;background:none;border-radius:0}
|
|
34
|
+
.auth-card h1{font-size:22px;font-weight:700;margin-bottom:4px;color:hsl(0 0% 3.9%);letter-spacing:-.02em}
|
|
35
|
+
.auth-card p{color:hsl(0 0% 45.1%);margin-bottom:24px;font-size:14px}
|
|
36
|
+
.auth-card input{width:100%;padding:12px 16px;border:1px solid hsl(0 0% 89.8%);border-radius:8px;font-size:14px;transition:all .2s;margin-bottom:10px;outline:none;background:#fff;color:hsl(0 0% 3.9%)}
|
|
37
|
+
.auth-card input::placeholder{color:hsl(0 0% 45.1%)}
|
|
38
|
+
.auth-card input:focus{border-color:rgb(168,85,247);box-shadow:0 0 0 3px rgba(168,85,247,.2)}
|
|
39
|
+
.auth-card .btn-auth{width:100%;padding:12px;border:none;border-radius:8px;background:hsl(0 0% 9%);color:hsl(0 0% 98%);font-weight:600;font-size:14px;transition:all .2s}
|
|
40
|
+
.auth-card .btn-auth:hover{background:hsl(0 0% 14%);transform:translateY(-1px);box-shadow:0 8px 25px rgba(0,0,0,.2)}
|
|
41
|
+
.auth-card .error-msg{color:hsl(0 84.2% 60.2%);font-size:13px;margin-top:8px;min-height:20px}
|
|
42
|
+
.auth-card .btn-text{color:hsl(0 0% 45.1%)}
|
|
43
|
+
.auth-card .btn-text:hover{color:rgb(168,85,247)}
|
|
44
|
+
|
|
45
|
+
.reset-guide{text-align:left;margin-bottom:20px}
|
|
46
|
+
.reset-step{display:flex;gap:14px;margin-bottom:16px}
|
|
47
|
+
.step-num{width:28px;height:28px;border-radius:50%;background:hsl(0 0% 9%);color:hsl(0 0% 98%);font-size:12px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
|
48
|
+
.step-body{flex:1;min-width:0}
|
|
49
|
+
.step-title{font-size:14px;font-weight:600;color:hsl(0 0% 3.9%);margin-bottom:2px}
|
|
50
|
+
.step-desc{font-size:13px;color:hsl(0 0% 45.1%);line-height:1.5}
|
|
51
|
+
.cmd-box{margin-top:8px;background:hsl(0 0% 96.1%);border:1px solid hsl(0 0% 89.8%);border-radius:8px;padding:12px 14px;font-size:12px;font-family:ui-monospace,monospace;cursor:pointer;transition:all .15s;display:flex;align-items:center;justify-content:space-between;gap:8px;word-break:break-all;color:hsl(0 0% 3.9%)}
|
|
52
|
+
.cmd-box:hover{border-color:rgb(168,85,247);background:rgba(168,85,247,.08)}
|
|
53
|
+
.cmd-box code{flex:1}
|
|
54
|
+
.copy-hint{font-size:11px;color:hsl(0 0% 45.1%);white-space:nowrap}
|
|
55
|
+
.cmd-box.copied .copy-hint{color:hsl(142 71% 45%)}
|
|
56
|
+
|
|
57
|
+
/* ─── App Layout (dark dashboard, same as www) ─── */
|
|
58
|
+
.app{display:none;flex-direction:column;min-height:100vh}
|
|
59
|
+
.topbar{background:rgba(5,5,16,.85);border-bottom:1px solid var(--border);padding:0 28px;height:64px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:100;backdrop-filter:blur(12px)}
|
|
60
|
+
.topbar .brand{display:flex;align-items:center;gap:12px;font-weight:700;font-size:17px;color:var(--text);letter-spacing:-.02em}
|
|
61
|
+
.topbar .brand .icon{width:38px;height:38px;display:flex;align-items:center;justify-content:center;font-size:26px;background:none;border-radius:0}
|
|
62
|
+
.topbar .actions{display:flex;align-items:center;gap:10px}
|
|
63
|
+
|
|
64
|
+
.main-content{display:flex;flex:1;max-width:1400px;margin:0 auto;width:100%;padding:28px 32px;gap:28px}
|
|
65
|
+
|
|
66
|
+
/* ─── Sidebar ─── */
|
|
67
|
+
.sidebar{width:260px;flex-shrink:0}
|
|
68
|
+
.sidebar .stats-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:24px}
|
|
69
|
+
.stat-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:18px;transition:all .2s}
|
|
70
|
+
.stat-card:hover{border-color:var(--border-glow);background:var(--bg-card-hover)}
|
|
71
|
+
.stat-card .stat-value{font-size:22px;font-weight:700;color:var(--text);letter-spacing:-.02em}
|
|
72
|
+
.stat-card .stat-label{font-size:12px;color:var(--text-sec);margin-top:4px;font-weight:500}
|
|
73
|
+
.stat-card.pri .stat-value{color:var(--pri)}
|
|
74
|
+
.stat-card.green .stat-value{color:var(--green)}
|
|
75
|
+
.stat-card.amber .stat-value{color:var(--amber)}
|
|
76
|
+
.stat-card.rose .stat-value{color:var(--rose)}
|
|
77
|
+
|
|
78
|
+
.sidebar .section-title{font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.08em;margin:24px 0 12px;padding:0 2px}
|
|
79
|
+
.sidebar .session-list{display:flex;flex-direction:column;gap:6px;max-height:280px;overflow-y:auto}
|
|
80
|
+
.session-item{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:var(--bg-card);border:1px solid var(--border);border-radius:10px;cursor:pointer;transition:all .15s;font-size:13px;color:var(--text)}
|
|
81
|
+
.session-item:hover{border-color:var(--pri);background:var(--pri-glow)}
|
|
82
|
+
.session-item.active{border-color:var(--pri);background:var(--pri-glow);font-weight:600;color:var(--pri)}
|
|
83
|
+
.session-item .count{color:var(--text-sec);font-size:11px;font-weight:600;background:rgba(0,0,0,.2);padding:3px 8px;border-radius:8px}
|
|
84
|
+
|
|
85
|
+
.provider-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--green-bg);color:var(--green);border-radius:999px;font-size:11px;font-weight:600;margin-top:10px}
|
|
86
|
+
.provider-badge.offline{background:var(--amber-bg);color:var(--amber)}
|
|
87
|
+
|
|
88
|
+
/* ─── Feed ─── */
|
|
89
|
+
.feed{flex:1;min-width:0}
|
|
90
|
+
.search-bar{display:flex;gap:12px;margin-bottom:16px;position:relative}
|
|
91
|
+
.search-bar input{flex:1;padding:12px 16px 12px 44px;border:1px solid var(--border);border-radius:12px;font-size:14px;outline:none;background:var(--bg-card);color:var(--text);transition:all .2s}
|
|
92
|
+
.search-bar input::placeholder{color:var(--text-muted)}
|
|
93
|
+
.search-bar input:focus{border-color:var(--pri);box-shadow:0 0 0 3px var(--pri-glow)}
|
|
94
|
+
.search-bar .search-icon{position:absolute;left:16px;top:50%;transform:translateY(-50%);color:var(--text-muted);font-size:15px;pointer-events:none}
|
|
95
|
+
.search-meta{font-size:12px;color:var(--text-sec);margin-bottom:14px;padding:0 2px}
|
|
96
|
+
|
|
97
|
+
.filter-bar{display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap}
|
|
98
|
+
.filter-chip{padding:6px 14px;border:1px solid var(--border);border-radius:999px;background:var(--bg-card);color:var(--text-sec);font-size:13px;font-weight:500;transition:all .15s}
|
|
99
|
+
.filter-chip:hover{border-color:var(--pri);color:var(--pri)}
|
|
100
|
+
.filter-chip.active{background:var(--pri);color:#000;border-color:var(--pri)}
|
|
101
|
+
|
|
102
|
+
.memory-list{display:flex;flex-direction:column;gap:16px}
|
|
103
|
+
.memory-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:20px 24px;transition:all .2s}
|
|
104
|
+
.memory-card:hover{border-color:var(--border-glow);background:var(--bg-card-hover)}
|
|
105
|
+
.memory-card .card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:wrap;gap:8px}
|
|
106
|
+
.memory-card .meta{display:flex;align-items:center;gap:8px}
|
|
107
|
+
.role-tag{padding:4px 10px;border-radius:8px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.03em}
|
|
108
|
+
.role-tag.user{background:var(--pri-glow);color:var(--pri);border:1px solid rgba(0,187,238,.2)}
|
|
109
|
+
.role-tag.assistant{background:var(--accent-glow);color:var(--accent);border:1px solid rgba(230,57,70,.2)}
|
|
110
|
+
.role-tag.system{background:var(--amber-bg);color:var(--amber);border:1px solid rgba(245,158,11,.2)}
|
|
111
|
+
.kind-tag{padding:4px 10px;border-radius:8px;font-size:11px;color:var(--text-sec);background:rgba(0,0,0,.2);font-weight:500}
|
|
112
|
+
.card-time{font-size:12px;color:var(--text-sec);display:flex;align-items:center;gap:8px}
|
|
113
|
+
.session-tag{font-size:11px;font-family:ui-monospace,monospace;color:var(--text-muted);background:rgba(0,0,0,.2);padding:3px 8px;border-radius:6px;cursor:default}
|
|
114
|
+
.card-summary{font-size:15px;font-weight:600;color:var(--text);margin-bottom:10px;line-height:1.5;letter-spacing:-.01em}
|
|
115
|
+
.card-content{font-size:13px;color:var(--text-sec);line-height:1.65;max-height:0;overflow:hidden;transition:max-height .3s ease}
|
|
116
|
+
.card-content.show{max-height:600px;overflow-y:auto}
|
|
117
|
+
.card-content pre{white-space:pre-wrap;word-break:break-all;background:rgba(0,0,0,.25);padding:14px;border-radius:10px;font-size:12px;font-family:ui-monospace,monospace;margin-top:10px;border:1px solid var(--border);color:var(--text-sec)}
|
|
118
|
+
.card-actions{display:flex;align-items:center;gap:8px;margin-top:14px}
|
|
119
|
+
.vscore-badge{display:inline-flex;align-items:center;background:linear-gradient(135deg,var(--pri),var(--violet));color:#fff;font-size:10px;font-weight:700;padding:4px 10px;border-radius:8px;margin-left:auto}
|
|
120
|
+
|
|
121
|
+
/* ─── Buttons ─── */
|
|
122
|
+
.btn{padding:8px 16px;border-radius:10px;border:1px solid var(--border);background:var(--bg-card);color:var(--text);font-size:13px;font-weight:500;transition:all .15s;display:inline-flex;align-items:center;gap:6px}
|
|
123
|
+
.btn:hover{border-color:var(--pri);color:var(--pri)}
|
|
124
|
+
.btn-primary{background:var(--pri);color:#000;border:none}
|
|
125
|
+
.btn-primary:hover{background:#4dd9ff;transform:translateY(-1px);box-shadow:0 6px 20px rgba(0,187,238,.25)}
|
|
126
|
+
.btn-danger{color:var(--accent);border-color:var(--accent)}
|
|
127
|
+
.btn-danger:hover{background:var(--accent);color:#fff;border-color:var(--accent)}
|
|
128
|
+
.btn-sm{padding:6px 12px;font-size:12px}
|
|
129
|
+
.btn-icon{padding:6px 8px;font-size:14px}
|
|
130
|
+
.btn-text{border:none;background:none;color:var(--text-sec);font-size:13px;padding:4px 8px}
|
|
131
|
+
.btn-text:hover{color:var(--pri)}
|
|
132
|
+
|
|
133
|
+
/* ─── Modal ─── */
|
|
134
|
+
.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:500;align-items:center;justify-content:center;backdrop-filter:blur(8px)}
|
|
135
|
+
.modal-overlay.show{display:flex}
|
|
136
|
+
.modal{background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-xl);padding:32px;width:100%;max-width:520px;box-shadow:var(--shadow-lg);max-height:85vh;overflow-y:auto}
|
|
137
|
+
.modal h2{font-size:20px;font-weight:700;margin-bottom:24px;color:var(--text);letter-spacing:-.02em}
|
|
138
|
+
.form-group{margin-bottom:18px}
|
|
139
|
+
.form-group label{display:block;font-size:13px;font-weight:600;color:var(--text-sec);margin-bottom:6px}
|
|
140
|
+
.form-group input,.form-group textarea,.form-group select{width:100%;padding:10px 14px;border:1px solid var(--border);border-radius:10px;font-size:14px;outline:none;transition:all .2s;background:var(--bg-card);color:var(--text)}
|
|
141
|
+
.form-group input::placeholder,.form-group textarea::placeholder{color:var(--text-muted)}
|
|
142
|
+
.form-group input:focus,.form-group textarea:focus,.form-group select:focus{border-color:var(--pri);box-shadow:0 0 0 3px var(--pri-glow)}
|
|
143
|
+
.form-group textarea{min-height:100px;resize:vertical}
|
|
144
|
+
.modal-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:28px}
|
|
145
|
+
|
|
146
|
+
/* ─── Toast ─── */
|
|
147
|
+
.toast-container{position:fixed;top:80px;right:24px;z-index:1000;display:flex;flex-direction:column;gap:8px}
|
|
148
|
+
.toast{padding:14px 20px;border-radius:10px;font-size:13px;font-weight:500;box-shadow:var(--shadow-lg);animation:slideIn .3s ease;display:flex;align-items:center;gap:10px;max-width:360px;border:1px solid}
|
|
149
|
+
.toast.success{background:var(--green-bg);color:var(--green);border-color:rgba(16,185,129,.3)}
|
|
150
|
+
.toast.error{background:var(--rose-bg);color:var(--rose);border-color:rgba(244,63,94,.3)}
|
|
151
|
+
.toast.info{background:var(--pri-glow);color:var(--pri);border-color:rgba(0,187,238,.3)}
|
|
152
|
+
@keyframes slideIn{from{transform:translateX(100px);opacity:0}to{transform:translateX(0);opacity:1}}
|
|
153
|
+
|
|
154
|
+
.empty{text-align:center;padding:64px 20px;color:var(--text-sec)}
|
|
155
|
+
.empty .icon{font-size:52px;margin-bottom:16px;opacity:.5}
|
|
156
|
+
.empty p{font-size:15px;font-weight:500}
|
|
157
|
+
|
|
158
|
+
.spinner{width:40px;height:40px;border:3px solid var(--border);border-top-color:var(--pri);border-radius:50%;animation:spin .8s linear infinite;margin:48px auto}
|
|
159
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
160
|
+
|
|
161
|
+
::-webkit-scrollbar{width:6px;height:6px}
|
|
162
|
+
::-webkit-scrollbar-track{background:transparent}
|
|
163
|
+
::-webkit-scrollbar-thumb{background:rgba(255,255,255,.15);border-radius:3px}
|
|
164
|
+
::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.25)}
|
|
165
|
+
|
|
166
|
+
.filter-sep{width:1px;height:20px;background:var(--border);margin:0 4px}
|
|
167
|
+
.filter-select{padding:6px 12px;border:1px solid var(--border);border-radius:999px;background:var(--bg-card);color:var(--text-sec);font-size:13px;outline:none;cursor:pointer}
|
|
168
|
+
.filter-select:focus{border-color:var(--pri)}
|
|
169
|
+
.date-filter{display:flex;align-items:center;gap:10px;margin-bottom:18px;font-size:13px;color:var(--text-sec)}
|
|
170
|
+
.date-filter input[type="datetime-local"]{padding:6px 10px;border:1px solid var(--border);border-radius:8px;font-size:12px;outline:none;background:var(--bg-card);color:var(--text)}
|
|
171
|
+
.date-filter input[type="datetime-local"]:focus{border-color:var(--pri)}
|
|
172
|
+
.date-filter label{font-weight:500}
|
|
173
|
+
|
|
174
|
+
.pagination{display:flex;align-items:center;justify-content:center;gap:6px;padding:28px 0;flex-wrap:wrap}
|
|
175
|
+
.pagination .pg-btn{min-width:38px;height:38px;display:flex;align-items:center;justify-content:center;border:1px solid var(--border);border-radius:10px;background:var(--bg-card);color:var(--text-sec);font-size:13px;font-weight:500;cursor:pointer;transition:all .15s}
|
|
176
|
+
.pagination .pg-btn:hover{border-color:var(--pri);color:var(--pri)}
|
|
177
|
+
.pagination .pg-btn.active{background:var(--pri);color:#000;border-color:var(--pri)}
|
|
178
|
+
.pagination .pg-btn.disabled{opacity:.4;pointer-events:none}
|
|
179
|
+
.pagination .pg-info{font-size:12px;color:var(--text-sec);padding:0 12px}
|
|
180
|
+
|
|
181
|
+
@media(max-width:900px){.main-content{flex-direction:column;padding:20px}.sidebar{width:100%}.sidebar .stats-grid{grid-template-columns:repeat(4,1fr)}}
|
|
182
|
+
</style>
|
|
183
|
+
</head>
|
|
184
|
+
<body>
|
|
185
|
+
|
|
186
|
+
<!-- ─── Auth: Setup Password ─── -->
|
|
187
|
+
<div id="setupScreen" class="auth-screen" style="display:none">
|
|
188
|
+
<div class="auth-card">
|
|
189
|
+
<div class="logo">\u{1F99E}</div>
|
|
190
|
+
<h1>OpenClaw Memory</h1>
|
|
191
|
+
<p style="font-size:12px;color:var(--text-sec);margin-bottom:6px">Powered by MemOS</p>
|
|
192
|
+
<p>Set a password to protect your memories</p>
|
|
193
|
+
<input type="password" id="setupPw" placeholder="Enter a password (4+ characters)" autofocus>
|
|
194
|
+
<input type="password" id="setupPw2" placeholder="Confirm password">
|
|
195
|
+
<button class="btn-auth" onclick="doSetup()">Set Password & Enter</button>
|
|
196
|
+
<div class="error-msg" id="setupErr"></div>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<!-- ─── Auth: Login ─── -->
|
|
201
|
+
<div id="loginScreen" class="auth-screen" style="display:none">
|
|
202
|
+
<div class="auth-card">
|
|
203
|
+
<div class="logo">\u{1F99E}</div>
|
|
204
|
+
<h1>OpenClaw Memory</h1>
|
|
205
|
+
<p style="font-size:12px;color:var(--text-sec);margin-bottom:6px">Powered by MemOS</p>
|
|
206
|
+
<p>Enter your password to access memories</p>
|
|
207
|
+
<div id="loginForm">
|
|
208
|
+
<input type="password" id="loginPw" placeholder="Password" autofocus>
|
|
209
|
+
<button class="btn-auth" onclick="doLogin()">Unlock</button>
|
|
210
|
+
<div class="error-msg" id="loginErr"></div>
|
|
211
|
+
<button class="btn-text" style="margin-top:12px;font-size:13px;color:var(--text-sec)" onclick="showResetForm()">Forgot password?</button>
|
|
212
|
+
</div>
|
|
213
|
+
<div id="resetForm" style="display:none">
|
|
214
|
+
<div class="reset-guide">
|
|
215
|
+
<div class="reset-step">
|
|
216
|
+
<div class="step-num">1</div>
|
|
217
|
+
<div class="step-body">
|
|
218
|
+
<div class="step-title">Open Terminal</div>
|
|
219
|
+
<div class="step-desc">Run the following command to get your reset token:</div>
|
|
220
|
+
<div class="cmd-box" onclick="copyCmd(this)">
|
|
221
|
+
<code>grep "reset token" /tmp/openclaw/openclaw-*.log | tail -1</code>
|
|
222
|
+
<span class="copy-hint">Click to copy</span>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
<div class="reset-step">
|
|
227
|
+
<div class="step-num">2</div>
|
|
228
|
+
<div class="step-body">
|
|
229
|
+
<div class="step-title">Find the token</div>
|
|
230
|
+
<div class="step-desc">In the output, look for a line like:<br><span style="font-family:monospace;font-size:12px;color:var(--pri)">password reset token: <strong>a1b2c3d4e5f6...</strong></span><br>Copy the hex string after the colon.</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
<div class="reset-step">
|
|
234
|
+
<div class="step-num">3</div>
|
|
235
|
+
<div class="step-body">
|
|
236
|
+
<div class="step-title">Paste & reset</div>
|
|
237
|
+
<div class="step-desc">Paste the token below and set your new password.</div>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
<input type="text" id="resetToken" placeholder="Paste reset token here" style="margin-bottom:8px;font-family:monospace">
|
|
242
|
+
<input type="password" id="resetNewPw" placeholder="New password (4+ characters)">
|
|
243
|
+
<input type="password" id="resetNewPw2" placeholder="Confirm new password">
|
|
244
|
+
<button class="btn-auth" onclick="doReset()">Reset Password</button>
|
|
245
|
+
<div class="error-msg" id="resetErr"></div>
|
|
246
|
+
<button class="btn-text" style="margin-top:12px;font-size:13px;color:var(--text-sec)" onclick="showLoginForm()">\u2190 Back to login</button>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
<!-- ─── Main App ─── -->
|
|
252
|
+
<div class="app" id="app">
|
|
253
|
+
<div class="topbar">
|
|
254
|
+
<div class="brand">
|
|
255
|
+
<div class="icon">\u{1F99E}</div>
|
|
256
|
+
<span>OpenClaw Memory <span style="font-weight:400;color:var(--text-sec);font-size:12px">by MemOS</span></span>
|
|
257
|
+
</div>
|
|
258
|
+
<div class="actions">
|
|
259
|
+
<button class="btn btn-primary" onclick="openCreateModal()">+ New Memory</button>
|
|
260
|
+
<button class="btn" onclick="loadAll()">Refresh</button>
|
|
261
|
+
<button class="btn btn-danger" onclick="clearAll()">Clear All</button>
|
|
262
|
+
<button class="btn btn-text" onclick="doLogout()">Logout</button>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<div class="main-content">
|
|
267
|
+
<div class="sidebar" id="sidebar">
|
|
268
|
+
<div class="stats-grid" id="statsGrid">
|
|
269
|
+
<div class="stat-card pri"><div class="stat-value" id="statTotal">-</div><div class="stat-label">Memories</div></div>
|
|
270
|
+
<div class="stat-card green"><div class="stat-value" id="statSessions">-</div><div class="stat-label">Sessions</div></div>
|
|
271
|
+
<div class="stat-card amber"><div class="stat-value" id="statEmbeddings">-</div><div class="stat-label">Embeddings</div></div>
|
|
272
|
+
<div class="stat-card rose"><div class="stat-value" id="statTimeSpan">-</div><div class="stat-label">Days</div></div>
|
|
273
|
+
</div>
|
|
274
|
+
<div id="embeddingStatus"></div>
|
|
275
|
+
<div class="section-title">Sessions</div>
|
|
276
|
+
<div class="session-list" id="sessionList"></div>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<div class="feed">
|
|
280
|
+
<div class="search-bar">
|
|
281
|
+
<span class="search-icon">\u{1F50D}</span>
|
|
282
|
+
<input type="text" id="searchInput" placeholder="Search memories (supports semantic search)..." oninput="debounceSearch()">
|
|
283
|
+
</div>
|
|
284
|
+
<div class="search-meta" id="searchMeta"></div>
|
|
285
|
+
<div class="filter-bar" id="filterBar">
|
|
286
|
+
<button class="filter-chip active" data-role="" onclick="setRoleFilter(this,'')">All</button>
|
|
287
|
+
<button class="filter-chip" data-role="user" onclick="setRoleFilter(this,'user')">User</button>
|
|
288
|
+
<button class="filter-chip" data-role="assistant" onclick="setRoleFilter(this,'assistant')">Assistant</button>
|
|
289
|
+
<button class="filter-chip" data-role="system" onclick="setRoleFilter(this,'system')">System</button>
|
|
290
|
+
<span class="filter-sep"></span>
|
|
291
|
+
<select id="filterKind" class="filter-select" onchange="applyFilters()">
|
|
292
|
+
<option value="">All kinds</option>
|
|
293
|
+
<option value="paragraph">Paragraph</option>
|
|
294
|
+
<option value="code_block">Code</option>
|
|
295
|
+
<option value="dialog">Dialog</option>
|
|
296
|
+
<option value="list">List</option>
|
|
297
|
+
<option value="error_stack">Error</option>
|
|
298
|
+
<option value="command">Command</option>
|
|
299
|
+
</select>
|
|
300
|
+
<select id="filterSort" class="filter-select" onchange="applyFilters()">
|
|
301
|
+
<option value="newest">Newest first</option>
|
|
302
|
+
<option value="oldest">Oldest first</option>
|
|
303
|
+
</select>
|
|
304
|
+
</div>
|
|
305
|
+
<div class="date-filter">
|
|
306
|
+
<label>From</label><input type="datetime-local" id="dateFrom" step="1" onchange="applyFilters()">
|
|
307
|
+
<label>To</label><input type="datetime-local" id="dateTo" step="1" onchange="applyFilters()">
|
|
308
|
+
<button class="btn btn-sm btn-text" onclick="clearDateFilter()">Clear</button>
|
|
309
|
+
</div>
|
|
310
|
+
<div class="memory-list" id="memoryList"><div class="spinner"></div></div>
|
|
311
|
+
<div class="pagination" id="pagination"></div>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
<!-- ─── Memory Modal ─── -->
|
|
317
|
+
<div class="modal-overlay" id="modalOverlay">
|
|
318
|
+
<div class="modal">
|
|
319
|
+
<h2 id="modalTitle">New Memory</h2>
|
|
320
|
+
<div class="form-group"><label>Role</label><select id="mRole"><option value="user">User</option><option value="assistant">Assistant</option><option value="system">System</option></select></div>
|
|
321
|
+
<div class="form-group"><label>Content</label><textarea id="mContent" rows="4" placeholder="Memory content..."></textarea></div>
|
|
322
|
+
<div class="form-group"><label>Summary</label><input type="text" id="mSummary" placeholder="Brief summary (optional)"></div>
|
|
323
|
+
<div class="form-group"><label>Kind</label><select id="mKind"><option value="paragraph">Paragraph</option><option value="code">Code</option><option value="dialog">Dialog</option></select></div>
|
|
324
|
+
<div class="modal-actions">
|
|
325
|
+
<button class="btn" onclick="closeModal()">Cancel</button>
|
|
326
|
+
<button class="btn btn-primary" id="modalSubmit" onclick="submitModal()">Create</button>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
<!-- ─── Toast ─── -->
|
|
332
|
+
<div class="toast-container" id="toasts"></div>
|
|
333
|
+
|
|
334
|
+
<script>
|
|
335
|
+
let activeSession=null,activeRole='',editingId=null,searchTimer=null,memoryCache={},currentPage=1,totalPages=1,totalCount=0,PAGE_SIZE=30;
|
|
336
|
+
|
|
337
|
+
/* ─── Auth flow ─── */
|
|
338
|
+
async function checkAuth(){
|
|
339
|
+
const r=await fetch('/api/auth/status');
|
|
340
|
+
const d=await r.json();
|
|
341
|
+
if(d.needsSetup){
|
|
342
|
+
document.getElementById('setupScreen').style.display='flex';
|
|
343
|
+
document.getElementById('setupPw').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('setupPw2').focus()});
|
|
344
|
+
document.getElementById('setupPw2').addEventListener('keydown',e=>{if(e.key==='Enter')doSetup()});
|
|
345
|
+
} else if(!d.loggedIn){
|
|
346
|
+
document.getElementById('loginScreen').style.display='flex';
|
|
347
|
+
document.getElementById('loginPw').addEventListener('keydown',e=>{if(e.key==='Enter')doLogin()});
|
|
348
|
+
} else {
|
|
349
|
+
enterApp();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function doSetup(){
|
|
354
|
+
const pw=document.getElementById('setupPw').value;
|
|
355
|
+
const pw2=document.getElementById('setupPw2').value;
|
|
356
|
+
const err=document.getElementById('setupErr');
|
|
357
|
+
if(pw.length<4){err.textContent='Password must be at least 4 characters';return}
|
|
358
|
+
if(pw!==pw2){err.textContent='Passwords do not match';return}
|
|
359
|
+
const r=await fetch('/api/auth/setup',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({password:pw})});
|
|
360
|
+
const d=await r.json();
|
|
361
|
+
if(d.ok){document.getElementById('setupScreen').style.display='none';enterApp();}
|
|
362
|
+
else{err.textContent=d.error||'Setup failed'}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function doLogin(){
|
|
366
|
+
const pw=document.getElementById('loginPw').value;
|
|
367
|
+
const err=document.getElementById('loginErr');
|
|
368
|
+
const r=await fetch('/api/auth/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({password:pw})});
|
|
369
|
+
const d=await r.json();
|
|
370
|
+
if(d.ok){document.getElementById('loginScreen').style.display='none';enterApp();}
|
|
371
|
+
else{err.textContent='Incorrect password';document.getElementById('loginPw').value='';document.getElementById('loginPw').focus();}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function doLogout(){
|
|
375
|
+
await fetch('/api/auth/logout',{method:'POST'});
|
|
376
|
+
location.reload();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function showResetForm(){
|
|
380
|
+
document.getElementById('loginForm').style.display='none';
|
|
381
|
+
document.getElementById('resetForm').style.display='block';
|
|
382
|
+
document.getElementById('resetToken').focus();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function showLoginForm(){
|
|
386
|
+
document.getElementById('resetForm').style.display='none';
|
|
387
|
+
document.getElementById('loginForm').style.display='block';
|
|
388
|
+
document.getElementById('loginPw').focus();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function copyCmd(el){
|
|
392
|
+
const code=el.querySelector('code').textContent;
|
|
393
|
+
navigator.clipboard.writeText(code).then(()=>{
|
|
394
|
+
el.classList.add('copied');
|
|
395
|
+
el.querySelector('.copy-hint').textContent='Copied!';
|
|
396
|
+
setTimeout(()=>{el.classList.remove('copied');el.querySelector('.copy-hint').textContent='Click to copy'},2000);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function doReset(){
|
|
401
|
+
const token=document.getElementById('resetToken').value.trim();
|
|
402
|
+
const pw=document.getElementById('resetNewPw').value;
|
|
403
|
+
const pw2=document.getElementById('resetNewPw2').value;
|
|
404
|
+
const err=document.getElementById('resetErr');
|
|
405
|
+
if(!token){err.textContent='Please enter the reset token';return}
|
|
406
|
+
if(pw.length<4){err.textContent='Password must be at least 4 characters';return}
|
|
407
|
+
if(pw!==pw2){err.textContent='Passwords do not match';return}
|
|
408
|
+
const r=await fetch('/api/auth/reset',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token,newPassword:pw})});
|
|
409
|
+
const d=await r.json();
|
|
410
|
+
if(d.ok){document.getElementById('loginScreen').style.display='none';enterApp();}
|
|
411
|
+
else{err.textContent=d.error||'Reset failed'}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function enterApp(){
|
|
415
|
+
document.getElementById('app').style.display='flex';
|
|
416
|
+
loadAll();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* ─── Data loading ─── */
|
|
420
|
+
async function loadAll(){
|
|
421
|
+
await Promise.all([loadStats(),loadMemories()]);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async function loadStats(){
|
|
425
|
+
const r=await fetch('/api/stats');
|
|
426
|
+
const d=await r.json();
|
|
427
|
+
document.getElementById('statTotal').textContent=d.totalMemories;
|
|
428
|
+
document.getElementById('statSessions').textContent=d.totalSessions;
|
|
429
|
+
document.getElementById('statEmbeddings').textContent=d.totalEmbeddings;
|
|
430
|
+
const days=d.timeRange.earliest?Math.max(1,Math.round((new Date(d.timeRange.latest)-new Date(d.timeRange.earliest))/(86400000))):0;
|
|
431
|
+
document.getElementById('statTimeSpan').textContent=days;
|
|
432
|
+
|
|
433
|
+
const provEl=document.getElementById('embeddingStatus');
|
|
434
|
+
if(d.embeddingProvider && d.embeddingProvider!=='none'){
|
|
435
|
+
provEl.innerHTML='<div class="provider-badge"><span>\\u2713</span> Embedding: '+d.embeddingProvider+'</div>';
|
|
436
|
+
} else {
|
|
437
|
+
provEl.innerHTML='<div class="provider-badge offline"><span>\\u26A0</span> No embedding model</div>';
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const sl=document.getElementById('sessionList');
|
|
441
|
+
sl.innerHTML='<div class="session-item'+(activeSession===null?' active':'')+'" onclick="filterSession(null)"><span>All Sessions</span><span class="count">'+d.totalMemories+'</span></div>';
|
|
442
|
+
(d.sessions||[]).forEach(s=>{
|
|
443
|
+
const isActive=activeSession===s.session_key;
|
|
444
|
+
const name=s.session_key.length>20?s.session_key.slice(0,8)+'...'+s.session_key.slice(-8):s.session_key;
|
|
445
|
+
sl.innerHTML+='<div class="session-item'+(isActive?' active':'')+'" onclick="filterSession(\\''+s.session_key.replace(/'/g,"\\\\'")+'\\')"><span title="'+s.session_key+'">'+name+'</span><span class="count">'+s.count+'</span></div>';
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function getFilterParams(){
|
|
450
|
+
const p=new URLSearchParams();
|
|
451
|
+
if(activeSession) p.set('session',activeSession);
|
|
452
|
+
if(activeRole) p.set('role',activeRole);
|
|
453
|
+
const kind=document.getElementById('filterKind').value;
|
|
454
|
+
if(kind) p.set('kind',kind);
|
|
455
|
+
const df=document.getElementById('dateFrom').value;
|
|
456
|
+
if(df) p.set('dateFrom',df);
|
|
457
|
+
const dt=document.getElementById('dateTo').value;
|
|
458
|
+
if(dt) p.set('dateTo',dt);
|
|
459
|
+
const sort=document.getElementById('filterSort').value;
|
|
460
|
+
if(sort==='oldest') p.set('sort','oldest');
|
|
461
|
+
return p;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async function loadMemories(page){
|
|
465
|
+
if(page) currentPage=page;
|
|
466
|
+
const list=document.getElementById('memoryList');
|
|
467
|
+
list.innerHTML='<div class="spinner"></div>';
|
|
468
|
+
const p=getFilterParams();
|
|
469
|
+
p.set('limit',PAGE_SIZE);
|
|
470
|
+
p.set('page',currentPage);
|
|
471
|
+
const r=await fetch('/api/memories?'+p.toString());
|
|
472
|
+
const d=await r.json();
|
|
473
|
+
totalPages=d.totalPages||1;
|
|
474
|
+
totalCount=d.total||0;
|
|
475
|
+
document.getElementById('searchMeta').textContent=totalCount+' memories total';
|
|
476
|
+
renderMemories(d.memories||[]);
|
|
477
|
+
renderPagination();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function doSearch(q){
|
|
481
|
+
if(!q.trim()){currentPage=1;loadMemories();return}
|
|
482
|
+
const list=document.getElementById('memoryList');
|
|
483
|
+
list.innerHTML='<div class="spinner"></div>';
|
|
484
|
+
const p=getFilterParams();
|
|
485
|
+
p.set('q',q);
|
|
486
|
+
const r=await fetch('/api/search?'+p.toString());
|
|
487
|
+
const d=await r.json();
|
|
488
|
+
const meta=[];
|
|
489
|
+
if(d.vectorCount>0) meta.push(d.vectorCount+' semantic');
|
|
490
|
+
if(d.ftsCount>0) meta.push(d.ftsCount+' text');
|
|
491
|
+
meta.push(d.total+' results');
|
|
492
|
+
document.getElementById('searchMeta').textContent=meta.join(' \\u00B7 ');
|
|
493
|
+
renderMemories(d.results||[]);
|
|
494
|
+
document.getElementById('pagination').innerHTML='';
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function debounceSearch(){
|
|
498
|
+
clearTimeout(searchTimer);
|
|
499
|
+
searchTimer=setTimeout(()=>doSearch(document.getElementById('searchInput').value),350);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function filterSession(key){
|
|
503
|
+
activeSession=key;
|
|
504
|
+
currentPage=1;
|
|
505
|
+
loadAll();
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function setRoleFilter(btn,role){
|
|
509
|
+
activeRole=role;
|
|
510
|
+
currentPage=1;
|
|
511
|
+
document.querySelectorAll('.filter-chip').forEach(c=>c.classList.remove('active'));
|
|
512
|
+
btn.classList.add('active');
|
|
513
|
+
applyFilters();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function applyFilters(){
|
|
517
|
+
currentPage=1;
|
|
518
|
+
if(document.getElementById('searchInput').value.trim()){
|
|
519
|
+
doSearch(document.getElementById('searchInput').value);
|
|
520
|
+
} else {
|
|
521
|
+
loadMemories();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function clearDateFilter(){
|
|
526
|
+
document.getElementById('dateFrom').value='';
|
|
527
|
+
document.getElementById('dateTo').value='';
|
|
528
|
+
applyFilters();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/* ─── Rendering ─── */
|
|
532
|
+
function renderMemories(items){
|
|
533
|
+
const list=document.getElementById('memoryList');
|
|
534
|
+
if(!items.length){
|
|
535
|
+
list.innerHTML='<div class="empty"><div class="icon">\\u{1F4ED}</div><p>No memories found</p></div>';
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
items.forEach(m=>{memoryCache[m.id]=m});
|
|
539
|
+
list.innerHTML=items.map(m=>{
|
|
540
|
+
const time=m.created_at?new Date(typeof m.created_at==='number'?m.created_at:m.created_at).toLocaleString('zh-CN'):'';
|
|
541
|
+
const role=m.role||'user';
|
|
542
|
+
const kind=m.kind||'paragraph';
|
|
543
|
+
const summary=esc(m.summary||m.content?.slice(0,120)||'');
|
|
544
|
+
const content=esc(m.content||'');
|
|
545
|
+
const id=m.id;
|
|
546
|
+
const vscore=m._vscore?'<span class="vscore-badge">'+Math.round(m._vscore*100)+'%</span>':'';
|
|
547
|
+
const sid=m.session_key||'';
|
|
548
|
+
const sidShort=sid.length>18?sid.slice(0,6)+'..'+sid.slice(-6):sid;
|
|
549
|
+
return '<div class="memory-card">'+
|
|
550
|
+
'<div class="card-header"><div class="meta"><span class="role-tag '+role+'">'+role+'</span><span class="kind-tag">'+kind+'</span></div><span class="card-time"><span class="session-tag" title="'+esc(sid)+'">'+esc(sidShort)+'</span> '+time+'</span></div>'+
|
|
551
|
+
'<div class="card-summary">'+summary+'</div>'+
|
|
552
|
+
'<div class="card-content" id="content-'+id+'"><pre>'+content+'</pre></div>'+
|
|
553
|
+
'<div class="card-actions">'+
|
|
554
|
+
'<button class="btn btn-sm btn-text" onclick="toggleContent(\\''+id+'\\')">Expand</button>'+
|
|
555
|
+
'<button class="btn btn-sm" onclick="openEditModal(\\''+id+'\\')">Edit</button>'+
|
|
556
|
+
'<button class="btn btn-sm btn-danger" onclick="deleteMemory(\\''+id+'\\')">Delete</button>'+
|
|
557
|
+
vscore+
|
|
558
|
+
'</div></div>';
|
|
559
|
+
}).join('');
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function renderPagination(){
|
|
563
|
+
const el=document.getElementById('pagination');
|
|
564
|
+
if(totalPages<=1){el.innerHTML='';return}
|
|
565
|
+
let h='';
|
|
566
|
+
h+='<button class="pg-btn'+(currentPage<=1?' disabled':'')+'" onclick="goPage('+(currentPage-1)+')">\u2039</button>';
|
|
567
|
+
const range=[];
|
|
568
|
+
range.push(1);
|
|
569
|
+
for(let i=Math.max(2,currentPage-2);i<=Math.min(totalPages-1,currentPage+2);i++) range.push(i);
|
|
570
|
+
if(totalPages>1) range.push(totalPages);
|
|
571
|
+
const unique=[...new Set(range)].sort((a,b)=>a-b);
|
|
572
|
+
let prev=0;
|
|
573
|
+
for(const p of unique){
|
|
574
|
+
if(p-prev>1) h+='<span class="pg-info">...</span>';
|
|
575
|
+
h+='<button class="pg-btn'+(p===currentPage?' active':'')+'" onclick="goPage('+p+')">'+p+'</button>';
|
|
576
|
+
prev=p;
|
|
577
|
+
}
|
|
578
|
+
h+='<button class="pg-btn'+(currentPage>=totalPages?' disabled':'')+'" onclick="goPage('+(currentPage+1)+')">\u203A</button>';
|
|
579
|
+
h+='<span class="pg-info">'+totalCount+' total</span>';
|
|
580
|
+
el.innerHTML=h;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function goPage(p){
|
|
584
|
+
if(p<1||p>totalPages||p===currentPage) return;
|
|
585
|
+
currentPage=p;
|
|
586
|
+
loadMemories();
|
|
587
|
+
document.getElementById('memoryList').scrollIntoView({behavior:'smooth',block:'start'});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function toggleContent(id){
|
|
591
|
+
const el=document.getElementById('content-'+id);
|
|
592
|
+
el.classList.toggle('show');
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function esc(s){
|
|
596
|
+
if(!s)return'';
|
|
597
|
+
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/* ─── CRUD ─── */
|
|
601
|
+
function openCreateModal(){
|
|
602
|
+
editingId=null;
|
|
603
|
+
document.getElementById('modalTitle').textContent='New Memory';
|
|
604
|
+
document.getElementById('modalSubmit').textContent='Create';
|
|
605
|
+
document.getElementById('mRole').value='user';
|
|
606
|
+
document.getElementById('mContent').value='';
|
|
607
|
+
document.getElementById('mSummary').value='';
|
|
608
|
+
document.getElementById('mKind').value='paragraph';
|
|
609
|
+
document.getElementById('modalOverlay').classList.add('show');
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function openEditModal(id){
|
|
613
|
+
const m=memoryCache[id];
|
|
614
|
+
if(!m){toast('Memory not found in cache','error');return}
|
|
615
|
+
editingId=id;
|
|
616
|
+
document.getElementById('modalTitle').textContent='Edit Memory';
|
|
617
|
+
document.getElementById('modalSubmit').textContent='Save';
|
|
618
|
+
document.getElementById('mRole').value=m.role||'user';
|
|
619
|
+
document.getElementById('mContent').value=m.content||'';
|
|
620
|
+
document.getElementById('mSummary').value=m.summary||'';
|
|
621
|
+
document.getElementById('mKind').value=m.kind||'paragraph';
|
|
622
|
+
document.getElementById('modalOverlay').classList.add('show');
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function closeModal(){
|
|
626
|
+
document.getElementById('modalOverlay').classList.remove('show');
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
async function submitModal(){
|
|
630
|
+
const data={
|
|
631
|
+
role:document.getElementById('mRole').value,
|
|
632
|
+
content:document.getElementById('mContent').value,
|
|
633
|
+
summary:document.getElementById('mSummary').value,
|
|
634
|
+
kind:document.getElementById('mKind').value,
|
|
635
|
+
};
|
|
636
|
+
if(!data.content.trim()){toast('Please enter content','error');return}
|
|
637
|
+
let r;
|
|
638
|
+
if(editingId){
|
|
639
|
+
r=await fetch('/api/memory/'+editingId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
|
|
640
|
+
} else {
|
|
641
|
+
r=await fetch('/api/memory',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
|
|
642
|
+
}
|
|
643
|
+
const d=await r.json();
|
|
644
|
+
if(d.ok){toast(editingId?'Memory updated':'Memory created','success');closeModal();loadAll();}
|
|
645
|
+
else{toast(d.error||'Operation failed','error')}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
async function deleteMemory(id){
|
|
649
|
+
if(!confirm('Delete this memory?'))return;
|
|
650
|
+
const r=await fetch('/api/memory/'+id,{method:'DELETE'});
|
|
651
|
+
const d=await r.json();
|
|
652
|
+
if(d.ok){toast('Memory deleted','success');loadAll();}
|
|
653
|
+
else{toast('Delete failed','error')}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async function clearAll(){
|
|
657
|
+
if(!confirm('Delete ALL memories? This cannot be undone.'))return;
|
|
658
|
+
if(!confirm('Are you absolutely sure?'))return;
|
|
659
|
+
const r=await fetch('/api/memories',{method:'DELETE'});
|
|
660
|
+
const d=await r.json();
|
|
661
|
+
if(d.ok){toast('All memories cleared','success');loadAll();}
|
|
662
|
+
else{toast('Clear failed','error')}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/* ─── Toast ─── */
|
|
666
|
+
function toast(msg,type='info'){
|
|
667
|
+
const c=document.getElementById('toasts');
|
|
668
|
+
const t=document.createElement('div');
|
|
669
|
+
t.className='toast '+type;
|
|
670
|
+
const icons={success:'\\u2705',error:'\\u274C',info:'\\u2139\\uFE0F'};
|
|
671
|
+
t.innerHTML=(icons[type]||'')+' '+esc(msg);
|
|
672
|
+
c.appendChild(t);
|
|
673
|
+
setTimeout(()=>t.remove(),3500);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/* ─── Init ─── */
|
|
677
|
+
document.getElementById('modalOverlay').addEventListener('click',e=>{if(e.target.id==='modalOverlay')closeModal()});
|
|
678
|
+
document.getElementById('searchInput').addEventListener('keydown',e=>{if(e.key==='Escape'){e.target.value='';loadMemories()}});
|
|
679
|
+
checkAuth();
|
|
680
|
+
</script>
|
|
681
|
+
</body>
|
|
682
|
+
</html>`;
|