@memtensor/memos-local-openclaw-plugin 0.1.3 → 0.1.4
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 +13 -5
- package/README.md +177 -97
- package/dist/capture/index.d.ts +5 -7
- package/dist/capture/index.d.ts.map +1 -1
- package/dist/capture/index.js +72 -43
- package/dist/capture/index.js.map +1 -1
- package/dist/ingest/providers/anthropic.d.ts +2 -0
- package/dist/ingest/providers/anthropic.d.ts.map +1 -1
- package/dist/ingest/providers/anthropic.js +110 -1
- package/dist/ingest/providers/anthropic.js.map +1 -1
- package/dist/ingest/providers/bedrock.d.ts +2 -5
- package/dist/ingest/providers/bedrock.d.ts.map +1 -1
- package/dist/ingest/providers/bedrock.js +110 -6
- package/dist/ingest/providers/bedrock.js.map +1 -1
- package/dist/ingest/providers/gemini.d.ts +2 -0
- package/dist/ingest/providers/gemini.d.ts.map +1 -1
- package/dist/ingest/providers/gemini.js +106 -1
- package/dist/ingest/providers/gemini.js.map +1 -1
- package/dist/ingest/providers/index.d.ts +9 -0
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +66 -4
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/ingest/providers/openai.d.ts +2 -0
- package/dist/ingest/providers/openai.d.ts.map +1 -1
- package/dist/ingest/providers/openai.js +112 -1
- package/dist/ingest/providers/openai.js.map +1 -1
- package/dist/ingest/task-processor.d.ts +63 -0
- package/dist/ingest/task-processor.d.ts.map +1 -0
- package/dist/ingest/task-processor.js +339 -0
- package/dist/ingest/task-processor.js.map +1 -0
- package/dist/ingest/worker.d.ts +1 -1
- package/dist/ingest/worker.d.ts.map +1 -1
- package/dist/ingest/worker.js +18 -13
- package/dist/ingest/worker.js.map +1 -1
- package/dist/recall/engine.d.ts +1 -0
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +21 -11
- package/dist/recall/engine.js.map +1 -1
- package/dist/recall/mmr.d.ts.map +1 -1
- package/dist/recall/mmr.js +3 -1
- package/dist/recall/mmr.js.map +1 -1
- package/dist/storage/sqlite.d.ts +67 -1
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +251 -5
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/dist/viewer/html.d.ts +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +919 -123
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +3 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +59 -1
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +217 -42
- package/openclaw.plugin.json +20 -45
- package/package.json +3 -4
- package/skill/SKILL.md +59 -0
- package/src/capture/index.ts +85 -45
- package/src/ingest/providers/anthropic.ts +128 -1
- package/src/ingest/providers/bedrock.ts +130 -6
- package/src/ingest/providers/gemini.ts +128 -1
- package/src/ingest/providers/index.ts +74 -8
- package/src/ingest/providers/openai.ts +130 -1
- package/src/ingest/task-processor.ts +380 -0
- package/src/ingest/worker.ts +21 -15
- package/src/recall/engine.ts +22 -12
- package/src/recall/mmr.ts +3 -1
- package/src/storage/sqlite.ts +298 -5
- package/src/types.ts +19 -0
- package/src/viewer/html.ts +919 -123
- package/src/viewer/server.ts +63 -1
- package/SKILL.md +0 -43
- package/www/index.html +0 -632
package/src/viewer/html.ts
CHANGED
|
@@ -38,11 +38,17 @@ export const viewerHTML = `<!DOCTYPE html>
|
|
|
38
38
|
}
|
|
39
39
|
[data-theme="light"] .auth-screen{background:linear-gradient(135deg,#e0f2fe 0%,#f0f9ff 50%,#e0e7ff 100%)}
|
|
40
40
|
[data-theme="light"] .auth-card{box-shadow:0 25px 50px -12px rgba(0,0,0,.12)}
|
|
41
|
-
[data-theme="light"] .topbar{background:rgba(255,255,255,.
|
|
41
|
+
[data-theme="light"] .topbar{background:rgba(255,255,255,.95);border-bottom-color:var(--border)}
|
|
42
42
|
[data-theme="light"] .session-item .count,[data-theme="light"] .kind-tag,[data-theme="light"] .session-tag{background:rgba(0,0,0,.06)}
|
|
43
43
|
[data-theme="light"] .card-content pre{background:rgba(0,0,0,.05);border-color:var(--border)}
|
|
44
|
-
[data-theme="light"] .vscore-badge{background:
|
|
44
|
+
[data-theme="light"] .vscore-badge{background:rgba(59,130,246,.1);color:#3b82f6}
|
|
45
45
|
[data-theme="light"] ::-webkit-scrollbar-thumb{background:rgba(0,0,0,.2)}
|
|
46
|
+
[data-theme="light"] .analytics-card{background:linear-gradient(145deg,#fff 0%,#f8fafc 100%);border-color:rgba(0,0,0,.08)}
|
|
47
|
+
[data-theme="light"] .analytics-section{background:#fff;border-color:rgba(0,0,0,.08)}
|
|
48
|
+
[data-theme="light"] .breakdown-item{background:rgba(0,0,0,.04)}
|
|
49
|
+
[data-theme="light"] .metrics-toolbar .range-btn{background:#fff;border-color:rgba(0,0,0,.1)}
|
|
50
|
+
[data-theme="light"] .metrics-toolbar .range-btn.active{background:rgba(0,0,0,.06);color:var(--text);border-color:rgba(0,0,0,.15)}
|
|
51
|
+
[data-theme="light"] .metrics-toolbar .range-btn:hover{border-color:var(--pri);color:var(--pri)}
|
|
46
52
|
body{font-family:'Inter',-apple-system,BlinkMacSystemFont,sans-serif;background:var(--bg);color:var(--text);line-height:1.6;transition:background .2s,color .2s}
|
|
47
53
|
button{cursor:pointer;font-family:inherit;font-size:inherit}
|
|
48
54
|
input,textarea,select{font-family:inherit;font-size:inherit}
|
|
@@ -55,12 +61,12 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
55
61
|
.auth-card p{color:hsl(0 0% 45.1%);margin-bottom:24px;font-size:14px}
|
|
56
62
|
.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%)}
|
|
57
63
|
.auth-card input::placeholder{color:hsl(0 0% 45.1%)}
|
|
58
|
-
.auth-card input:focus{border-color
|
|
59
|
-
.auth-card .btn-auth{width:100%;padding:12px;border:none;border-radius:8px;background
|
|
60
|
-
.auth-card .btn-auth:hover{background
|
|
64
|
+
.auth-card input:focus{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.15)}
|
|
65
|
+
.auth-card .btn-auth{width:100%;padding:12px;border:none;border-radius:8px;background:#3b82f6;color:#fff;font-weight:600;font-size:14px;transition:all .2s}
|
|
66
|
+
.auth-card .btn-auth:hover{background:#2563eb;transform:translateY(-1px);box-shadow:0 8px 25px rgba(59,130,246,.3)}
|
|
61
67
|
.auth-card .error-msg{color:hsl(0 84.2% 60.2%);font-size:13px;margin-top:8px;min-height:20px}
|
|
62
68
|
.auth-card .btn-text{color:hsl(0 0% 45.1%)}
|
|
63
|
-
.auth-card .btn-text:hover{color
|
|
69
|
+
.auth-card .btn-text:hover{color:#3b82f6}
|
|
64
70
|
|
|
65
71
|
.reset-guide{text-align:left;margin-bottom:20px}
|
|
66
72
|
.reset-step{display:flex;gap:14px;margin-bottom:16px}
|
|
@@ -76,10 +82,12 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
76
82
|
|
|
77
83
|
/* ─── App Layout (dark dashboard, same as www) ─── */
|
|
78
84
|
.app{display:none;flex-direction:column;min-height:100vh}
|
|
79
|
-
.topbar{background:rgba(5,5,16,.
|
|
80
|
-
.topbar .brand{display:flex;align-items:center;gap:
|
|
81
|
-
.topbar .brand .icon{width:
|
|
82
|
-
.topbar .
|
|
85
|
+
.topbar{background:rgba(5,5,16,.92);border-bottom:1px solid var(--border);padding:0 28px;height:56px;display:flex;align-items:center;position:sticky;top:0;z-index:100;backdrop-filter:blur(16px)}
|
|
86
|
+
.topbar .brand{display:flex;align-items:center;gap:10px;font-weight:700;font-size:15px;color:var(--text);letter-spacing:-.02em;flex-shrink:0}
|
|
87
|
+
.topbar .brand .icon{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:22px;background:none;border-radius:0}
|
|
88
|
+
.topbar .brand .sub{font-weight:400;color:var(--text-muted);font-size:11px}
|
|
89
|
+
.topbar-center{flex:1;display:flex;justify-content:center}
|
|
90
|
+
.topbar .actions{display:flex;align-items:center;gap:6px;flex-shrink:0}
|
|
83
91
|
|
|
84
92
|
.main-content{display:flex;flex:1;max-width:1400px;margin:0 auto;width:100%;padding:28px 32px;gap:28px}
|
|
85
93
|
|
|
@@ -107,11 +115,11 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
107
115
|
|
|
108
116
|
/* ─── Feed ─── */
|
|
109
117
|
.feed{flex:1;min-width:0}
|
|
110
|
-
.search-bar{display:flex;gap:
|
|
111
|
-
.search-bar input{flex:1;padding:
|
|
118
|
+
.search-bar{display:flex;gap:10px;margin-bottom:16px;position:relative;align-items:center}
|
|
119
|
+
.search-bar input{flex:1;padding:10px 16px 10px 40px;border:1px solid var(--border);border-radius:10px;font-size:14px;outline:none;background:var(--bg-card);color:var(--text);transition:all .2s}
|
|
112
120
|
.search-bar input::placeholder{color:var(--text-muted)}
|
|
113
121
|
.search-bar input:focus{border-color:var(--pri);box-shadow:0 0 0 3px var(--pri-glow)}
|
|
114
|
-
.search-bar .search-icon{position:absolute;left:
|
|
122
|
+
.search-bar .search-icon{position:absolute;left:14px;top:50%;transform:translateY(-50%);color:var(--text-muted);font-size:14px;pointer-events:none}
|
|
115
123
|
.search-meta{font-size:12px;color:var(--text-sec);margin-bottom:14px;padding:0 2px}
|
|
116
124
|
|
|
117
125
|
.filter-bar{display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap}
|
|
@@ -136,19 +144,25 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
136
144
|
.card-content.show{max-height:600px;overflow-y:auto}
|
|
137
145
|
.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)}
|
|
138
146
|
.card-actions{display:flex;align-items:center;gap:8px;margin-top:14px}
|
|
139
|
-
.vscore-badge{display:inline-flex;align-items:center;background:
|
|
147
|
+
.vscore-badge{display:inline-flex;align-items:center;background:rgba(59,130,246,.15);color:#60a5fa;font-size:10px;font-weight:700;padding:4px 10px;border-radius:8px;margin-left:auto}
|
|
140
148
|
|
|
141
149
|
/* ─── Buttons ─── */
|
|
142
|
-
.btn{padding:
|
|
150
|
+
.btn{padding:7px 14px;border-radius:8px;border:1px solid var(--border);background:var(--bg-card);color:var(--text);font-size:13px;font-weight:500;transition:all .18s ease;display:inline-flex;align-items:center;gap:5px;white-space:nowrap}
|
|
143
151
|
.btn:hover{border-color:var(--pri);color:var(--pri)}
|
|
144
|
-
.btn-primary{background:var(--
|
|
145
|
-
.btn-primary:hover{background
|
|
146
|
-
.btn-
|
|
147
|
-
.btn-
|
|
148
|
-
.btn-
|
|
149
|
-
.btn-
|
|
150
|
-
.btn-
|
|
152
|
+
.btn-primary{background:rgba(255,255,255,.08);color:var(--text);border:1px solid var(--border);font-weight:600}
|
|
153
|
+
.btn-primary:hover{background:rgba(255,255,255,.14);transform:translateY(-1px);border-color:var(--pri);color:var(--pri)}
|
|
154
|
+
.btn-ghost{border-color:transparent;background:transparent;color:var(--text-sec)}
|
|
155
|
+
.btn-ghost:hover{background:rgba(255,255,255,.06);color:var(--text)}
|
|
156
|
+
.btn-danger{color:var(--accent);border-color:rgba(230,57,70,.25)}
|
|
157
|
+
.btn-danger:hover{background:rgba(230,57,70,.1);color:var(--accent)}
|
|
158
|
+
.btn-sm{padding:5px 12px;font-size:12px}
|
|
159
|
+
.btn-icon{padding:5px 7px;font-size:15px;border-radius:8px}
|
|
160
|
+
.btn-text{border:none;background:none;color:var(--text-muted);font-size:12px;padding:4px 8px}
|
|
151
161
|
.btn-text:hover{color:var(--pri)}
|
|
162
|
+
[data-theme="light"] .btn-primary{background:rgba(0,0,0,.05);color:var(--text);border-color:rgba(0,0,0,.12)}
|
|
163
|
+
[data-theme="light"] .btn-primary:hover{background:rgba(0,0,0,.08);border-color:var(--pri);color:var(--pri)}
|
|
164
|
+
[data-theme="light"] .btn-ghost{color:var(--text-sec)}
|
|
165
|
+
[data-theme="light"] .btn-ghost:hover{background:rgba(0,0,0,.04);color:var(--text)}
|
|
152
166
|
|
|
153
167
|
/* ─── Modal ─── */
|
|
154
168
|
.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)}
|
|
@@ -198,89 +212,219 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
198
212
|
.pagination .pg-btn.disabled{opacity:.4;pointer-events:none}
|
|
199
213
|
.pagination .pg-info{font-size:12px;color:var(--text-sec);padding:0 12px}
|
|
200
214
|
|
|
201
|
-
|
|
215
|
+
/* ─── Tasks 视图 ─── */
|
|
216
|
+
.tasks-view{display:none;flex:1;min-width:0;flex-direction:column;gap:16px}
|
|
217
|
+
.tasks-view.show{display:flex}
|
|
218
|
+
.tasks-header{display:flex;flex-direction:column;gap:14px}
|
|
219
|
+
.tasks-stats{display:flex;gap:16px}
|
|
220
|
+
.tasks-stat{display:flex;align-items:center;gap:8px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:12px 18px;flex:1;transition:all .2s}
|
|
221
|
+
.tasks-stat:hover{border-color:var(--border-glow)}
|
|
222
|
+
.tasks-stat-value{font-size:22px;font-weight:700;color:var(--text)}
|
|
223
|
+
.tasks-stat-label{font-size:12px;color:var(--text-sec);font-weight:500}
|
|
224
|
+
.tasks-filters{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
225
|
+
.tasks-list{display:flex;flex-direction:column;gap:10px}
|
|
226
|
+
.task-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:18px 20px;cursor:pointer;transition:all .25s;position:relative;overflow:hidden}
|
|
227
|
+
.task-card:hover{border-color:var(--border-glow);background:var(--bg-card-hover);transform:translateY(-1px);box-shadow:var(--shadow)}
|
|
228
|
+
.task-card::before{content:'';position:absolute;top:0;left:0;bottom:0;width:3px;border-radius:3px 0 0 3px}
|
|
229
|
+
.task-card.status-active::before{background:var(--green)}
|
|
230
|
+
.task-card.status-completed::before{background:var(--pri)}
|
|
231
|
+
.task-card.status-skipped::before{background:var(--text-muted)}
|
|
232
|
+
.task-card.status-skipped{opacity:.6}
|
|
233
|
+
.task-card-top{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:8px}
|
|
234
|
+
.task-card-title{font-size:14px;font-weight:600;color:var(--text);line-height:1.4;flex:1;word-break:break-word}
|
|
235
|
+
.task-card-title:empty::after{content:'Untitled Task';color:var(--text-muted);font-style:italic}
|
|
236
|
+
.task-status-badge{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;padding:3px 10px;border-radius:20px;flex-shrink:0}
|
|
237
|
+
.task-status-badge.active{color:var(--green);background:var(--green-bg)}
|
|
238
|
+
.task-status-badge.completed{color:var(--pri);background:var(--pri-glow)}
|
|
239
|
+
.task-status-badge.skipped{color:var(--text-muted);background:rgba(128,128,128,.15)}
|
|
240
|
+
.task-card-summary{font-size:13px;color:var(--text-sec);line-height:1.5;margin-bottom:10px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
|
241
|
+
.task-card-summary:empty{display:none}
|
|
242
|
+
.task-card-bottom{display:flex;align-items:center;gap:14px;font-size:11px;color:var(--text-muted)}
|
|
243
|
+
.task-card-bottom .tag{display:flex;align-items:center;gap:4px}
|
|
244
|
+
.task-card-bottom .tag .icon{font-size:12px}
|
|
245
|
+
|
|
246
|
+
/* ─── Task Detail Overlay ─── */
|
|
247
|
+
.task-detail-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:200;align-items:center;justify-content:center;padding:24px;backdrop-filter:blur(4px)}
|
|
248
|
+
.task-detail-overlay.show{display:flex}
|
|
249
|
+
.task-detail-panel{background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-xl);width:100%;max-width:780px;max-height:85vh;overflow-y:auto;box-shadow:var(--shadow-lg);padding:28px 32px}
|
|
250
|
+
.task-detail-header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:16px}
|
|
251
|
+
.task-detail-header h2{font-size:18px;font-weight:700;color:var(--text);line-height:1.4;flex:1}
|
|
252
|
+
.task-detail-meta{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:20px;font-size:12px;color:var(--text-sec)}
|
|
253
|
+
.task-detail-meta .meta-item{display:flex;align-items:center;gap:5px;background:var(--bg-card);border:1px solid var(--border);border-radius:8px;padding:5px 12px}
|
|
254
|
+
.task-detail-summary{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:20px;font-size:13px;line-height:1.7;color:var(--text);word-break:break-word}
|
|
255
|
+
.task-detail-summary:empty::after{content:'Summary not yet generated (task still active)';color:var(--text-muted);font-style:italic}
|
|
256
|
+
.task-detail-summary .summary-section-title{font-size:14px;font-weight:700;color:var(--text);margin:14px 0 6px 0;padding-bottom:4px;border-bottom:1px solid var(--border)}
|
|
257
|
+
.task-detail-summary .summary-section-title:first-child{margin-top:0}
|
|
258
|
+
.task-detail-summary ul{margin:4px 0 8px 0;padding-left:20px}
|
|
259
|
+
.task-detail-summary li{margin:3px 0;color:var(--text-sec);line-height:1.6}
|
|
260
|
+
.task-detail-chunks-title{font-size:12px;font-weight:700;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}
|
|
261
|
+
.task-detail-chunks{display:flex;flex-direction:column;gap:14px;padding:8px 0}
|
|
262
|
+
.task-chunk-item{display:flex;flex-direction:column;max-width:82%;font-size:13px;line-height:1.6}
|
|
263
|
+
.task-chunk-item.role-user{align-self:flex-end;align-items:flex-end}
|
|
264
|
+
.task-chunk-item.role-assistant,.task-chunk-item.role-tool{align-self:flex-start;align-items:flex-start}
|
|
265
|
+
.task-chunk-role{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;margin-bottom:3px;padding:0 4px}
|
|
266
|
+
.task-chunk-role.user{color:var(--pri)}
|
|
267
|
+
.task-chunk-role.assistant{color:var(--green)}
|
|
268
|
+
.task-chunk-role.tool{color:var(--amber)}
|
|
269
|
+
.task-chunk-bubble{padding:12px 16px;border-radius:16px;white-space:pre-wrap;word-break:break-word;max-height:200px;overflow:hidden;position:relative;transition:all .2s}
|
|
270
|
+
.task-chunk-bubble.expanded{max-height:none}
|
|
271
|
+
.role-user .task-chunk-bubble{background:var(--pri);color:#000;border-bottom-right-radius:4px}
|
|
272
|
+
.role-assistant .task-chunk-bubble{background:var(--bg-card);border:1px solid var(--border);color:var(--text-sec);border-bottom-left-radius:4px}
|
|
273
|
+
.role-tool .task-chunk-bubble{background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.2);color:var(--text-sec);border-bottom-left-radius:4px;font-family:'SF Mono',Monaco,Consolas,monospace;font-size:12px}
|
|
274
|
+
.task-chunk-bubble:hover{filter:brightness(1.05)}
|
|
275
|
+
.task-chunk-time{font-size:10px;color:var(--text-muted);margin-top:3px;padding:0 4px}
|
|
276
|
+
[data-theme="light"] .role-user .task-chunk-bubble{background:var(--pri);color:#fff}
|
|
277
|
+
[data-theme="light"] .role-assistant .task-chunk-bubble{background:#f0f0f0;border:none;color:#333}
|
|
278
|
+
[data-theme="light"] .task-detail-panel{background:#fff}
|
|
279
|
+
[data-theme="light"] .task-card{background:#fff}
|
|
280
|
+
[data-theme="light"] .tasks-stat{background:#fff}
|
|
281
|
+
|
|
282
|
+
/* ─── Analytics / 统计 ─── */
|
|
283
|
+
.nav-tabs{display:flex;align-items:center;gap:2px;background:rgba(255,255,255,.06);border-radius:10px;padding:3px}
|
|
284
|
+
.nav-tabs .tab{padding:6px 20px;border-radius:8px;font-size:13px;font-weight:600;color:var(--text-sec);background:transparent;border:1px solid transparent;cursor:pointer;transition:all .2s;white-space:nowrap}
|
|
285
|
+
.nav-tabs .tab:hover{color:var(--text)}
|
|
286
|
+
.nav-tabs .tab.active{color:var(--text);background:rgba(255,255,255,.1);border-color:var(--border);box-shadow:0 1px 4px rgba(0,0,0,.15)}
|
|
287
|
+
[data-theme="light"] .nav-tabs{background:rgba(0,0,0,.05)}
|
|
288
|
+
[data-theme="light"] .nav-tabs .tab.active{background:#fff;border-color:rgba(0,0,0,.1);box-shadow:0 1px 3px rgba(0,0,0,.08);color:var(--text)}
|
|
289
|
+
.analytics-view{display:none;flex:1;min-width:0;flex-direction:column;gap:20px}
|
|
290
|
+
.analytics-view.show{display:flex}
|
|
291
|
+
.feed-wrap{flex:1;min-width:0;display:flex;flex-direction:column}
|
|
292
|
+
.feed-wrap.hide{display:none}
|
|
293
|
+
.analytics-cards{display:grid;grid-template-columns:repeat(5,1fr);gap:14px}
|
|
294
|
+
.analytics-card{background:linear-gradient(145deg,var(--bg-card) 0%,rgba(255,255,255,.02) 100%);border:1px solid var(--border);border-radius:var(--radius-lg);padding:20px 18px;position:relative;overflow:hidden;transition:all .25s}
|
|
295
|
+
.analytics-card:hover{border-color:var(--border-glow);transform:translateY(-2px);box-shadow:0 8px 20px rgba(0,0,0,.15)}
|
|
296
|
+
.analytics-card::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,#3b82f6,#60a5fa);opacity:.85}
|
|
297
|
+
.analytics-card.green::before{background:linear-gradient(90deg,var(--green),#34d399)}
|
|
298
|
+
.analytics-card.amber::before{background:linear-gradient(90deg,var(--amber),#fbbf24)}
|
|
299
|
+
.analytics-card.violet::before{background:linear-gradient(90deg,var(--violet),#a78bfa)}
|
|
300
|
+
.analytics-card .ac-value{font-size:26px;font-weight:800;letter-spacing:-.03em;color:var(--text);line-height:1.1}
|
|
301
|
+
.analytics-card .ac-label{font-size:11px;color:var(--text-muted);margin-top:5px;font-weight:500;text-transform:uppercase;letter-spacing:.04em}
|
|
302
|
+
.analytics-section{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:22px 24px}
|
|
303
|
+
.analytics-section h3{font-size:12px;font-weight:700;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:16px;display:flex;align-items:center;gap:8px}
|
|
304
|
+
.analytics-section h3 .icon{font-size:14px;opacity:.7}
|
|
305
|
+
.chart-bars{display:flex;align-items:flex-end;gap:4px;padding:8px 0;overflow-x:auto}
|
|
306
|
+
.chart-bar-wrap{flex:1;min-width:18px;max-width:60px;display:flex;flex-direction:column;align-items:center;gap:4px;position:relative}
|
|
307
|
+
.chart-bar-col{width:100%;height:160px;display:flex;flex-direction:column;justify-content:flex-end;align-items:stretch}
|
|
308
|
+
.chart-bar-wrap:hover .chart-bar{filter:brightness(1.25)}
|
|
309
|
+
.chart-bar-wrap:hover .chart-bar-label{color:var(--pri)}
|
|
310
|
+
.chart-bar-wrap:hover .chart-tip{opacity:1;transform:translateX(-50%) translateY(0)}
|
|
311
|
+
.chart-tip{position:absolute;top:-6px;left:50%;transform:translateX(-50%) translateY(4px);background:var(--bg);border:1px solid var(--border);color:var(--text);padding:2px 8px;border-radius:6px;font-size:10px;font-weight:700;white-space:nowrap;z-index:5;pointer-events:none;box-shadow:var(--shadow);opacity:0;transition:all .15s ease}
|
|
312
|
+
.chart-bar{width:100%;border-radius:4px 4px 0 0;background:linear-gradient(180deg,#60a5fa,#3b82f6);transition:all .3s ease}
|
|
313
|
+
.chart-bar.violet{background:linear-gradient(180deg,var(--violet),#7c3aed)}
|
|
314
|
+
.chart-bar.green{background:linear-gradient(180deg,var(--green),#059669)}
|
|
315
|
+
.chart-bar.zero{background:var(--border);opacity:.4;border-radius:2px}
|
|
316
|
+
.chart-bar-label{font-size:9px;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;text-align:center;transition:color .15s}
|
|
317
|
+
.chart-legend{display:flex;gap:16px;margin-top:12px;flex-wrap:wrap;font-size:12px;color:var(--text-sec)}
|
|
318
|
+
.chart-legend span{display:inline-flex;align-items:center;gap:6px}
|
|
319
|
+
.chart-legend .dot{width:8px;height:8px;border-radius:50%}
|
|
320
|
+
.chart-legend .dot.pri{background:var(--pri)}
|
|
321
|
+
.chart-legend .dot.violet{background:var(--violet)}
|
|
322
|
+
.chart-legend .dot.green{background:var(--green)}
|
|
323
|
+
.breakdown-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:20px}
|
|
324
|
+
.breakdown-item{display:flex;flex-direction:column;gap:6px;padding:12px 14px;background:rgba(0,0,0,.12);border-radius:10px;border:1px solid var(--border);transition:border-color .15s}
|
|
325
|
+
.breakdown-item:hover{border-color:var(--border-glow)}
|
|
326
|
+
.breakdown-item .bd-top{display:flex;align-items:center;justify-content:space-between}
|
|
327
|
+
.breakdown-item .label{font-size:13px;color:var(--text-sec);font-weight:500;text-transform:capitalize}
|
|
328
|
+
.breakdown-item .value{font-size:14px;font-weight:700;color:var(--text)}
|
|
329
|
+
.breakdown-bar-wrap{height:4px;background:rgba(0,0,0,.15);border-radius:2px;overflow:hidden}
|
|
330
|
+
.breakdown-bar{height:100%;border-radius:2px;background:linear-gradient(90deg,#3b82f6,#60a5fa);transition:width .5s ease}
|
|
331
|
+
.metrics-toolbar{display:flex;align-items:center;gap:12px;margin-bottom:16px;flex-wrap:wrap}
|
|
332
|
+
.metrics-toolbar .range-btn{padding:6px 14px;border-radius:8px;border:1px solid var(--border);background:var(--bg-card);color:var(--text-sec);font-size:12px;font-weight:600;cursor:pointer;transition:all .15s}
|
|
333
|
+
.metrics-toolbar .range-btn:hover{border-color:var(--pri);color:var(--pri)}
|
|
334
|
+
.metrics-toolbar .range-btn.active{background:rgba(255,255,255,.1);color:var(--text);border-color:var(--border)}
|
|
335
|
+
|
|
336
|
+
.theme-toggle{position:relative;width:28px;height:28px;padding:0;display:flex;align-items:center;justify-content:center;font-size:14px;border:none;background:transparent}
|
|
202
337
|
.theme-toggle .theme-icon-light{display:none}
|
|
203
338
|
.theme-toggle .theme-icon-dark{display:inline}
|
|
204
339
|
[data-theme="light"] .theme-toggle .theme-icon-light{display:inline}
|
|
205
340
|
[data-theme="light"] .theme-toggle .theme-icon-dark{display:none}
|
|
206
341
|
|
|
207
|
-
.auth-
|
|
208
|
-
.auth-theme-toggle:
|
|
342
|
+
.auth-top-actions{position:absolute;top:16px;right:16px;z-index:10;display:flex;align-items:center;gap:2px;background:rgba(255,255,255,.18);backdrop-filter:blur(10px);border-radius:20px;padding:3px}
|
|
343
|
+
.auth-theme-toggle{width:30px;height:30px;border:none;border-radius:50%;background:transparent;color:rgba(255,255,255,.7);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:13px;transition:all .2s}
|
|
344
|
+
.auth-theme-toggle:hover{background:rgba(255,255,255,.2);color:#fff}
|
|
209
345
|
.auth-theme-toggle .theme-icon-light{display:none}
|
|
210
346
|
.auth-theme-toggle .theme-icon-dark{display:inline}
|
|
211
|
-
[data-theme="light"] .auth-theme-toggle{
|
|
212
|
-
[data-theme="light"] .auth-theme-toggle:hover{background:rgba(0,0,0,.
|
|
347
|
+
[data-theme="light"] .auth-theme-toggle{color:rgba(0,0,0,.45)}
|
|
348
|
+
[data-theme="light"] .auth-theme-toggle:hover{background:rgba(0,0,0,.08);color:#0f172a}
|
|
349
|
+
[data-theme="light"] .auth-top-actions{background:rgba(0,0,0,.06)}
|
|
213
350
|
[data-theme="light"] .auth-theme-toggle .theme-icon-light{display:inline}
|
|
214
351
|
[data-theme="light"] .auth-theme-toggle .theme-icon-dark{display:none}
|
|
215
352
|
|
|
216
|
-
@media(max-width:
|
|
353
|
+
@media(max-width:1100px){.analytics-cards{grid-template-columns:repeat(3,1fr)}}
|
|
354
|
+
@media(max-width:900px){.main-content{flex-direction:column;padding:20px}.sidebar{width:100%}.sidebar .stats-grid{grid-template-columns:repeat(4,1fr)}.analytics-cards{grid-template-columns:repeat(2,1fr)}.topbar{padding:0 16px;gap:8px}.topbar .brand span{display:none}.topbar-center{justify-content:flex-start}}
|
|
217
355
|
</style>
|
|
218
356
|
</head>
|
|
219
357
|
<body>
|
|
220
358
|
|
|
221
359
|
<!-- ─── Auth: Setup Password ─── -->
|
|
222
360
|
<div id="setupScreen" class="auth-screen" style="display:none">
|
|
223
|
-
<
|
|
361
|
+
<div class="auth-top-actions">
|
|
362
|
+
<button class="auth-theme-toggle" onclick="toggleViewerTheme()" title="Toggle light/dark" aria-label="Toggle theme"><span class="theme-icon-dark">\u{1F319}</span><span class="theme-icon-light">\u2600</span></button>
|
|
363
|
+
<button class="auth-theme-toggle" onclick="toggleLang()" aria-label="Switch language"><span data-i18n="lang.switch">EN</span></button>
|
|
364
|
+
</div>
|
|
224
365
|
<div class="auth-card">
|
|
225
366
|
<div class="logo">\u{1F99E}</div>
|
|
226
|
-
<h1>OpenClaw Memory</h1>
|
|
227
|
-
<p style="font-size:12px;color:var(--text-sec);margin-bottom:6px">Powered by MemOS</p>
|
|
228
|
-
<p>Set a password to protect your memories</p>
|
|
229
|
-
<input type="password" id="setupPw" placeholder="Enter a password (4+ characters)" autofocus>
|
|
230
|
-
<input type="password" id="setupPw2" placeholder="Confirm password">
|
|
231
|
-
<button class="btn-auth" onclick="doSetup()">Set Password & Enter</button>
|
|
367
|
+
<h1 data-i18n="title">OpenClaw Memory</h1>
|
|
368
|
+
<p style="font-size:12px;color:var(--text-sec);margin-bottom:6px" data-i18n="subtitle">Powered by MemOS</p>
|
|
369
|
+
<p data-i18n="setup.desc">Set a password to protect your memories</p>
|
|
370
|
+
<input type="password" id="setupPw" data-i18n-ph="setup.pw" placeholder="Enter a password (4+ characters)" autofocus>
|
|
371
|
+
<input type="password" id="setupPw2" data-i18n-ph="setup.pw2" placeholder="Confirm password">
|
|
372
|
+
<button class="btn-auth" onclick="doSetup()" data-i18n="setup.btn">Set Password & Enter</button>
|
|
232
373
|
<div class="error-msg" id="setupErr"></div>
|
|
233
374
|
</div>
|
|
234
375
|
</div>
|
|
235
376
|
|
|
236
377
|
<!-- ─── Auth: Login ─── -->
|
|
237
378
|
<div id="loginScreen" class="auth-screen" style="display:none">
|
|
238
|
-
<
|
|
379
|
+
<div class="auth-top-actions">
|
|
380
|
+
<button class="auth-theme-toggle" onclick="toggleViewerTheme()" title="Toggle light/dark" aria-label="Toggle theme"><span class="theme-icon-dark">\u{1F319}</span><span class="theme-icon-light">\u2600</span></button>
|
|
381
|
+
<button class="auth-theme-toggle" onclick="toggleLang()" aria-label="Switch language"><span data-i18n="lang.switch">EN</span></button>
|
|
382
|
+
</div>
|
|
239
383
|
<div class="auth-card">
|
|
240
384
|
<div class="logo">\u{1F99E}</div>
|
|
241
|
-
<h1>OpenClaw Memory</h1>
|
|
242
|
-
<p style="font-size:12px;color:var(--text-sec);margin-bottom:6px">Powered by MemOS</p>
|
|
243
|
-
<p>Enter your password to access memories</p>
|
|
385
|
+
<h1 data-i18n="title">OpenClaw Memory</h1>
|
|
386
|
+
<p style="font-size:12px;color:var(--text-sec);margin-bottom:6px" data-i18n="subtitle">Powered by MemOS</p>
|
|
387
|
+
<p data-i18n="login.desc">Enter your password to access memories</p>
|
|
244
388
|
<div id="loginForm">
|
|
245
|
-
<input type="password" id="loginPw" placeholder="Password" autofocus>
|
|
246
|
-
<button class="btn-auth" onclick="doLogin()">Unlock</button>
|
|
389
|
+
<input type="password" id="loginPw" data-i18n-ph="login.pw" placeholder="Password" autofocus>
|
|
390
|
+
<button class="btn-auth" onclick="doLogin()" data-i18n="login.btn">Unlock</button>
|
|
247
391
|
<div class="error-msg" id="loginErr"></div>
|
|
248
|
-
<button class="btn-text" style="margin-top:12px;font-size:13px;color:var(--text-sec)" onclick="showResetForm()">Forgot password?</button>
|
|
392
|
+
<button class="btn-text" style="margin-top:12px;font-size:13px;color:var(--text-sec)" onclick="showResetForm()" data-i18n="login.forgot">Forgot password?</button>
|
|
249
393
|
</div>
|
|
250
394
|
<div id="resetForm" style="display:none">
|
|
251
395
|
<div class="reset-guide">
|
|
252
396
|
<div class="reset-step">
|
|
253
397
|
<div class="step-num">1</div>
|
|
254
398
|
<div class="step-body">
|
|
255
|
-
<div class="step-title">Open Terminal</div>
|
|
256
|
-
<div class="step-desc">Run the following command to get your reset token (use the pattern below so you get the line that contains the token):</div>
|
|
399
|
+
<div class="step-title" data-i18n="reset.step1.title">Open Terminal</div>
|
|
400
|
+
<div class="step-desc" data-i18n="reset.step1.desc">Run the following command to get your reset token (use the pattern below so you get the line that contains the token):</div>
|
|
257
401
|
<div class="cmd-box" onclick="copyCmd(this)">
|
|
258
402
|
<code>grep "password reset token:" /tmp/openclaw/openclaw-*.log ~/.openclaw/logs/gateway.log 2>/dev/null | tail -1</code>
|
|
259
|
-
<span class="copy-hint">Click to copy</span>
|
|
403
|
+
<span class="copy-hint" data-i18n="copy.hint">Click to copy</span>
|
|
260
404
|
</div>
|
|
261
405
|
</div>
|
|
262
406
|
</div>
|
|
263
407
|
<div class="reset-step">
|
|
264
408
|
<div class="step-num">2</div>
|
|
265
409
|
<div class="step-body">
|
|
266
|
-
<div class="step-title">Find the token</div>
|
|
267
|
-
<div class="step-desc">In the output, find <span style="font-family:monospace;font-size:12px;color:var(--pri)">password reset token: <strong>a1b2c3d4e5f6...</strong></span> (plain line or inside JSON). Copy the 32-character hex string after the colon.</div>
|
|
410
|
+
<div class="step-title" data-i18n="reset.step2.title">Find the token</div>
|
|
411
|
+
<div class="step-desc" id="resetStep2Desc">In the output, find <span style="font-family:monospace;font-size:12px;color:var(--pri)">password reset token: <strong>a1b2c3d4e5f6...</strong></span> (plain line or inside JSON). Copy the 32-character hex string after the colon.</div>
|
|
268
412
|
</div>
|
|
269
413
|
</div>
|
|
270
414
|
<div class="reset-step">
|
|
271
415
|
<div class="step-num">3</div>
|
|
272
416
|
<div class="step-body">
|
|
273
|
-
<div class="step-title">Paste & reset</div>
|
|
274
|
-
<div class="step-desc">Paste the token below and set your new password.</div>
|
|
417
|
+
<div class="step-title" data-i18n="reset.step3.title">Paste & reset</div>
|
|
418
|
+
<div class="step-desc" data-i18n="reset.step3.desc">Paste the token below and set your new password.</div>
|
|
275
419
|
</div>
|
|
276
420
|
</div>
|
|
277
421
|
</div>
|
|
278
|
-
<input type="text" id="resetToken" placeholder="Paste reset token here" style="margin-bottom:8px;font-family:monospace">
|
|
279
|
-
<input type="password" id="resetNewPw" placeholder="New password (4+ characters)">
|
|
280
|
-
<input type="password" id="resetNewPw2" placeholder="Confirm new password">
|
|
281
|
-
<button class="btn-auth" onclick="doReset()">Reset Password</button>
|
|
422
|
+
<input type="text" id="resetToken" data-i18n-ph="reset.token" placeholder="Paste reset token here" style="margin-bottom:8px;font-family:monospace">
|
|
423
|
+
<input type="password" id="resetNewPw" data-i18n-ph="reset.newpw" placeholder="New password (4+ characters)">
|
|
424
|
+
<input type="password" id="resetNewPw2" data-i18n-ph="reset.newpw2" placeholder="Confirm new password">
|
|
425
|
+
<button class="btn-auth" onclick="doReset()" data-i18n="reset.btn">Reset Password</button>
|
|
282
426
|
<div class="error-msg" id="resetErr"></div>
|
|
283
|
-
<button class="btn-text" style="margin-top:12px;font-size:13px;color:var(--text-sec)" onclick="showLoginForm()">\u2190 Back to login</button>
|
|
427
|
+
<button class="btn-text" style="margin-top:12px;font-size:13px;color:var(--text-sec)" onclick="showLoginForm()" data-i18n="reset.back">\u2190 Back to login</button>
|
|
284
428
|
</div>
|
|
285
429
|
</div>
|
|
286
430
|
</div>
|
|
@@ -290,78 +434,153 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
290
434
|
<div class="topbar">
|
|
291
435
|
<div class="brand">
|
|
292
436
|
<div class="icon">\u{1F99E}</div>
|
|
293
|
-
<span
|
|
437
|
+
<span data-i18n="title">OpenClaw Memory</span>
|
|
438
|
+
</div>
|
|
439
|
+
<div class="topbar-center">
|
|
440
|
+
<nav class="nav-tabs">
|
|
441
|
+
<button class="tab active" data-view="memories" onclick="switchView('memories')" data-i18n="tab.memories">\u{1F4DA} Memories</button>
|
|
442
|
+
<button class="tab" data-view="tasks" onclick="switchView('tasks')" data-i18n="tab.tasks">\u{1F4CB} Tasks</button>
|
|
443
|
+
<button class="tab" data-view="analytics" onclick="switchView('analytics')" data-i18n="tab.analytics">\u{1F4CA} Analytics</button>
|
|
444
|
+
</nav>
|
|
294
445
|
</div>
|
|
295
446
|
<div class="actions">
|
|
447
|
+
<button class="btn btn-icon" onclick="toggleLang()" aria-label="Switch language" style="font-size:12px;font-weight:700;padding:4px 8px"><span data-i18n="lang.switch">EN</span></button>
|
|
296
448
|
<button class="btn btn-icon theme-toggle" onclick="toggleViewerTheme()" title="Toggle light/dark" aria-label="Toggle theme"><span class="theme-icon-dark">\u{1F319}</span><span class="theme-icon-light">\u2600</span></button>
|
|
297
|
-
<button class="btn btn-
|
|
298
|
-
<button class="btn" onclick="
|
|
299
|
-
<button class="btn btn-danger" onclick="clearAll()">Clear All</button>
|
|
300
|
-
<button class="btn btn-text" onclick="doLogout()">Logout</button>
|
|
449
|
+
<button class="btn btn-ghost btn-sm" onclick="loadAll()" data-i18n="refresh">\u21BB Refresh</button>
|
|
450
|
+
<button class="btn btn-ghost btn-sm" onclick="doLogout()" data-i18n="logout">Logout</button>
|
|
301
451
|
</div>
|
|
302
452
|
</div>
|
|
303
453
|
|
|
304
454
|
<div class="main-content">
|
|
305
455
|
<div class="sidebar" id="sidebar">
|
|
306
456
|
<div class="stats-grid" id="statsGrid">
|
|
307
|
-
<div class="stat-card pri"><div class="stat-value" id="statTotal">-</div><div class="stat-label">Memories</div></div>
|
|
308
|
-
<div class="stat-card green"><div class="stat-value" id="statSessions">-</div><div class="stat-label">Sessions</div></div>
|
|
309
|
-
<div class="stat-card amber"><div class="stat-value" id="statEmbeddings">-</div><div class="stat-label">Embeddings</div></div>
|
|
310
|
-
<div class="stat-card rose"><div class="stat-value" id="statTimeSpan">-</div><div class="stat-label">Days</div></div>
|
|
457
|
+
<div class="stat-card pri"><div class="stat-value" id="statTotal">-</div><div class="stat-label" data-i18n="stat.memories">Memories</div></div>
|
|
458
|
+
<div class="stat-card green"><div class="stat-value" id="statSessions">-</div><div class="stat-label" data-i18n="stat.sessions">Sessions</div></div>
|
|
459
|
+
<div class="stat-card amber"><div class="stat-value" id="statEmbeddings">-</div><div class="stat-label" data-i18n="stat.embeddings">Embeddings</div></div>
|
|
460
|
+
<div class="stat-card rose"><div class="stat-value" id="statTimeSpan">-</div><div class="stat-label" data-i18n="stat.days">Days</div></div>
|
|
311
461
|
</div>
|
|
312
462
|
<div id="embeddingStatus"></div>
|
|
313
|
-
<div class="section-title">Sessions</div>
|
|
463
|
+
<div class="section-title" data-i18n="sidebar.sessions">Sessions</div>
|
|
314
464
|
<div class="session-list" id="sessionList"></div>
|
|
465
|
+
<button class="btn btn-sm btn-ghost" style="width:100%;margin-top:20px;justify-content:center;color:var(--text-muted);font-size:11px" onclick="clearAll()" data-i18n="sidebar.clear">\u{1F5D1} Clear All Data</button>
|
|
315
466
|
</div>
|
|
316
467
|
|
|
468
|
+
<div class="feed-wrap" id="feedWrap">
|
|
317
469
|
<div class="feed">
|
|
318
470
|
<div class="search-bar">
|
|
319
471
|
<span class="search-icon">\u{1F50D}</span>
|
|
320
|
-
<input type="text" id="searchInput" placeholder="Search memories (supports semantic search)..." oninput="debounceSearch()">
|
|
472
|
+
<input type="text" id="searchInput" data-i18n-ph="search.placeholder" placeholder="Search memories (supports semantic search)..." oninput="debounceSearch()">
|
|
321
473
|
</div>
|
|
322
474
|
<div class="search-meta" id="searchMeta"></div>
|
|
323
475
|
<div class="filter-bar" id="filterBar">
|
|
324
|
-
<button class="filter-chip active" data-role="" onclick="setRoleFilter(this,'')">All</button>
|
|
476
|
+
<button class="filter-chip active" data-role="" onclick="setRoleFilter(this,'')" data-i18n="filter.all">All</button>
|
|
325
477
|
<button class="filter-chip" data-role="user" onclick="setRoleFilter(this,'user')">User</button>
|
|
326
478
|
<button class="filter-chip" data-role="assistant" onclick="setRoleFilter(this,'assistant')">Assistant</button>
|
|
327
479
|
<button class="filter-chip" data-role="system" onclick="setRoleFilter(this,'system')">System</button>
|
|
328
480
|
<span class="filter-sep"></span>
|
|
329
481
|
<select id="filterKind" class="filter-select" onchange="applyFilters()">
|
|
330
|
-
<option value="">All kinds</option>
|
|
331
|
-
<option value="paragraph">Paragraph</option>
|
|
332
|
-
<option value="code_block">Code</option>
|
|
333
|
-
<option value="dialog">Dialog</option>
|
|
334
|
-
<option value="list">List</option>
|
|
335
|
-
<option value="error_stack">Error</option>
|
|
336
|
-
<option value="command">Command</option>
|
|
482
|
+
<option value="" data-i18n="filter.allkinds">All kinds</option>
|
|
483
|
+
<option value="paragraph" data-i18n="filter.paragraph">Paragraph</option>
|
|
484
|
+
<option value="code_block" data-i18n="filter.code">Code</option>
|
|
485
|
+
<option value="dialog" data-i18n="filter.dialog">Dialog</option>
|
|
486
|
+
<option value="list" data-i18n="filter.list">List</option>
|
|
487
|
+
<option value="error_stack" data-i18n="filter.error">Error</option>
|
|
488
|
+
<option value="command" data-i18n="filter.command">Command</option>
|
|
337
489
|
</select>
|
|
338
490
|
<select id="filterSort" class="filter-select" onchange="applyFilters()">
|
|
339
|
-
<option value="newest">Newest first</option>
|
|
340
|
-
<option value="oldest">Oldest first</option>
|
|
491
|
+
<option value="newest" data-i18n="filter.newest">Newest first</option>
|
|
492
|
+
<option value="oldest" data-i18n="filter.oldest">Oldest first</option>
|
|
341
493
|
</select>
|
|
342
494
|
</div>
|
|
343
495
|
<div class="date-filter">
|
|
344
|
-
<label>From</label><input type="datetime-local" id="dateFrom" step="1" onchange="applyFilters()">
|
|
345
|
-
<label>To</label><input type="datetime-local" id="dateTo" step="1" onchange="applyFilters()">
|
|
346
|
-
<button class="btn btn-sm btn-text" onclick="clearDateFilter()">Clear</button>
|
|
496
|
+
<label data-i18n="filter.from">From</label><input type="datetime-local" id="dateFrom" step="1" onchange="applyFilters()">
|
|
497
|
+
<label data-i18n="filter.to">To</label><input type="datetime-local" id="dateTo" step="1" onchange="applyFilters()">
|
|
498
|
+
<button class="btn btn-sm btn-text" onclick="clearDateFilter()" data-i18n="filter.clear">Clear</button>
|
|
347
499
|
</div>
|
|
348
500
|
<div class="memory-list" id="memoryList"><div class="spinner"></div></div>
|
|
349
501
|
<div class="pagination" id="pagination"></div>
|
|
350
502
|
</div>
|
|
503
|
+
</div>
|
|
504
|
+
<div class="tasks-view" id="tasksView">
|
|
505
|
+
<div class="tasks-header">
|
|
506
|
+
<div class="tasks-stats">
|
|
507
|
+
<div class="tasks-stat"><span class="tasks-stat-value" id="tasksTotalCount">-</span><span class="tasks-stat-label" data-i18n="tasks.total">Total Tasks</span></div>
|
|
508
|
+
<div class="tasks-stat"><span class="tasks-stat-value" id="tasksActiveCount">-</span><span class="tasks-stat-label" data-i18n="tasks.active">Active</span></div>
|
|
509
|
+
<div class="tasks-stat"><span class="tasks-stat-value" id="tasksCompletedCount">-</span><span class="tasks-stat-label" data-i18n="tasks.completed">Completed</span></div>
|
|
510
|
+
<div class="tasks-stat"><span class="tasks-stat-value" id="tasksSkippedCount">-</span><span class="tasks-stat-label" data-i18n="tasks.status.skipped">Skipped</span></div>
|
|
511
|
+
</div>
|
|
512
|
+
<div class="tasks-filters">
|
|
513
|
+
<button class="filter-chip active" data-task-status="" onclick="setTaskStatusFilter(this,'')" data-i18n="filter.all">All</button>
|
|
514
|
+
<button class="filter-chip" data-task-status="active" onclick="setTaskStatusFilter(this,'active')" data-i18n="tasks.status.active">Active</button>
|
|
515
|
+
<button class="filter-chip" data-task-status="completed" onclick="setTaskStatusFilter(this,'completed')" data-i18n="tasks.status.completed">Completed</button>
|
|
516
|
+
<button class="filter-chip" data-task-status="skipped" onclick="setTaskStatusFilter(this,'skipped')" data-i18n="tasks.status.skipped">Skipped</button>
|
|
517
|
+
<button class="btn btn-sm btn-ghost" onclick="loadTasks()" style="margin-left:auto" data-i18n="refresh">\u21BB Refresh</button>
|
|
518
|
+
</div>
|
|
519
|
+
</div>
|
|
520
|
+
<div class="tasks-list" id="tasksList"><div class="spinner"></div></div>
|
|
521
|
+
<div class="pagination" id="tasksPagination"></div>
|
|
522
|
+
<div class="task-detail-overlay" id="taskDetailOverlay" onclick="closeTaskDetail(event)">
|
|
523
|
+
<div class="task-detail-panel" onclick="event.stopPropagation()">
|
|
524
|
+
<div class="task-detail-header">
|
|
525
|
+
<h2 id="taskDetailTitle"></h2>
|
|
526
|
+
<button class="btn btn-icon" onclick="closeTaskDetail()" title="Close">\u2715</button>
|
|
527
|
+
</div>
|
|
528
|
+
<div class="task-detail-meta" id="taskDetailMeta"></div>
|
|
529
|
+
<div class="task-detail-summary" id="taskDetailSummary"></div>
|
|
530
|
+
<div class="task-detail-chunks-title" data-i18n="tasks.chunks">Related Memories</div>
|
|
531
|
+
<div class="task-detail-chunks" id="taskDetailChunks"></div>
|
|
532
|
+
</div>
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
<div class="analytics-view" id="analyticsView">
|
|
536
|
+
<div class="metrics-toolbar">
|
|
537
|
+
<span style="font-size:12px;color:var(--text-sec);font-weight:600" data-i18n="range">Range</span>
|
|
538
|
+
<button class="range-btn" data-days="7" onclick="setMetricsDays(7)">7 <span data-i18n="range.days">days</span></button>
|
|
539
|
+
<button class="range-btn active" data-days="30" onclick="setMetricsDays(30)">30 <span data-i18n="range.days">days</span></button>
|
|
540
|
+
<button class="range-btn" data-days="90" onclick="setMetricsDays(90)">90 <span data-i18n="range.days">days</span></button>
|
|
541
|
+
<button class="btn btn-sm" onclick="loadMetrics()" style="margin-left:auto" data-i18n="refresh">\u21BB Refresh</button>
|
|
542
|
+
</div>
|
|
543
|
+
<div class="analytics-cards" id="analyticsCards">
|
|
544
|
+
<div class="analytics-card"><div class="ac-value" id="mTotal">-</div><div class="ac-label" data-i18n="analytics.total">Total Memories</div></div>
|
|
545
|
+
<div class="analytics-card green"><div class="ac-value" id="mTodayWrites">-</div><div class="ac-label" data-i18n="analytics.writes">Writes Today</div></div>
|
|
546
|
+
<div class="analytics-card violet"><div class="ac-value" id="mTodayCalls">-</div><div class="ac-label" data-i18n="analytics.calls">Viewer Calls Today</div></div>
|
|
547
|
+
<div class="analytics-card"><div class="ac-value" id="mSessions">-</div><div class="ac-label" data-i18n="analytics.sessions">Sessions</div></div>
|
|
548
|
+
<div class="analytics-card amber"><div class="ac-value" id="mEmbeddings">-</div><div class="ac-label" data-i18n="analytics.embeddings">Embeddings</div></div>
|
|
549
|
+
</div>
|
|
550
|
+
<div class="analytics-section">
|
|
551
|
+
<h3><span class="icon">\u{1F4CA}</span> <span data-i18n="chart.writes">Memory Writes per Day</span></h3>
|
|
552
|
+
<div class="chart-bars" id="chartWrites"></div>
|
|
553
|
+
</div>
|
|
554
|
+
<div class="analytics-section">
|
|
555
|
+
<h3><span class="icon">\u{1F50D}</span> <span data-i18n="chart.calls">Viewer API Calls per Day (List / Search)</span></h3>
|
|
556
|
+
<div class="chart-bars" id="chartCalls"></div>
|
|
557
|
+
<div class="chart-legend"><span><span class="dot pri"></span> <span data-i18n="chart.list">List</span></span><span><span class="dot violet"></span> <span data-i18n="chart.search">Search</span></span></div>
|
|
558
|
+
</div>
|
|
559
|
+
<div class="breakdown-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:24px">
|
|
560
|
+
<div class="analytics-section">
|
|
561
|
+
<h3><span class="icon">\u{1F464}</span> <span data-i18n="breakdown.role">By Role</span></h3>
|
|
562
|
+
<div id="breakdownRole"></div>
|
|
563
|
+
</div>
|
|
564
|
+
<div class="analytics-section">
|
|
565
|
+
<h3><span class="icon">\u{1F4DD}</span> <span data-i18n="breakdown.kind">By Kind</span></h3>
|
|
566
|
+
<div id="breakdownKind"></div>
|
|
567
|
+
</div>
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
351
570
|
</div>
|
|
352
571
|
</div>
|
|
353
572
|
|
|
354
573
|
<!-- ─── Memory Modal ─── -->
|
|
355
574
|
<div class="modal-overlay" id="modalOverlay">
|
|
356
575
|
<div class="modal">
|
|
357
|
-
<h2 id="modalTitle">New Memory</h2>
|
|
358
|
-
<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>
|
|
359
|
-
<div class="form-group"><label>Content</label><textarea id="mContent" rows="4" placeholder="Memory content..."></textarea></div>
|
|
360
|
-
<div class="form-group"><label>Summary</label><input type="text" id="mSummary" placeholder="Brief summary (optional)"></div>
|
|
361
|
-
<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>
|
|
576
|
+
<h2 id="modalTitle" data-i18n="modal.new">New Memory</h2>
|
|
577
|
+
<div class="form-group"><label data-i18n="modal.role">Role</label><select id="mRole"><option value="user">User</option><option value="assistant">Assistant</option><option value="system">System</option></select></div>
|
|
578
|
+
<div class="form-group"><label data-i18n="modal.content">Content</label><textarea id="mContent" rows="4" data-i18n-ph="modal.content.ph" placeholder="Memory content..."></textarea></div>
|
|
579
|
+
<div class="form-group"><label data-i18n="modal.summary">Summary</label><input type="text" id="mSummary" data-i18n-ph="modal.summary.ph" placeholder="Brief summary (optional)"></div>
|
|
580
|
+
<div class="form-group"><label data-i18n="modal.kind">Kind</label><select id="mKind"><option value="paragraph" data-i18n="filter.paragraph">Paragraph</option><option value="code" data-i18n="filter.code">Code</option><option value="dialog" data-i18n="filter.dialog">Dialog</option></select></div>
|
|
362
581
|
<div class="modal-actions">
|
|
363
|
-
<button class="btn" onclick="closeModal()">Cancel</button>
|
|
364
|
-
<button class="btn btn-primary" id="modalSubmit" onclick="submitModal()">Create</button>
|
|
582
|
+
<button class="btn btn-ghost" onclick="closeModal()" data-i18n="modal.cancel">Cancel</button>
|
|
583
|
+
<button class="btn btn-primary" id="modalSubmit" onclick="submitModal()" data-i18n="modal.create">Create</button>
|
|
365
584
|
</div>
|
|
366
585
|
</div>
|
|
367
586
|
</div>
|
|
@@ -370,7 +589,276 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
370
589
|
<div class="toast-container" id="toasts"></div>
|
|
371
590
|
|
|
372
591
|
<script>
|
|
373
|
-
let activeSession=null,activeRole='',editingId=null,searchTimer=null,memoryCache={},currentPage=1,totalPages=1,totalCount=0,PAGE_SIZE=30;
|
|
592
|
+
let activeSession=null,activeRole='',editingId=null,searchTimer=null,memoryCache={},currentPage=1,totalPages=1,totalCount=0,PAGE_SIZE=30,metricsDays=30;
|
|
593
|
+
|
|
594
|
+
/* ─── i18n ─── */
|
|
595
|
+
const I18N={
|
|
596
|
+
en:{
|
|
597
|
+
'title':'OpenClaw Memory',
|
|
598
|
+
'subtitle':'Powered by MemOS',
|
|
599
|
+
'setup.desc':'Set a password to protect your memories',
|
|
600
|
+
'setup.pw':'Enter a password (4+ characters)',
|
|
601
|
+
'setup.pw2':'Confirm password',
|
|
602
|
+
'setup.btn':'Set Password & Enter',
|
|
603
|
+
'setup.err.short':'Password must be at least 4 characters',
|
|
604
|
+
'setup.err.mismatch':'Passwords do not match',
|
|
605
|
+
'setup.err.fail':'Setup failed',
|
|
606
|
+
'login.desc':'Enter your password to access memories',
|
|
607
|
+
'login.pw':'Password',
|
|
608
|
+
'login.btn':'Unlock',
|
|
609
|
+
'login.err':'Incorrect password',
|
|
610
|
+
'login.forgot':'Forgot password?',
|
|
611
|
+
'reset.step1.title':'Open Terminal',
|
|
612
|
+
'reset.step1.desc':'Run the following command to get your reset token (use the pattern below so you get the line that contains the token):',
|
|
613
|
+
'reset.step2.title':'Find the token',
|
|
614
|
+
'reset.step2.desc.pre':'In the output, find ',
|
|
615
|
+
'reset.step2.desc.post':' (plain line or inside JSON). Copy the 32-character hex string after the colon.',
|
|
616
|
+
'reset.step3.title':'Paste & reset',
|
|
617
|
+
'reset.step3.desc':'Paste the token below and set your new password.',
|
|
618
|
+
'reset.token':'Paste reset token here',
|
|
619
|
+
'reset.newpw':'New password (4+ characters)',
|
|
620
|
+
'reset.newpw2':'Confirm new password',
|
|
621
|
+
'reset.btn':'Reset Password',
|
|
622
|
+
'reset.err.token':'Please enter the reset token',
|
|
623
|
+
'reset.err.short':'Password must be at least 4 characters',
|
|
624
|
+
'reset.err.mismatch':'Passwords do not match',
|
|
625
|
+
'reset.err.fail':'Reset failed',
|
|
626
|
+
'reset.back':'\\u2190 Back to login',
|
|
627
|
+
'copy.hint':'Click to copy',
|
|
628
|
+
'copy.done':'Copied!',
|
|
629
|
+
'tab.memories':'\\u{1F4DA} Memories',
|
|
630
|
+
'tab.tasks':'\\u{1F4CB} Tasks',
|
|
631
|
+
'tab.analytics':'\\u{1F4CA} Analytics',
|
|
632
|
+
'tasks.total':'Total Tasks',
|
|
633
|
+
'tasks.active':'Active',
|
|
634
|
+
'tasks.completed':'Completed',
|
|
635
|
+
'tasks.status.active':'Active',
|
|
636
|
+
'tasks.status.completed':'Completed',
|
|
637
|
+
'tasks.status.skipped':'Skipped',
|
|
638
|
+
'tasks.empty':'No tasks yet. Tasks are automatically created as you converse.',
|
|
639
|
+
'tasks.loading':'Loading...',
|
|
640
|
+
'tasks.untitled':'Untitled Task',
|
|
641
|
+
'tasks.chunks':'Related Memories',
|
|
642
|
+
'tasks.nochunks':'No memories in this task yet.',
|
|
643
|
+
'tasks.skipped.default':'This conversation was too brief to generate a summary. It will not appear in search results.',
|
|
644
|
+
'refresh':'\\u21BB Refresh',
|
|
645
|
+
'logout':'Logout',
|
|
646
|
+
'stat.memories':'Memories',
|
|
647
|
+
'stat.sessions':'Sessions',
|
|
648
|
+
'stat.embeddings':'Embeddings',
|
|
649
|
+
'stat.days':'Days',
|
|
650
|
+
'sidebar.sessions':'Sessions',
|
|
651
|
+
'sidebar.allsessions':'All Sessions',
|
|
652
|
+
'sidebar.clear':'\\u{1F5D1} Clear All Data',
|
|
653
|
+
'search.placeholder':'Search memories (supports semantic search)...',
|
|
654
|
+
'search.meta.total':' memories total',
|
|
655
|
+
'search.meta.semantic':' semantic',
|
|
656
|
+
'search.meta.text':' text',
|
|
657
|
+
'search.meta.results':' results',
|
|
658
|
+
'filter.all':'All',
|
|
659
|
+
'filter.allkinds':'All kinds',
|
|
660
|
+
'filter.paragraph':'Paragraph',
|
|
661
|
+
'filter.code':'Code',
|
|
662
|
+
'filter.dialog':'Dialog',
|
|
663
|
+
'filter.list':'List',
|
|
664
|
+
'filter.error':'Error',
|
|
665
|
+
'filter.command':'Command',
|
|
666
|
+
'filter.newest':'Newest first',
|
|
667
|
+
'filter.oldest':'Oldest first',
|
|
668
|
+
'filter.from':'From',
|
|
669
|
+
'filter.to':'To',
|
|
670
|
+
'filter.clear':'Clear',
|
|
671
|
+
'empty.text':'No memories found',
|
|
672
|
+
'card.expand':'Expand',
|
|
673
|
+
'card.edit':'Edit',
|
|
674
|
+
'card.delete':'Delete',
|
|
675
|
+
'pagination.total':' total',
|
|
676
|
+
'range':'Range',
|
|
677
|
+
'range.days':'days',
|
|
678
|
+
'analytics.total':'Total Memories',
|
|
679
|
+
'analytics.writes':'Writes Today',
|
|
680
|
+
'analytics.calls':'Viewer Calls Today',
|
|
681
|
+
'analytics.sessions':'Sessions',
|
|
682
|
+
'analytics.embeddings':'Embeddings',
|
|
683
|
+
'chart.writes':'Memory Writes per Day',
|
|
684
|
+
'chart.calls':'Viewer API Calls per Day (List / Search)',
|
|
685
|
+
'chart.nodata':'No data in this range',
|
|
686
|
+
'chart.nocalls':'No viewer calls in this range',
|
|
687
|
+
'chart.list':'List',
|
|
688
|
+
'chart.search':'Search',
|
|
689
|
+
'breakdown.role':'By Role',
|
|
690
|
+
'breakdown.kind':'By Kind',
|
|
691
|
+
'modal.new':'New Memory',
|
|
692
|
+
'modal.edit':'Edit Memory',
|
|
693
|
+
'modal.role':'Role',
|
|
694
|
+
'modal.content':'Content',
|
|
695
|
+
'modal.content.ph':'Memory content...',
|
|
696
|
+
'modal.summary':'Summary',
|
|
697
|
+
'modal.summary.ph':'Brief summary (optional)',
|
|
698
|
+
'modal.kind':'Kind',
|
|
699
|
+
'modal.cancel':'Cancel',
|
|
700
|
+
'modal.create':'Create',
|
|
701
|
+
'modal.save':'Save',
|
|
702
|
+
'modal.err.empty':'Please enter content',
|
|
703
|
+
'toast.created':'Memory created',
|
|
704
|
+
'toast.updated':'Memory updated',
|
|
705
|
+
'toast.deleted':'Memory deleted',
|
|
706
|
+
'toast.opfail':'Operation failed',
|
|
707
|
+
'toast.delfail':'Delete failed',
|
|
708
|
+
'toast.cleared':'All memories cleared',
|
|
709
|
+
'toast.clearfail':'Clear failed',
|
|
710
|
+
'toast.notfound':'Memory not found in cache',
|
|
711
|
+
'confirm.delete':'Delete this memory?',
|
|
712
|
+
'confirm.clearall':'Delete ALL memories? This cannot be undone.',
|
|
713
|
+
'confirm.clearall2':'Are you absolutely sure?',
|
|
714
|
+
'embed.on':'Embedding: ',
|
|
715
|
+
'embed.off':'No embedding model',
|
|
716
|
+
'lang.switch':'中'
|
|
717
|
+
},
|
|
718
|
+
zh:{
|
|
719
|
+
'title':'OpenClaw 记忆',
|
|
720
|
+
'subtitle':'由 MemOS 驱动',
|
|
721
|
+
'setup.desc':'设置密码以保护你的记忆数据',
|
|
722
|
+
'setup.pw':'输入密码(至少4位)',
|
|
723
|
+
'setup.pw2':'确认密码',
|
|
724
|
+
'setup.btn':'设置密码并进入',
|
|
725
|
+
'setup.err.short':'密码至少需要4个字符',
|
|
726
|
+
'setup.err.mismatch':'两次密码不一致',
|
|
727
|
+
'setup.err.fail':'设置失败',
|
|
728
|
+
'login.desc':'输入密码以访问记忆',
|
|
729
|
+
'login.pw':'密码',
|
|
730
|
+
'login.btn':'解锁',
|
|
731
|
+
'login.err':'密码错误',
|
|
732
|
+
'login.forgot':'忘记密码?',
|
|
733
|
+
'reset.step1.title':'打开终端',
|
|
734
|
+
'reset.step1.desc':'运行以下命令获取重置令牌:',
|
|
735
|
+
'reset.step2.title':'找到令牌',
|
|
736
|
+
'reset.step2.desc.pre':'在输出中找到 ',
|
|
737
|
+
'reset.step2.desc.post':'(纯文本行或 JSON 内)。复制冒号后的32位十六进制字符串。',
|
|
738
|
+
'reset.step3.title':'粘贴并重置',
|
|
739
|
+
'reset.step3.desc':'将令牌粘贴到下方并设置新密码。',
|
|
740
|
+
'reset.token':'在此粘贴重置令牌',
|
|
741
|
+
'reset.newpw':'新密码(至少4位)',
|
|
742
|
+
'reset.newpw2':'确认新密码',
|
|
743
|
+
'reset.btn':'重置密码',
|
|
744
|
+
'reset.err.token':'请输入重置令牌',
|
|
745
|
+
'reset.err.short':'密码至少需要4个字符',
|
|
746
|
+
'reset.err.mismatch':'两次密码不一致',
|
|
747
|
+
'reset.err.fail':'重置失败',
|
|
748
|
+
'reset.back':'\\u2190 返回登录',
|
|
749
|
+
'copy.hint':'点击复制',
|
|
750
|
+
'copy.done':'已复制!',
|
|
751
|
+
'tab.memories':'\\u{1F4DA} 记忆',
|
|
752
|
+
'tab.tasks':'\\u{1F4CB} 任务',
|
|
753
|
+
'tab.analytics':'\\u{1F4CA} 分析',
|
|
754
|
+
'tasks.total':'任务总数',
|
|
755
|
+
'tasks.active':'进行中',
|
|
756
|
+
'tasks.completed':'已完成',
|
|
757
|
+
'tasks.status.active':'进行中',
|
|
758
|
+
'tasks.status.completed':'已完成',
|
|
759
|
+
'tasks.status.skipped':'已跳过',
|
|
760
|
+
'tasks.empty':'暂无任务。任务会随着对话自动创建。',
|
|
761
|
+
'tasks.loading':'加载中...',
|
|
762
|
+
'tasks.untitled':'未命名任务',
|
|
763
|
+
'tasks.chunks':'关联记忆',
|
|
764
|
+
'tasks.nochunks':'此任务暂无关联记忆。',
|
|
765
|
+
'tasks.skipped.default':'对话内容过少,未生成摘要。该任务不会出现在检索结果中。',
|
|
766
|
+
'refresh':'\\u21BB 刷新',
|
|
767
|
+
'logout':'退出',
|
|
768
|
+
'stat.memories':'记忆',
|
|
769
|
+
'stat.sessions':'会话',
|
|
770
|
+
'stat.embeddings':'嵌入',
|
|
771
|
+
'stat.days':'天数',
|
|
772
|
+
'sidebar.sessions':'会话列表',
|
|
773
|
+
'sidebar.allsessions':'全部会话',
|
|
774
|
+
'sidebar.clear':'\\u{1F5D1} 清除所有数据',
|
|
775
|
+
'search.placeholder':'搜索记忆(支持语义搜索)...',
|
|
776
|
+
'search.meta.total':' 条记忆',
|
|
777
|
+
'search.meta.semantic':' 语义',
|
|
778
|
+
'search.meta.text':' 文本',
|
|
779
|
+
'search.meta.results':' 条结果',
|
|
780
|
+
'filter.all':'全部',
|
|
781
|
+
'filter.allkinds':'所有类型',
|
|
782
|
+
'filter.paragraph':'段落',
|
|
783
|
+
'filter.code':'代码',
|
|
784
|
+
'filter.dialog':'对话',
|
|
785
|
+
'filter.list':'列表',
|
|
786
|
+
'filter.error':'错误',
|
|
787
|
+
'filter.command':'命令',
|
|
788
|
+
'filter.newest':'最新优先',
|
|
789
|
+
'filter.oldest':'最早优先',
|
|
790
|
+
'filter.from':'起始',
|
|
791
|
+
'filter.to':'截止',
|
|
792
|
+
'filter.clear':'清除',
|
|
793
|
+
'empty.text':'暂无记忆',
|
|
794
|
+
'card.expand':'展开',
|
|
795
|
+
'card.edit':'编辑',
|
|
796
|
+
'card.delete':'删除',
|
|
797
|
+
'pagination.total':' 条',
|
|
798
|
+
'range':'范围',
|
|
799
|
+
'range.days':'天',
|
|
800
|
+
'analytics.total':'总记忆数',
|
|
801
|
+
'analytics.writes':'今日写入',
|
|
802
|
+
'analytics.calls':'今日查看器调用',
|
|
803
|
+
'analytics.sessions':'会话数',
|
|
804
|
+
'analytics.embeddings':'嵌入数',
|
|
805
|
+
'chart.writes':'每日记忆写入',
|
|
806
|
+
'chart.calls':'每日查看器 API 调用(列表 / 搜索)',
|
|
807
|
+
'chart.nodata':'此范围内暂无数据',
|
|
808
|
+
'chart.nocalls':'此范围内暂无查看器调用',
|
|
809
|
+
'chart.list':'列表',
|
|
810
|
+
'chart.search':'搜索',
|
|
811
|
+
'breakdown.role':'按角色',
|
|
812
|
+
'breakdown.kind':'按类型',
|
|
813
|
+
'modal.new':'新建记忆',
|
|
814
|
+
'modal.edit':'编辑记忆',
|
|
815
|
+
'modal.role':'角色',
|
|
816
|
+
'modal.content':'内容',
|
|
817
|
+
'modal.content.ph':'记忆内容...',
|
|
818
|
+
'modal.summary':'摘要',
|
|
819
|
+
'modal.summary.ph':'简要摘要(可选)',
|
|
820
|
+
'modal.kind':'类型',
|
|
821
|
+
'modal.cancel':'取消',
|
|
822
|
+
'modal.create':'创建',
|
|
823
|
+
'modal.save':'保存',
|
|
824
|
+
'modal.err.empty':'请输入内容',
|
|
825
|
+
'toast.created':'记忆已创建',
|
|
826
|
+
'toast.updated':'记忆已更新',
|
|
827
|
+
'toast.deleted':'记忆已删除',
|
|
828
|
+
'toast.opfail':'操作失败',
|
|
829
|
+
'toast.delfail':'删除失败',
|
|
830
|
+
'toast.cleared':'所有记忆已清除',
|
|
831
|
+
'toast.clearfail':'清除失败',
|
|
832
|
+
'toast.notfound':'缓存中未找到此记忆',
|
|
833
|
+
'confirm.delete':'确定要删除这条记忆吗?',
|
|
834
|
+
'confirm.clearall':'确定要删除所有记忆?此操作不可撤销。',
|
|
835
|
+
'confirm.clearall2':'你真的确定吗?',
|
|
836
|
+
'embed.on':'嵌入模型:',
|
|
837
|
+
'embed.off':'无嵌入模型',
|
|
838
|
+
'lang.switch':'EN'
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
const LANG_KEY='memos-viewer-lang';
|
|
842
|
+
let curLang=localStorage.getItem(LANG_KEY)||(navigator.language.startsWith('zh')?'zh':'en');
|
|
843
|
+
function t(key){return (I18N[curLang]||I18N.en)[key]||key;}
|
|
844
|
+
function setLang(lang){curLang=lang;localStorage.setItem(LANG_KEY,lang);applyI18n();}
|
|
845
|
+
function toggleLang(){setLang(curLang==='zh'?'en':'zh');}
|
|
846
|
+
|
|
847
|
+
function applyI18n(){
|
|
848
|
+
document.querySelectorAll('[data-i18n]').forEach(el=>{
|
|
849
|
+
const key=el.getAttribute('data-i18n');
|
|
850
|
+
if(key) el.textContent=t(key);
|
|
851
|
+
});
|
|
852
|
+
document.querySelectorAll('[data-i18n-ph]').forEach(el=>{
|
|
853
|
+
const key=el.getAttribute('data-i18n-ph');
|
|
854
|
+
if(key) el.placeholder=t(key);
|
|
855
|
+
});
|
|
856
|
+
const step2=document.getElementById('resetStep2Desc');
|
|
857
|
+
if(step2) step2.innerHTML=t('reset.step2.desc.pre')+'<span style="font-family:monospace;font-size:12px;color:var(--pri)">password reset token: <strong>a1b2c3d4e5f6...</strong></span>'+t('reset.step2.desc.post');
|
|
858
|
+
document.title=t('title')+' - MemOS';
|
|
859
|
+
if(typeof loadStats==='function' && document.getElementById('app').style.display==='flex'){loadStats();}
|
|
860
|
+
if(document.querySelector('.analytics-view.show') && typeof loadMetrics==='function'){loadMetrics();}
|
|
861
|
+
}
|
|
374
862
|
|
|
375
863
|
/* ─── Auth flow ─── */
|
|
376
864
|
async function checkAuth(){
|
|
@@ -392,12 +880,12 @@ async function doSetup(){
|
|
|
392
880
|
const pw=document.getElementById('setupPw').value;
|
|
393
881
|
const pw2=document.getElementById('setupPw2').value;
|
|
394
882
|
const err=document.getElementById('setupErr');
|
|
395
|
-
if(pw.length<4){err.textContent='
|
|
396
|
-
if(pw!==pw2){err.textContent='
|
|
883
|
+
if(pw.length<4){err.textContent=t('setup.err.short');return}
|
|
884
|
+
if(pw!==pw2){err.textContent=t('setup.err.mismatch');return}
|
|
397
885
|
const r=await fetch('/api/auth/setup',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({password:pw})});
|
|
398
886
|
const d=await r.json();
|
|
399
887
|
if(d.ok){document.getElementById('setupScreen').style.display='none';enterApp();}
|
|
400
|
-
else{err.textContent=d.error||'
|
|
888
|
+
else{err.textContent=d.error||t('setup.err.fail')}
|
|
401
889
|
}
|
|
402
890
|
|
|
403
891
|
async function doLogin(){
|
|
@@ -406,7 +894,7 @@ async function doLogin(){
|
|
|
406
894
|
const r=await fetch('/api/auth/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({password:pw})});
|
|
407
895
|
const d=await r.json();
|
|
408
896
|
if(d.ok){document.getElementById('loginScreen').style.display='none';enterApp();}
|
|
409
|
-
else{err.textContent='
|
|
897
|
+
else{err.textContent=t('login.err');document.getElementById('loginPw').value='';document.getElementById('loginPw').focus();}
|
|
410
898
|
}
|
|
411
899
|
|
|
412
900
|
async function doLogout(){
|
|
@@ -430,8 +918,8 @@ function copyCmd(el){
|
|
|
430
918
|
const code=el.querySelector('code').textContent;
|
|
431
919
|
navigator.clipboard.writeText(code).then(()=>{
|
|
432
920
|
el.classList.add('copied');
|
|
433
|
-
el.querySelector('.copy-hint').textContent='
|
|
434
|
-
setTimeout(()=>{el.classList.remove('copied');el.querySelector('.copy-hint').textContent='
|
|
921
|
+
el.querySelector('.copy-hint').textContent=t('copy.done');
|
|
922
|
+
setTimeout(()=>{el.classList.remove('copied');el.querySelector('.copy-hint').textContent=t('copy.hint')},2000);
|
|
435
923
|
});
|
|
436
924
|
}
|
|
437
925
|
|
|
@@ -440,13 +928,13 @@ async function doReset(){
|
|
|
440
928
|
const pw=document.getElementById('resetNewPw').value;
|
|
441
929
|
const pw2=document.getElementById('resetNewPw2').value;
|
|
442
930
|
const err=document.getElementById('resetErr');
|
|
443
|
-
if(!token){err.textContent='
|
|
444
|
-
if(pw.length<4){err.textContent='
|
|
445
|
-
if(pw!==pw2){err.textContent='
|
|
931
|
+
if(!token){err.textContent=t('reset.err.token');return}
|
|
932
|
+
if(pw.length<4){err.textContent=t('reset.err.short');return}
|
|
933
|
+
if(pw!==pw2){err.textContent=t('reset.err.mismatch');return}
|
|
446
934
|
const r=await fetch('/api/auth/reset',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token,newPassword:pw})});
|
|
447
935
|
const d=await r.json();
|
|
448
936
|
if(d.ok){document.getElementById('loginScreen').style.display='none';enterApp();}
|
|
449
|
-
else{err.textContent=d.error||'
|
|
937
|
+
else{err.textContent=d.error||t('reset.err.fail')}
|
|
450
938
|
}
|
|
451
939
|
|
|
452
940
|
function enterApp(){
|
|
@@ -454,6 +942,281 @@ function enterApp(){
|
|
|
454
942
|
loadAll();
|
|
455
943
|
}
|
|
456
944
|
|
|
945
|
+
function switchView(view){
|
|
946
|
+
document.querySelectorAll('.nav-tabs .tab').forEach(t=>t.classList.toggle('active',t.dataset.view===view));
|
|
947
|
+
const feedWrap=document.getElementById('feedWrap');
|
|
948
|
+
const analyticsView=document.getElementById('analyticsView');
|
|
949
|
+
const tasksView=document.getElementById('tasksView');
|
|
950
|
+
feedWrap.classList.add('hide');
|
|
951
|
+
analyticsView.classList.remove('show');
|
|
952
|
+
tasksView.classList.remove('show');
|
|
953
|
+
if(view==='analytics'){
|
|
954
|
+
analyticsView.classList.add('show');
|
|
955
|
+
loadMetrics();
|
|
956
|
+
} else if(view==='tasks'){
|
|
957
|
+
tasksView.classList.add('show');
|
|
958
|
+
loadTasks();
|
|
959
|
+
} else {
|
|
960
|
+
feedWrap.classList.remove('hide');
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function setMetricsDays(d){
|
|
965
|
+
metricsDays=d;
|
|
966
|
+
document.querySelectorAll('.metrics-toolbar .range-btn').forEach(btn=>btn.classList.toggle('active',Number(btn.dataset.days)===d));
|
|
967
|
+
loadMetrics();
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
async function loadMetrics(){
|
|
971
|
+
const r=await fetch('/api/metrics?days='+metricsDays);
|
|
972
|
+
const d=await r.json();
|
|
973
|
+
document.getElementById('mTotal').textContent=formatNum(d.totals.memories);
|
|
974
|
+
document.getElementById('mTodayWrites').textContent=formatNum(d.totals.todayWrites);
|
|
975
|
+
document.getElementById('mTodayCalls').textContent=formatNum(d.totals.todayViewerCalls);
|
|
976
|
+
document.getElementById('mSessions').textContent=formatNum(d.totals.sessions);
|
|
977
|
+
document.getElementById('mEmbeddings').textContent=formatNum(d.totals.embeddings);
|
|
978
|
+
renderChartWrites(d.writesPerDay);
|
|
979
|
+
renderChartCalls(d.viewerCallsPerDay);
|
|
980
|
+
renderBreakdown(d.roleBreakdown,'breakdownRole');
|
|
981
|
+
renderBreakdown(d.kindBreakdown,'breakdownKind');
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
function formatNum(n){return n>=1e6?(n/1e6).toFixed(1)+'M':n>=1e3?(n/1e3).toFixed(1)+'k':String(n);}
|
|
985
|
+
|
|
986
|
+
/* ─── Tasks View Logic ─── */
|
|
987
|
+
let tasksStatusFilter='';
|
|
988
|
+
let tasksPage=0;
|
|
989
|
+
const TASKS_PER_PAGE=20;
|
|
990
|
+
|
|
991
|
+
function setTaskStatusFilter(btn,status){
|
|
992
|
+
document.querySelectorAll('.tasks-filters .filter-chip').forEach(c=>c.classList.remove('active'));
|
|
993
|
+
btn.classList.add('active');
|
|
994
|
+
tasksStatusFilter=status;
|
|
995
|
+
tasksPage=0;
|
|
996
|
+
loadTasks();
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
async function loadTasks(){
|
|
1000
|
+
const list=document.getElementById('tasksList');
|
|
1001
|
+
list.innerHTML='<div class="spinner"></div>';
|
|
1002
|
+
try{
|
|
1003
|
+
const params=new URLSearchParams({limit:String(TASKS_PER_PAGE),offset:String(tasksPage*TASKS_PER_PAGE)});
|
|
1004
|
+
if(tasksStatusFilter) params.set('status',tasksStatusFilter);
|
|
1005
|
+
const r=await fetch('/api/tasks?'+params);
|
|
1006
|
+
const data=await r.json();
|
|
1007
|
+
|
|
1008
|
+
// stats
|
|
1009
|
+
const allR=await fetch('/api/tasks?limit=1&offset=0');
|
|
1010
|
+
const allD=await allR.json();
|
|
1011
|
+
document.getElementById('tasksTotalCount').textContent=formatNum(allD.total);
|
|
1012
|
+
|
|
1013
|
+
const activeR=await fetch('/api/tasks?status=active&limit=1&offset=0');
|
|
1014
|
+
const activeD=await activeR.json();
|
|
1015
|
+
document.getElementById('tasksActiveCount').textContent=formatNum(activeD.total);
|
|
1016
|
+
|
|
1017
|
+
const compR=await fetch('/api/tasks?status=completed&limit=1&offset=0');
|
|
1018
|
+
const compD=await compR.json();
|
|
1019
|
+
document.getElementById('tasksCompletedCount').textContent=formatNum(compD.total);
|
|
1020
|
+
|
|
1021
|
+
const skipR=await fetch('/api/tasks?status=skipped&limit=1&offset=0');
|
|
1022
|
+
const skipD=await skipR.json();
|
|
1023
|
+
document.getElementById('tasksSkippedCount').textContent=formatNum(skipD.total);
|
|
1024
|
+
|
|
1025
|
+
if(!data.tasks||data.tasks.length===0){
|
|
1026
|
+
list.innerHTML='<div style="text-align:center;padding:48px;color:var(--text-muted);font-size:14px" data-i18n="tasks.empty">'+t('tasks.empty')+'</div>';
|
|
1027
|
+
document.getElementById('tasksPagination').innerHTML='';
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
list.innerHTML=data.tasks.map(task=>{
|
|
1032
|
+
const timeStr=formatTime(task.startedAt);
|
|
1033
|
+
const endStr=task.endedAt?formatTime(task.endedAt):'';
|
|
1034
|
+
const durationStr=task.endedAt?formatDuration(task.endedAt-task.startedAt):'';
|
|
1035
|
+
return '<div class="task-card status-'+task.status+'" onclick="openTaskDetail(\\''+task.id+'\\')">'+
|
|
1036
|
+
'<div class="task-card-top">'+
|
|
1037
|
+
'<div class="task-card-title">'+esc(task.title)+'</div>'+
|
|
1038
|
+
'<span class="task-status-badge '+task.status+'">'+task.status+'</span>'+
|
|
1039
|
+
'</div>'+
|
|
1040
|
+
(task.summary?'<div class="task-card-summary">'+esc(task.summary)+'</div>':'')+
|
|
1041
|
+
'<div class="task-card-bottom">'+
|
|
1042
|
+
'<span class="tag"><span class="icon">\\u{1F4C5}</span> '+timeStr+'</span>'+
|
|
1043
|
+
(durationStr?'<span class="tag"><span class="icon">\\u23F1</span> '+durationStr+'</span>':'')+
|
|
1044
|
+
'<span class="tag"><span class="icon">\\u{1F4DD}</span> '+task.chunkCount+' '+(task.chunkCount===1?'chunk':'chunks')+'</span>'+
|
|
1045
|
+
'<span class="tag"><span class="icon">\\u{1F4C2}</span> '+task.sessionKey.slice(0,12)+'</span>'+
|
|
1046
|
+
'</div>'+
|
|
1047
|
+
'</div>';
|
|
1048
|
+
}).join('');
|
|
1049
|
+
|
|
1050
|
+
renderTasksPagination(data.total);
|
|
1051
|
+
}catch(e){
|
|
1052
|
+
list.innerHTML='<div style="text-align:center;padding:24px;color:var(--rose)">Failed to load tasks</div>';
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function renderTasksPagination(total){
|
|
1057
|
+
const el=document.getElementById('tasksPagination');
|
|
1058
|
+
const pages=Math.ceil(total/TASKS_PER_PAGE);
|
|
1059
|
+
if(pages<=1){el.innerHTML='';return;}
|
|
1060
|
+
let html='<button class="pg-btn'+(tasksPage===0?' disabled':'')+'" onclick="tasksPage=Math.max(0,tasksPage-1);loadTasks()">\\u2190</button>';
|
|
1061
|
+
const start=Math.max(0,tasksPage-2),end=Math.min(pages,tasksPage+3);
|
|
1062
|
+
for(let i=start;i<end;i++){
|
|
1063
|
+
html+='<button class="pg-btn'+(i===tasksPage?' active':'')+'" onclick="tasksPage='+i+';loadTasks()">'+(i+1)+'</button>';
|
|
1064
|
+
}
|
|
1065
|
+
html+='<button class="pg-btn'+(tasksPage>=pages-1?' disabled':'')+'" onclick="tasksPage=Math.min('+(pages-1)+',tasksPage+1);loadTasks()">\\u2192</button>';
|
|
1066
|
+
html+='<span class="pg-info">'+total+' '+t('pagination.total')+'</span>';
|
|
1067
|
+
el.innerHTML=html;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
async function openTaskDetail(taskId){
|
|
1071
|
+
const overlay=document.getElementById('taskDetailOverlay');
|
|
1072
|
+
overlay.classList.add('show');
|
|
1073
|
+
document.getElementById('taskDetailTitle').textContent=t('tasks.loading');
|
|
1074
|
+
document.getElementById('taskDetailMeta').innerHTML='';
|
|
1075
|
+
document.getElementById('taskDetailSummary').textContent='';
|
|
1076
|
+
document.getElementById('taskDetailChunks').innerHTML='<div class="spinner"></div>';
|
|
1077
|
+
|
|
1078
|
+
try{
|
|
1079
|
+
const r=await fetch('/api/task/'+taskId);
|
|
1080
|
+
const task=await r.json();
|
|
1081
|
+
|
|
1082
|
+
document.getElementById('taskDetailTitle').textContent=task.title||t('tasks.untitled');
|
|
1083
|
+
|
|
1084
|
+
const meta=[
|
|
1085
|
+
'<span class="meta-item"><span class="task-status-badge '+task.status+'">'+task.status+'</span></span>',
|
|
1086
|
+
'<span class="meta-item">\\u{1F4C5} '+formatTime(task.startedAt)+'</span>',
|
|
1087
|
+
];
|
|
1088
|
+
if(task.endedAt) meta.push('<span class="meta-item">\\u2192 '+formatTime(task.endedAt)+'</span>');
|
|
1089
|
+
meta.push('<span class="meta-item">\\u{1F4C2} '+task.sessionKey+'</span>');
|
|
1090
|
+
meta.push('<span class="meta-item">\\u{1F4DD} '+task.chunks.length+' chunks</span>');
|
|
1091
|
+
document.getElementById('taskDetailMeta').innerHTML=meta.join('');
|
|
1092
|
+
|
|
1093
|
+
var summaryEl=document.getElementById('taskDetailSummary');
|
|
1094
|
+
if(task.status==='skipped'){
|
|
1095
|
+
summaryEl.innerHTML='<div style="color:var(--text-muted);font-style:italic;display:flex;align-items:flex-start;gap:8px"><span style="font-size:18px">\\u26A0\\uFE0F</span><span>'+esc(task.summary||t('tasks.skipped.default'))+'</span></div>';
|
|
1096
|
+
}else{
|
|
1097
|
+
summaryEl.innerHTML=renderSummaryHtml(task.summary);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
if(task.chunks.length===0){
|
|
1101
|
+
document.getElementById('taskDetailChunks').innerHTML='<div style="color:var(--text-muted);padding:12px;font-size:13px">'+t('tasks.nochunks')+'</div>';
|
|
1102
|
+
}else{
|
|
1103
|
+
document.getElementById('taskDetailChunks').innerHTML=task.chunks.map(c=>{
|
|
1104
|
+
var roleLabel=c.role==='user'?'You':c.role==='assistant'?'Assistant':c.role.toUpperCase();
|
|
1105
|
+
return '<div class="task-chunk-item role-'+c.role+'">'+
|
|
1106
|
+
'<div class="task-chunk-role '+c.role+'">'+roleLabel+'</div>'+
|
|
1107
|
+
'<div class="task-chunk-bubble" onclick="this.classList.toggle(\\\'expanded\\\')">'+esc(c.content)+'</div>'+
|
|
1108
|
+
'<div class="task-chunk-time">'+formatTime(c.createdAt)+'</div>'+
|
|
1109
|
+
'</div>';
|
|
1110
|
+
}).join('');
|
|
1111
|
+
}
|
|
1112
|
+
}catch(e){
|
|
1113
|
+
document.getElementById('taskDetailTitle').textContent='Error';
|
|
1114
|
+
document.getElementById('taskDetailChunks').innerHTML='<div style="color:var(--rose)">Failed to load task details</div>';
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
function closeTaskDetail(event){
|
|
1119
|
+
if(event && event.target!==document.getElementById('taskDetailOverlay')) return;
|
|
1120
|
+
document.getElementById('taskDetailOverlay').classList.remove('show');
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function formatDuration(ms){
|
|
1124
|
+
const s=Math.floor(ms/1000);
|
|
1125
|
+
if(s<60) return s+'s';
|
|
1126
|
+
const m=Math.floor(s/60);
|
|
1127
|
+
if(m<60) return m+'min';
|
|
1128
|
+
const h=Math.floor(m/60);
|
|
1129
|
+
if(h<24) return h+'h '+((m%60)>0?(m%60)+'min':'');
|
|
1130
|
+
const d=Math.floor(h/24);
|
|
1131
|
+
return d+'d '+((h%24)>0?(h%24)+'h':'');
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function formatTime(ts){
|
|
1135
|
+
if(!ts) return '-';
|
|
1136
|
+
return new Date(ts).toLocaleString('zh-CN',{month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'});
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
function fillDays(rows,days){
|
|
1140
|
+
const map=new Map((rows||[]).map(r=>[r.date,{...r}]));
|
|
1141
|
+
const out=[];const now=new Date();
|
|
1142
|
+
for(let i=days-1;i>=0;i--){
|
|
1143
|
+
const d=new Date(now);d.setDate(d.getDate()-i);
|
|
1144
|
+
const dateStr=d.toISOString().slice(0,10);
|
|
1145
|
+
const row=map.get(dateStr)||{};
|
|
1146
|
+
out.push({date:dateStr,count:row.count??0,list:row.list??0,search:row.search??0,total:(row.list??0)+(row.search??0)});
|
|
1147
|
+
}
|
|
1148
|
+
if(days>21){
|
|
1149
|
+
const weeks=[];let i=0;
|
|
1150
|
+
while(i<out.length){
|
|
1151
|
+
const chunk=out.slice(i,i+7);
|
|
1152
|
+
const first=chunk[0].date,last=chunk[chunk.length-1].date;
|
|
1153
|
+
const c=chunk.reduce((s,r)=>s+r.count,0);
|
|
1154
|
+
const l=chunk.reduce((s,r)=>s+r.list,0);
|
|
1155
|
+
const se=chunk.reduce((s,r)=>s+r.search,0);
|
|
1156
|
+
const label=first.slice(5,10)+'~'+last.slice(8,10);
|
|
1157
|
+
weeks.push({date:label,count:c,list:l,search:se,total:l+se});
|
|
1158
|
+
i+=7;
|
|
1159
|
+
}
|
|
1160
|
+
return weeks;
|
|
1161
|
+
}
|
|
1162
|
+
return out;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
function renderBars(el,data,valueKey,H){
|
|
1166
|
+
const vals=data.map(d=>d[valueKey]??0);
|
|
1167
|
+
if(vals.every(v=>v===0)){el.innerHTML='<div style="color:var(--text-muted);font-size:13px;padding:20px;text-align:center">'+t('chart.nodata')+'</div>';return;}
|
|
1168
|
+
const max=Math.max(1,...vals);
|
|
1169
|
+
el.innerHTML=data.map(r=>{
|
|
1170
|
+
const v=r[valueKey]??0;
|
|
1171
|
+
const label=r.date.includes('~')?r.date:(r.date.length>5?r.date.slice(5):r.date);
|
|
1172
|
+
if(v===0){
|
|
1173
|
+
return '<div class="chart-bar-wrap"><div class="chart-tip">0</div><div class="chart-bar-col"><div class="chart-bar zero" style="height:2px"></div></div><div class="chart-bar-label">'+label+'</div></div>';
|
|
1174
|
+
}
|
|
1175
|
+
const h=Math.max(8,Math.round((v/max)*H));
|
|
1176
|
+
return '<div class="chart-bar-wrap"><div class="chart-tip">'+v+'</div><div class="chart-bar-col"><div class="chart-bar" style="height:'+h+'px"></div></div><div class="chart-bar-label">'+label+'</div></div>';
|
|
1177
|
+
}).join('');
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function renderChartWrites(rows){
|
|
1181
|
+
const el=document.getElementById('chartWrites');
|
|
1182
|
+
const filled=fillDays(rows?.map(r=>({date:r.date,count:r.count})),metricsDays);
|
|
1183
|
+
renderBars(el,filled,'count',160);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function renderChartCalls(rows){
|
|
1187
|
+
const el=document.getElementById('chartCalls');
|
|
1188
|
+
const filled=fillDays(rows?.map(r=>({date:r.date,list:r.list,search:r.search})),metricsDays);
|
|
1189
|
+
const vals=filled.map(f=>f.total);
|
|
1190
|
+
if(vals.every(v=>v===0)){el.innerHTML='<div style="color:var(--text-muted);font-size:13px;padding:20px;text-align:center">'+t('chart.nocalls')+'</div>';return;}
|
|
1191
|
+
const max=Math.max(1,...vals);
|
|
1192
|
+
const H=160;
|
|
1193
|
+
el.innerHTML=filled.map(r=>{
|
|
1194
|
+
const label=r.date.includes('~')?r.date:(r.date.length>5?r.date.slice(5):r.date);
|
|
1195
|
+
if(r.total===0){
|
|
1196
|
+
return '<div class="chart-bar-wrap"><div class="chart-tip">0</div><div class="chart-bar-col"><div class="chart-bar zero" style="height:2px"></div></div><div class="chart-bar-label">'+label+'</div></div>';
|
|
1197
|
+
}
|
|
1198
|
+
const totalH=Math.max(8,Math.round((r.total/max)*H));
|
|
1199
|
+
const listH=r.list?Math.max(3,Math.round((r.list/r.total)*totalH)):0;
|
|
1200
|
+
const searchH=r.search?totalH-listH:0;
|
|
1201
|
+
const tip='List: '+r.list+', Search: '+r.search;
|
|
1202
|
+
let bars='';
|
|
1203
|
+
if(searchH>0) bars+='<div class="chart-bar violet" style="height:'+searchH+'px"></div>';
|
|
1204
|
+
if(listH>0) bars+='<div class="chart-bar" style="height:'+listH+'px"></div>';
|
|
1205
|
+
return '<div class="chart-bar-wrap"><div class="chart-tip">'+tip+'</div><div class="chart-bar-col"><div style="display:flex;flex-direction:column;gap:1px">'+bars+'</div></div><div class="chart-bar-label">'+label+'</div></div>';
|
|
1206
|
+
}).join('');
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
function renderBreakdown(obj,containerId){
|
|
1210
|
+
const el=document.getElementById(containerId);
|
|
1211
|
+
if(!el)return;
|
|
1212
|
+
const entries=Object.entries(obj||{}).sort((a,b)=>b[1]-a[1]);
|
|
1213
|
+
const total=entries.reduce((s,[,v])=>s+v,0)||1;
|
|
1214
|
+
el.innerHTML=entries.map(([label,value])=>{
|
|
1215
|
+
const pct=Math.round((value/total)*100);
|
|
1216
|
+
return '<div class="breakdown-item"><div class="bd-top"><span class="label">'+esc(label)+'</span><span class="value">'+value+' <span style="font-size:11px;font-weight:500;color:var(--text-muted)">('+pct+'%)</span></span></div><div class="breakdown-bar-wrap"><div class="breakdown-bar" style="width:'+pct+'%"></div></div></div>';
|
|
1217
|
+
}).join('');
|
|
1218
|
+
}
|
|
1219
|
+
|
|
457
1220
|
/* ─── Data loading ─── */
|
|
458
1221
|
async function loadAll(){
|
|
459
1222
|
await Promise.all([loadStats(),loadMemories()]);
|
|
@@ -470,13 +1233,13 @@ async function loadStats(){
|
|
|
470
1233
|
|
|
471
1234
|
const provEl=document.getElementById('embeddingStatus');
|
|
472
1235
|
if(d.embeddingProvider && d.embeddingProvider!=='none'){
|
|
473
|
-
provEl.innerHTML='<div class="provider-badge"><span>\\u2713</span>
|
|
1236
|
+
provEl.innerHTML='<div class="provider-badge"><span>\\u2713</span> '+t('embed.on')+d.embeddingProvider+'</div>';
|
|
474
1237
|
} else {
|
|
475
|
-
provEl.innerHTML='<div class="provider-badge offline"><span>\\u26A0</span>
|
|
1238
|
+
provEl.innerHTML='<div class="provider-badge offline"><span>\\u26A0</span> '+t('embed.off')+'</div>';
|
|
476
1239
|
}
|
|
477
1240
|
|
|
478
1241
|
const sl=document.getElementById('sessionList');
|
|
479
|
-
sl.innerHTML='<div class="session-item'+(activeSession===null?' active':'')+'" onclick="filterSession(null)"><span>
|
|
1242
|
+
sl.innerHTML='<div class="session-item'+(activeSession===null?' active':'')+'" onclick="filterSession(null)"><span>'+t('sidebar.allsessions')+'</span><span class="count">'+d.totalMemories+'</span></div>';
|
|
480
1243
|
(d.sessions||[]).forEach(s=>{
|
|
481
1244
|
const isActive=activeSession===s.session_key;
|
|
482
1245
|
const name=s.session_key.length>20?s.session_key.slice(0,8)+'...'+s.session_key.slice(-8):s.session_key;
|
|
@@ -510,7 +1273,7 @@ async function loadMemories(page){
|
|
|
510
1273
|
const d=await r.json();
|
|
511
1274
|
totalPages=d.totalPages||1;
|
|
512
1275
|
totalCount=d.total||0;
|
|
513
|
-
document.getElementById('searchMeta').textContent=totalCount+'
|
|
1276
|
+
document.getElementById('searchMeta').textContent=totalCount+t('search.meta.total');
|
|
514
1277
|
renderMemories(d.memories||[]);
|
|
515
1278
|
renderPagination();
|
|
516
1279
|
}
|
|
@@ -524,9 +1287,9 @@ async function doSearch(q){
|
|
|
524
1287
|
const r=await fetch('/api/search?'+p.toString());
|
|
525
1288
|
const d=await r.json();
|
|
526
1289
|
const meta=[];
|
|
527
|
-
if(d.vectorCount>0) meta.push(d.vectorCount+'
|
|
528
|
-
if(d.ftsCount>0) meta.push(d.ftsCount+'
|
|
529
|
-
meta.push(d.total+'
|
|
1290
|
+
if(d.vectorCount>0) meta.push(d.vectorCount+t('search.meta.semantic'));
|
|
1291
|
+
if(d.ftsCount>0) meta.push(d.ftsCount+t('search.meta.text'));
|
|
1292
|
+
meta.push(d.total+t('search.meta.results'));
|
|
530
1293
|
document.getElementById('searchMeta').textContent=meta.join(' \\u00B7 ');
|
|
531
1294
|
renderMemories(d.results||[]);
|
|
532
1295
|
document.getElementById('pagination').innerHTML='';
|
|
@@ -570,7 +1333,7 @@ function clearDateFilter(){
|
|
|
570
1333
|
function renderMemories(items){
|
|
571
1334
|
const list=document.getElementById('memoryList');
|
|
572
1335
|
if(!items.length){
|
|
573
|
-
list.innerHTML='<div class="empty"><div class="icon">\\u{1F4ED}</div><p>
|
|
1336
|
+
list.innerHTML='<div class="empty"><div class="icon">\\u{1F4ED}</div><p>'+t('empty.text')+'</p></div>';
|
|
574
1337
|
return;
|
|
575
1338
|
}
|
|
576
1339
|
items.forEach(m=>{memoryCache[m.id]=m});
|
|
@@ -589,9 +1352,9 @@ function renderMemories(items){
|
|
|
589
1352
|
'<div class="card-summary">'+summary+'</div>'+
|
|
590
1353
|
'<div class="card-content" id="content-'+id+'"><pre>'+content+'</pre></div>'+
|
|
591
1354
|
'<div class="card-actions">'+
|
|
592
|
-
'<button class="btn btn-sm btn-
|
|
593
|
-
'<button class="btn btn-sm" onclick="openEditModal(\\''+id+'\\')">
|
|
594
|
-
'<button class="btn btn-sm btn-
|
|
1355
|
+
'<button class="btn btn-sm btn-ghost" onclick="toggleContent(\\''+id+'\\')">'+t('card.expand')+'</button>'+
|
|
1356
|
+
'<button class="btn btn-sm btn-ghost" onclick="openEditModal(\\''+id+'\\')">'+t('card.edit')+'</button>'+
|
|
1357
|
+
'<button class="btn btn-sm btn-ghost" style="color:var(--accent)" onclick="deleteMemory(\\''+id+'\\')">'+t('card.delete')+'</button>'+
|
|
595
1358
|
vscore+
|
|
596
1359
|
'</div></div>';
|
|
597
1360
|
}).join('');
|
|
@@ -614,7 +1377,7 @@ function renderPagination(){
|
|
|
614
1377
|
prev=p;
|
|
615
1378
|
}
|
|
616
1379
|
h+='<button class="pg-btn'+(currentPage>=totalPages?' disabled':'')+'" onclick="goPage('+(currentPage+1)+')">\u203A</button>';
|
|
617
|
-
h+='<span class="pg-info">'+totalCount+'
|
|
1380
|
+
h+='<span class="pg-info">'+totalCount+t('pagination.total')+'</span>';
|
|
618
1381
|
el.innerHTML=h;
|
|
619
1382
|
}
|
|
620
1383
|
|
|
@@ -635,11 +1398,43 @@ function esc(s){
|
|
|
635
1398
|
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
636
1399
|
}
|
|
637
1400
|
|
|
1401
|
+
function renderSummaryHtml(raw){
|
|
1402
|
+
if(!raw)return'';
|
|
1403
|
+
var lines=raw.split('\\n');
|
|
1404
|
+
var html=[];
|
|
1405
|
+
var inList=false;
|
|
1406
|
+
var sectionRe=new RegExp('^(\u{1F3AF}|\u{1F4CB}|\u2705|\u{1F4A1})\\\\s+(.+)$');
|
|
1407
|
+
var listRe=new RegExp('^- (.+)$');
|
|
1408
|
+
for(var i=0;i<lines.length;i++){
|
|
1409
|
+
var line=lines[i];
|
|
1410
|
+
var hm=line.match(sectionRe);
|
|
1411
|
+
if(hm){
|
|
1412
|
+
if(inList){html.push('</ul>');inList=false;}
|
|
1413
|
+
html.push('<div class="summary-section-title">'+esc(line)+'</div>');
|
|
1414
|
+
continue;
|
|
1415
|
+
}
|
|
1416
|
+
var lm=line.match(listRe);
|
|
1417
|
+
if(lm){
|
|
1418
|
+
if(!inList){html.push('<ul>');inList=true;}
|
|
1419
|
+
html.push('<li>'+esc(lm[1])+'</li>');
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
if(line.trim()===''){
|
|
1423
|
+
if(inList){html.push('</ul>');inList=false;}
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
if(inList){html.push('</ul>');inList=false;}
|
|
1427
|
+
html.push('<p style="margin:4px 0">'+esc(line)+'</p>');
|
|
1428
|
+
}
|
|
1429
|
+
if(inList)html.push('</ul>');
|
|
1430
|
+
return html.join('');
|
|
1431
|
+
}
|
|
1432
|
+
|
|
638
1433
|
/* ─── CRUD ─── */
|
|
639
1434
|
function openCreateModal(){
|
|
640
1435
|
editingId=null;
|
|
641
|
-
document.getElementById('modalTitle').textContent='
|
|
642
|
-
document.getElementById('modalSubmit').textContent='
|
|
1436
|
+
document.getElementById('modalTitle').textContent=t('modal.new');
|
|
1437
|
+
document.getElementById('modalSubmit').textContent=t('modal.create');
|
|
643
1438
|
document.getElementById('mRole').value='user';
|
|
644
1439
|
document.getElementById('mContent').value='';
|
|
645
1440
|
document.getElementById('mSummary').value='';
|
|
@@ -649,10 +1444,10 @@ function openCreateModal(){
|
|
|
649
1444
|
|
|
650
1445
|
function openEditModal(id){
|
|
651
1446
|
const m=memoryCache[id];
|
|
652
|
-
if(!m){toast('
|
|
1447
|
+
if(!m){toast(t('toast.notfound'),'error');return}
|
|
653
1448
|
editingId=id;
|
|
654
|
-
document.getElementById('modalTitle').textContent='
|
|
655
|
-
document.getElementById('modalSubmit').textContent='
|
|
1449
|
+
document.getElementById('modalTitle').textContent=t('modal.edit');
|
|
1450
|
+
document.getElementById('modalSubmit').textContent=t('modal.save');
|
|
656
1451
|
document.getElementById('mRole').value=m.role||'user';
|
|
657
1452
|
document.getElementById('mContent').value=m.content||'';
|
|
658
1453
|
document.getElementById('mSummary').value=m.summary||'';
|
|
@@ -671,7 +1466,7 @@ async function submitModal(){
|
|
|
671
1466
|
summary:document.getElementById('mSummary').value,
|
|
672
1467
|
kind:document.getElementById('mKind').value,
|
|
673
1468
|
};
|
|
674
|
-
if(!data.content.trim()){toast('
|
|
1469
|
+
if(!data.content.trim()){toast(t('modal.err.empty'),'error');return}
|
|
675
1470
|
let r;
|
|
676
1471
|
if(editingId){
|
|
677
1472
|
r=await fetch('/api/memory/'+editingId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
|
|
@@ -679,25 +1474,25 @@ async function submitModal(){
|
|
|
679
1474
|
r=await fetch('/api/memory',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
|
|
680
1475
|
}
|
|
681
1476
|
const d=await r.json();
|
|
682
|
-
if(d.ok){toast(editingId?'
|
|
683
|
-
else{toast(d.error||'
|
|
1477
|
+
if(d.ok){toast(editingId?t('toast.updated'):t('toast.created'),'success');closeModal();loadAll();}
|
|
1478
|
+
else{toast(d.error||t('toast.opfail'),'error')}
|
|
684
1479
|
}
|
|
685
1480
|
|
|
686
1481
|
async function deleteMemory(id){
|
|
687
|
-
if(!confirm('
|
|
1482
|
+
if(!confirm(t('confirm.delete')))return;
|
|
688
1483
|
const r=await fetch('/api/memory/'+id,{method:'DELETE'});
|
|
689
1484
|
const d=await r.json();
|
|
690
|
-
if(d.ok){toast('
|
|
691
|
-
else{toast('
|
|
1485
|
+
if(d.ok){toast(t('toast.deleted'),'success');loadAll();}
|
|
1486
|
+
else{toast(t('toast.delfail'),'error')}
|
|
692
1487
|
}
|
|
693
1488
|
|
|
694
1489
|
async function clearAll(){
|
|
695
|
-
if(!confirm('
|
|
696
|
-
if(!confirm('
|
|
1490
|
+
if(!confirm(t('confirm.clearall')))return;
|
|
1491
|
+
if(!confirm(t('confirm.clearall2')))return;
|
|
697
1492
|
const r=await fetch('/api/memories',{method:'DELETE'});
|
|
698
1493
|
const d=await r.json();
|
|
699
|
-
if(d.ok){toast('
|
|
700
|
-
else{toast('
|
|
1494
|
+
if(d.ok){toast(t('toast.cleared'),'success');loadAll();}
|
|
1495
|
+
else{toast(t('toast.clearfail'),'error')}
|
|
701
1496
|
}
|
|
702
1497
|
|
|
703
1498
|
/* ─── Toast ─── */
|
|
@@ -720,6 +1515,7 @@ initViewerTheme();
|
|
|
720
1515
|
/* ─── Init ─── */
|
|
721
1516
|
document.getElementById('modalOverlay').addEventListener('click',e=>{if(e.target.id==='modalOverlay')closeModal()});
|
|
722
1517
|
document.getElementById('searchInput').addEventListener('keydown',e=>{if(e.key==='Escape'){e.target.value='';loadMemories()}});
|
|
1518
|
+
applyI18n();
|
|
723
1519
|
checkAuth();
|
|
724
1520
|
</script>
|
|
725
1521
|
</body>
|