@memtensor/memos-local-openclaw-plugin 0.1.3 → 0.1.5
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 +283 -91
- 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/dedup.d.ts +8 -0
- package/dist/ingest/dedup.d.ts.map +1 -1
- package/dist/ingest/dedup.js +21 -0
- package/dist/ingest/dedup.js.map +1 -1
- package/dist/ingest/providers/anthropic.d.ts +16 -0
- package/dist/ingest/providers/anthropic.d.ts.map +1 -1
- package/dist/ingest/providers/anthropic.js +214 -1
- package/dist/ingest/providers/anthropic.js.map +1 -1
- package/dist/ingest/providers/bedrock.d.ts +16 -5
- package/dist/ingest/providers/bedrock.d.ts.map +1 -1
- package/dist/ingest/providers/bedrock.js +210 -6
- package/dist/ingest/providers/bedrock.js.map +1 -1
- package/dist/ingest/providers/gemini.d.ts +16 -0
- package/dist/ingest/providers/gemini.d.ts.map +1 -1
- package/dist/ingest/providers/gemini.js +202 -1
- package/dist/ingest/providers/gemini.js.map +1 -1
- package/dist/ingest/providers/index.d.ts +31 -0
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +134 -4
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/ingest/providers/openai.d.ts +24 -0
- package/dist/ingest/providers/openai.d.ts.map +1 -1
- package/dist/ingest/providers/openai.js +255 -1
- package/dist/ingest/providers/openai.js.map +1 -1
- package/dist/ingest/task-processor.d.ts +65 -0
- package/dist/ingest/task-processor.d.ts.map +1 -0
- package/dist/ingest/task-processor.js +354 -0
- package/dist/ingest/task-processor.js.map +1 -0
- package/dist/ingest/worker.d.ts +3 -1
- package/dist/ingest/worker.d.ts.map +1 -1
- package/dist/ingest/worker.js +131 -23
- 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 +22 -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/skill/bundled-memory-guide.d.ts +6 -0
- package/dist/skill/bundled-memory-guide.d.ts.map +1 -0
- package/dist/skill/bundled-memory-guide.js +95 -0
- package/dist/skill/bundled-memory-guide.js.map +1 -0
- package/dist/skill/evaluator.d.ts +31 -0
- package/dist/skill/evaluator.d.ts.map +1 -0
- package/dist/skill/evaluator.js +194 -0
- package/dist/skill/evaluator.js.map +1 -0
- package/dist/skill/evolver.d.ts +22 -0
- package/dist/skill/evolver.d.ts.map +1 -0
- package/dist/skill/evolver.js +193 -0
- package/dist/skill/evolver.js.map +1 -0
- package/dist/skill/generator.d.ts +25 -0
- package/dist/skill/generator.d.ts.map +1 -0
- package/dist/skill/generator.js +477 -0
- package/dist/skill/generator.js.map +1 -0
- package/dist/skill/installer.d.ts +16 -0
- package/dist/skill/installer.d.ts.map +1 -0
- package/dist/skill/installer.js +89 -0
- package/dist/skill/installer.js.map +1 -0
- package/dist/skill/upgrader.d.ts +19 -0
- package/dist/skill/upgrader.d.ts.map +1 -0
- package/dist/skill/upgrader.js +263 -0
- package/dist/skill/upgrader.js.map +1 -0
- package/dist/skill/validator.d.ts +29 -0
- package/dist/skill/validator.d.ts.map +1 -0
- package/dist/skill/validator.js +227 -0
- package/dist/skill/validator.js.map +1 -0
- package/dist/storage/sqlite.d.ts +141 -1
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +664 -7
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/types.d.ts +93 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -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 +2391 -159
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +16 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +346 -3
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +572 -89
- package/openclaw.plugin.json +20 -45
- package/package.json +3 -4
- package/skill/memos-memory-guide/SKILL.md +86 -0
- package/src/capture/index.ts +85 -45
- package/src/ingest/dedup.ts +29 -0
- package/src/ingest/providers/anthropic.ts +258 -1
- package/src/ingest/providers/bedrock.ts +256 -6
- package/src/ingest/providers/gemini.ts +252 -1
- package/src/ingest/providers/index.ts +156 -8
- package/src/ingest/providers/openai.ts +304 -1
- package/src/ingest/task-processor.ts +396 -0
- package/src/ingest/worker.ts +145 -34
- package/src/recall/engine.ts +23 -12
- package/src/recall/mmr.ts +3 -1
- package/src/skill/bundled-memory-guide.ts +91 -0
- package/src/skill/evaluator.ts +220 -0
- package/src/skill/evolver.ts +169 -0
- package/src/skill/generator.ts +506 -0
- package/src/skill/installer.ts +59 -0
- package/src/skill/upgrader.ts +257 -0
- package/src/skill/validator.ts +227 -0
- package/src/storage/sqlite.ts +802 -7
- package/src/types.ts +96 -0
- package/src/viewer/html.ts +2391 -159
- package/src/viewer/server.ts +346 -3
- package/SKILL.md +0 -43
- package/www/index.html +0 -632
package/src/viewer/html.ts
CHANGED
|
@@ -10,39 +10,67 @@ export const viewerHTML = `<!DOCTYPE html>
|
|
|
10
10
|
<style>
|
|
11
11
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
12
12
|
:root{
|
|
13
|
-
--bg:#
|
|
14
|
-
--border:rgba(255,255,255,.08);--border-glow:rgba(
|
|
15
|
-
--text:#
|
|
16
|
-
--pri:#
|
|
17
|
-
--pri-grad:linear-gradient(135deg,#
|
|
18
|
-
--accent:#
|
|
19
|
-
--green:#
|
|
20
|
-
--amber:#
|
|
21
|
-
--violet:#
|
|
22
|
-
--shadow-sm:0 1px 2px rgba(0,0,0,.
|
|
23
|
-
--shadow-lg:0 20px 40px rgba(0,0,0,.
|
|
13
|
+
--bg:#0b0d11;--bg-card:#12141a;--bg-card-hover:#1a1d25;
|
|
14
|
+
--border:rgba(255,255,255,.08);--border-glow:rgba(255,255,255,.14);
|
|
15
|
+
--text:#e8eaed;--text-sec:#8b8fa4;--text-muted:#555a6e;
|
|
16
|
+
--pri:#818cf8;--pri-glow:rgba(129,140,248,.1);--pri-dark:#6366f1;
|
|
17
|
+
--pri-grad:linear-gradient(135deg,#818cf8,#6366f1);
|
|
18
|
+
--accent:#ef4444;--accent-glow:rgba(239,68,68,.1);
|
|
19
|
+
--green:#34d399;--green-bg:rgba(52,211,153,.08);
|
|
20
|
+
--amber:#fbbf24;--amber-bg:rgba(251,191,36,.08);
|
|
21
|
+
--violet:#818cf8;--rose:#ef4444;--rose-bg:rgba(239,68,68,.08);
|
|
22
|
+
--shadow-sm:0 1px 2px rgba(0,0,0,.3);--shadow:0 4px 12px rgba(0,0,0,.35);
|
|
23
|
+
--shadow-lg:0 20px 40px rgba(0,0,0,.45);
|
|
24
24
|
--radius:12px;--radius-lg:14px;--radius-xl:18px;
|
|
25
25
|
}
|
|
26
26
|
[data-theme="light"]{
|
|
27
|
-
--bg:#
|
|
28
|
-
--border
|
|
29
|
-
--text:#
|
|
30
|
-
--pri:#
|
|
31
|
-
--pri-grad:linear-gradient(135deg,#
|
|
32
|
-
--accent:#dc2626;--accent-glow:rgba(220,38,38,.
|
|
33
|
-
--green:#059669;--green-bg:rgba(5,150,105,.
|
|
34
|
-
--amber:#d97706;--amber-bg:rgba(217,119,6,.
|
|
35
|
-
--violet:#
|
|
36
|
-
--shadow-sm:0 1px 2px rgba(0,0,0,.
|
|
37
|
-
--shadow-lg:0 20px 40px rgba(0,0,0,.
|
|
38
|
-
}
|
|
39
|
-
[data-theme="light"] .auth-screen{background:linear-gradient(135deg,#
|
|
40
|
-
[data-theme="light"] .auth-card{box-shadow:0 25px 50px -12px rgba(0,0,0,.
|
|
41
|
-
[data-theme="light"] .topbar{background:rgba(255,255,255,.
|
|
42
|
-
[data-theme="light"] .session-item .count,[data-theme="light"] .kind-tag,[data-theme="light"] .session-tag{background:rgba(0,0,0,.
|
|
43
|
-
[data-theme="light"] .card-content pre{background
|
|
44
|
-
[data-theme="light"] .vscore-badge{background:
|
|
45
|
-
[data-theme="light"] ::-webkit-scrollbar-thumb{background:rgba(0,0,0,.
|
|
27
|
+
--bg:#f8f9fb;--bg-card:#fff;--bg-card-hover:#f3f4f6;
|
|
28
|
+
--border:#e2e4e9;--border-glow:#cbd0d8;
|
|
29
|
+
--text:#111827;--text-sec:#4b5563;--text-muted:#9ca3af;
|
|
30
|
+
--pri:#4f46e5;--pri-glow:rgba(79,70,229,.06);--pri-dark:#4338ca;
|
|
31
|
+
--pri-grad:linear-gradient(135deg,#4f46e5,#4338ca);
|
|
32
|
+
--accent:#dc2626;--accent-glow:rgba(220,38,38,.06);
|
|
33
|
+
--green:#059669;--green-bg:rgba(5,150,105,.06);
|
|
34
|
+
--amber:#d97706;--amber-bg:rgba(217,119,6,.06);
|
|
35
|
+
--violet:#4f46e5;--rose:#dc2626;--rose-bg:rgba(220,38,38,.06);
|
|
36
|
+
--shadow-sm:0 1px 2px rgba(0,0,0,.04);--shadow:0 4px 12px rgba(0,0,0,.06);
|
|
37
|
+
--shadow-lg:0 20px 40px rgba(0,0,0,.1);
|
|
38
|
+
}
|
|
39
|
+
[data-theme="light"] .auth-screen{background:linear-gradient(135deg,#f0f4ff 0%,#f8f9fb 50%,#eef2ff 100%)}
|
|
40
|
+
[data-theme="light"] .auth-card{box-shadow:0 25px 50px -12px rgba(0,0,0,.08)}
|
|
41
|
+
[data-theme="light"] .topbar{background:rgba(255,255,255,.92);border-bottom-color:var(--border);backdrop-filter:blur(8px)}
|
|
42
|
+
[data-theme="light"] .session-item .count,[data-theme="light"] .kind-tag,[data-theme="light"] .session-tag{background:rgba(0,0,0,.05)}
|
|
43
|
+
[data-theme="light"] .card-content pre{background:#f3f4f6;border-color:var(--border)}
|
|
44
|
+
[data-theme="light"] .vscore-badge{background:rgba(79,70,229,.06);color:#4f46e5}
|
|
45
|
+
[data-theme="light"] ::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15)}
|
|
46
|
+
[data-theme="light"] .analytics-card{background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.06);border:1px solid var(--border)}
|
|
47
|
+
[data-theme="light"] .analytics-card::before{background:none}
|
|
48
|
+
[data-theme="light"] .analytics-card::after{display:none}
|
|
49
|
+
[data-theme="light"] .analytics-card:hover{box-shadow:0 4px 16px rgba(0,0,0,.08);transform:translateY(-2px)}
|
|
50
|
+
[data-theme="light"] .analytics-card.green{background:#fff;border-color:var(--border)}
|
|
51
|
+
[data-theme="light"] .analytics-card.green::before{background:none}
|
|
52
|
+
[data-theme="light"] .analytics-card.amber{background:#fff;border-color:var(--border)}
|
|
53
|
+
[data-theme="light"] .analytics-card.amber::before{background:none}
|
|
54
|
+
[data-theme="light"] .analytics-card .ac-value{-webkit-text-fill-color:unset;background:none;color:#111827}
|
|
55
|
+
[data-theme="light"] .analytics-card.green .ac-value{color:#059669}
|
|
56
|
+
[data-theme="light"] .analytics-card.amber .ac-value{color:#d97706}
|
|
57
|
+
[data-theme="light"] .analytics-section{background:#fff;border-color:var(--border);box-shadow:0 1px 3px rgba(0,0,0,.04)}
|
|
58
|
+
[data-theme="light"] .analytics-section::before{background:none}
|
|
59
|
+
[data-theme="light"] .chart-bar{box-shadow:none}
|
|
60
|
+
[data-theme="light"] .chart-bar:hover{box-shadow:0 2px 8px rgba(79,70,229,.15)}
|
|
61
|
+
[data-theme="light"] .tool-chart-tooltip{background:rgba(17,24,39,.92);color:#e8eaed;border-color:rgba(99,102,241,.3);box-shadow:0 8px 24px rgba(0,0,0,.2)}
|
|
62
|
+
[data-theme="light"] .tool-chart-tooltip .tt-time{color:#a5b4fc}
|
|
63
|
+
[data-theme="light"] .tool-chart-tooltip .tt-val{color:#e8eaed}
|
|
64
|
+
[data-theme="light"] .tool-agg-table td{background:transparent}
|
|
65
|
+
[data-theme="light"] .tool-agg-table tr:hover td{background:rgba(79,70,229,.03)}
|
|
66
|
+
[data-theme="light"] .tool-agg-table th{color:#9ca3af}
|
|
67
|
+
[data-theme="light"] .breakdown-item{background:#f9fafb;border-color:var(--border)}
|
|
68
|
+
[data-theme="light"] .breakdown-item:hover{background:#f3f4f6;border-color:#cbd5e1}
|
|
69
|
+
[data-theme="light"] .breakdown-bar-wrap{background:#e5e7eb}
|
|
70
|
+
[data-theme="light"] .breakdown-bar{background:linear-gradient(90deg,#4f46e5,#6366f1);box-shadow:none}
|
|
71
|
+
[data-theme="light"] .range-btn{background:transparent;border-color:var(--border);color:var(--text-sec)}
|
|
72
|
+
[data-theme="light"] .range-btn.active{background:rgba(79,70,229,.06);color:#4f46e5;border-color:rgba(79,70,229,.2)}
|
|
73
|
+
[data-theme="light"] .range-btn:hover{border-color:#4f46e5;color:#4f46e5}
|
|
46
74
|
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
75
|
button{cursor:pointer;font-family:inherit;font-size:inherit}
|
|
48
76
|
input,textarea,select{font-family:inherit;font-size:inherit}
|
|
@@ -55,31 +83,33 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
55
83
|
.auth-card p{color:hsl(0 0% 45.1%);margin-bottom:24px;font-size:14px}
|
|
56
84
|
.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
85
|
.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:
|
|
60
|
-
.auth-card .btn-auth:hover{background:
|
|
86
|
+
.auth-card input:focus{border-color:var(--pri);box-shadow:0 0 0 3px var(--pri-glow)}
|
|
87
|
+
.auth-card .btn-auth{width:100%;padding:11px;border:1px solid var(--pri);border-radius:8px;background:rgba(99,102,241,.06);color:var(--pri);font-weight:600;font-size:14px;transition:all .15s}
|
|
88
|
+
.auth-card .btn-auth:hover{background:rgba(99,102,241,.12);border-color:var(--pri-dark)}
|
|
61
89
|
.auth-card .error-msg{color:hsl(0 84.2% 60.2%);font-size:13px;margin-top:8px;min-height:20px}
|
|
62
90
|
.auth-card .btn-text{color:hsl(0 0% 45.1%)}
|
|
63
|
-
.auth-card .btn-text:hover{color:
|
|
91
|
+
.auth-card .btn-text:hover{color:var(--pri)}
|
|
64
92
|
|
|
65
93
|
.reset-guide{text-align:left;margin-bottom:20px}
|
|
66
94
|
.reset-step{display:flex;gap:14px;margin-bottom:16px}
|
|
67
|
-
.step-num{width:28px;height:28px;border-radius:50%;background:
|
|
95
|
+
.step-num{width:28px;height:28px;border-radius:50%;background:var(--pri);color:#fff;font-size:12px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
|
68
96
|
.step-body{flex:1;min-width:0}
|
|
69
97
|
.step-title{font-size:14px;font-weight:600;color:hsl(0 0% 3.9%);margin-bottom:2px}
|
|
70
98
|
.step-desc{font-size:13px;color:hsl(0 0% 45.1%);line-height:1.5}
|
|
71
99
|
.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%)}
|
|
72
|
-
.cmd-box:hover{border-color:
|
|
100
|
+
.cmd-box:hover{border-color:hsl(0 0% 70%);background:hsl(0 0% 96.1%)}
|
|
73
101
|
.cmd-box code{flex:1}
|
|
74
102
|
.copy-hint{font-size:11px;color:hsl(0 0% 45.1%);white-space:nowrap}
|
|
75
103
|
.cmd-box.copied .copy-hint{color:hsl(142 71% 45%)}
|
|
76
104
|
|
|
77
105
|
/* ─── App Layout (dark dashboard, same as www) ─── */
|
|
78
106
|
.app{display:none;flex-direction:column;min-height:100vh}
|
|
79
|
-
.topbar{background:rgba(
|
|
80
|
-
.topbar .brand{display:flex;align-items:center;gap:
|
|
81
|
-
.topbar .brand .icon{width:
|
|
82
|
-
.topbar .
|
|
107
|
+
.topbar{background:rgba(11,13,17,.88);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(12px)}
|
|
108
|
+
.topbar .brand{display:flex;align-items:center;gap:10px;font-weight:700;font-size:15px;color:var(--text);letter-spacing:-.02em;flex-shrink:0}
|
|
109
|
+
.topbar .brand .icon{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:22px;background:none;border-radius:0}
|
|
110
|
+
.topbar .brand .sub{font-weight:400;color:var(--text-muted);font-size:11px}
|
|
111
|
+
.topbar-center{flex:1;display:flex;justify-content:center}
|
|
112
|
+
.topbar .actions{display:flex;align-items:center;gap:6px;flex-shrink:0}
|
|
83
113
|
|
|
84
114
|
.main-content{display:flex;flex:1;max-width:1400px;margin:0 auto;width:100%;padding:28px 32px;gap:28px}
|
|
85
115
|
|
|
@@ -107,17 +137,17 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
107
137
|
|
|
108
138
|
/* ─── Feed ─── */
|
|
109
139
|
.feed{flex:1;min-width:0}
|
|
110
|
-
.search-bar{display:flex;gap:
|
|
111
|
-
.search-bar input{flex:1;padding:
|
|
140
|
+
.search-bar{display:flex;gap:10px;margin-bottom:16px;position:relative;align-items:center}
|
|
141
|
+
.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
142
|
.search-bar input::placeholder{color:var(--text-muted)}
|
|
113
143
|
.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:
|
|
144
|
+
.search-bar .search-icon{position:absolute;left:14px;top:50%;transform:translateY(-50%);color:var(--text-muted);font-size:14px;pointer-events:none}
|
|
115
145
|
.search-meta{font-size:12px;color:var(--text-sec);margin-bottom:14px;padding:0 2px}
|
|
116
146
|
|
|
117
147
|
.filter-bar{display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap}
|
|
118
|
-
.filter-chip{padding:
|
|
148
|
+
.filter-chip{padding:5px 14px;border:1px solid var(--border);border-radius:6px;background:transparent;color:var(--text-sec);font-size:12px;font-weight:500;transition:all .15s}
|
|
119
149
|
.filter-chip:hover{border-color:var(--pri);color:var(--pri)}
|
|
120
|
-
.filter-chip.active{background:var(--pri);
|
|
150
|
+
.filter-chip.active{background:rgba(99,102,241,.08);color:var(--pri);border-color:rgba(99,102,241,.25)}
|
|
121
151
|
|
|
122
152
|
.memory-list{display:flex;flex-direction:column;gap:16px}
|
|
123
153
|
.memory-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:20px 24px;transition:all .2s}
|
|
@@ -125,7 +155,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
125
155
|
.memory-card .card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:wrap;gap:8px}
|
|
126
156
|
.memory-card .meta{display:flex;align-items:center;gap:8px}
|
|
127
157
|
.role-tag{padding:4px 10px;border-radius:8px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.03em}
|
|
128
|
-
.role-tag.user{background:var(--pri-glow);color:var(--pri);border:1px solid rgba(
|
|
158
|
+
.role-tag.user{background:var(--pri-glow);color:var(--pri);border:1px solid rgba(99,102,241,.12)}
|
|
129
159
|
.role-tag.assistant{background:var(--accent-glow);color:var(--accent);border:1px solid rgba(230,57,70,.2)}
|
|
130
160
|
.role-tag.system{background:var(--amber-bg);color:var(--amber);border:1px solid rgba(245,158,11,.2)}
|
|
131
161
|
.kind-tag{padding:4px 10px;border-radius:8px;font-size:11px;color:var(--text-sec);background:rgba(0,0,0,.2);font-weight:500}
|
|
@@ -136,19 +166,55 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
136
166
|
.card-content.show{max-height:600px;overflow-y:auto}
|
|
137
167
|
.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
168
|
.card-actions{display:flex;align-items:center;gap:8px;margin-top:14px}
|
|
139
|
-
.vscore-badge{display:inline-flex;align-items:center;background:
|
|
169
|
+
.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}
|
|
170
|
+
.merge-badge{display:inline-flex;align-items:center;gap:4px;background:rgba(16,185,129,.12);color:#10b981;font-size:10px;font-weight:600;padding:3px 10px;border-radius:8px}
|
|
171
|
+
.merge-history{margin-top:12px;padding:12px 14px;background:rgba(0,0,0,.15);border-radius:10px;border:1px solid var(--border);font-size:12px;line-height:1.7;color:var(--text-sec);max-height:200px;overflow-y:auto}
|
|
172
|
+
.merge-history-item{padding:6px 0;border-bottom:1px dashed rgba(255,255,255,.06)}
|
|
173
|
+
.merge-history-item:last-child{border-bottom:none}
|
|
174
|
+
.merge-action{font-weight:600;font-size:11px;padding:2px 6px;border-radius:4px}
|
|
175
|
+
.merge-action.UPDATE{background:rgba(59,130,246,.15);color:#60a5fa}
|
|
176
|
+
.merge-action.DUPLICATE{background:rgba(245,158,11,.15);color:#f59e0b}
|
|
177
|
+
.card-updated{font-size:11px;color:var(--text-muted);margin-left:6px}
|
|
178
|
+
.dedup-badge{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:3px 10px;border-radius:8px}
|
|
179
|
+
.dedup-badge.duplicate{background:rgba(245,158,11,.12);color:#f59e0b}
|
|
180
|
+
.dedup-badge.merged{background:rgba(59,130,246,.12);color:#60a5fa}
|
|
181
|
+
.memory-card.dedup-inactive{opacity:.55;border-style:dashed}
|
|
182
|
+
.memory-card.dedup-inactive:hover{opacity:.85}
|
|
183
|
+
.dedup-target-link{font-size:11px;color:var(--pri);cursor:pointer;text-decoration:underline;margin-left:4px}
|
|
184
|
+
.memory-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:9999;display:none;align-items:center;justify-content:center;backdrop-filter:blur(4px)}
|
|
185
|
+
.memory-modal-overlay.show{display:flex}
|
|
186
|
+
.memory-modal{background:var(--bg-card);border:1px solid var(--border);border-radius:16px;width:min(600px,90vw);max-height:80vh;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,.4);animation:modalIn .2s ease-out}
|
|
187
|
+
@keyframes modalIn{from{opacity:0;transform:scale(.95) translateY(10px)}to{opacity:1;transform:scale(1) translateY(0)}}
|
|
188
|
+
.memory-modal-title{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--border);font-size:14px;font-weight:700}
|
|
189
|
+
.memory-modal-body{padding:20px;overflow-y:auto;flex:1}
|
|
190
|
+
.modal-memory-card{display:flex;flex-direction:column;gap:14px}
|
|
191
|
+
.modal-header-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
192
|
+
.modal-field{display:flex;flex-direction:column;gap:4px}
|
|
193
|
+
.modal-field-label{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1px;color:var(--text-sec)}
|
|
194
|
+
.modal-field-val{font-size:13px;color:var(--text);line-height:1.5}
|
|
195
|
+
.modal-field-content{font-family:'SF Mono',Consolas,monospace;font-size:12px;line-height:1.6;color:var(--text);white-space:pre-wrap;word-break:break-all;background:rgba(0,0,0,.15);border-radius:8px;padding:12px;max-height:240px;overflow-y:auto;margin:0}
|
|
196
|
+
[data-theme="light"] .modal-field-content{background:rgba(0,0,0,.04)}
|
|
197
|
+
.modal-meta-row{display:flex;flex-wrap:wrap;gap:12px;font-size:11px;color:var(--text-sec);padding:8px 0;border-top:1px dashed var(--border)}
|
|
198
|
+
[data-theme="light"] .merge-history{background:rgba(0,0,0,.04)}
|
|
199
|
+
[data-theme="light"] .merge-history-item{border-bottom-color:rgba(0,0,0,.06)}
|
|
140
200
|
|
|
141
201
|
/* ─── Buttons ─── */
|
|
142
|
-
.btn{padding:
|
|
202
|
+
.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
203
|
.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-
|
|
204
|
+
.btn-primary{background:rgba(255,255,255,.08);color:var(--text);border:1px solid var(--border);font-weight:600}
|
|
205
|
+
.btn-primary:hover{background:rgba(255,255,255,.14);transform:translateY(-1px);border-color:var(--pri);color:var(--pri)}
|
|
206
|
+
.btn-ghost{border-color:transparent;background:transparent;color:var(--text-sec)}
|
|
207
|
+
.btn-ghost:hover{background:rgba(255,255,255,.06);color:var(--text)}
|
|
208
|
+
.btn-danger{color:var(--accent);border-color:rgba(230,57,70,.25)}
|
|
209
|
+
.btn-danger:hover{background:rgba(230,57,70,.1);color:var(--accent)}
|
|
210
|
+
.btn-sm{padding:5px 12px;font-size:12px}
|
|
211
|
+
.btn-icon{padding:5px 7px;font-size:15px;border-radius:8px}
|
|
212
|
+
.btn-text{border:none;background:none;color:var(--text-muted);font-size:12px;padding:4px 8px}
|
|
151
213
|
.btn-text:hover{color:var(--pri)}
|
|
214
|
+
[data-theme="light"] .btn-primary{background:rgba(0,0,0,.05);color:var(--text);border-color:rgba(0,0,0,.12)}
|
|
215
|
+
[data-theme="light"] .btn-primary:hover{background:rgba(0,0,0,.08);border-color:var(--pri);color:var(--pri)}
|
|
216
|
+
[data-theme="light"] .btn-ghost{color:var(--text-sec)}
|
|
217
|
+
[data-theme="light"] .btn-ghost:hover{background:rgba(0,0,0,.04);color:var(--text)}
|
|
152
218
|
|
|
153
219
|
/* ─── Modal ─── */
|
|
154
220
|
.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)}
|
|
@@ -168,7 +234,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
168
234
|
.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}
|
|
169
235
|
.toast.success{background:var(--green-bg);color:var(--green);border-color:rgba(16,185,129,.3)}
|
|
170
236
|
.toast.error{background:var(--rose-bg);color:var(--rose);border-color:rgba(244,63,94,.3)}
|
|
171
|
-
.toast.info{background:var(--pri-glow);color:var(--pri);border-color:rgba(
|
|
237
|
+
.toast.info{background:var(--pri-glow);color:var(--pri);border-color:rgba(99,102,241,.15)}
|
|
172
238
|
@keyframes slideIn{from{transform:translateX(100px);opacity:0}to{transform:translateX(0);opacity:1}}
|
|
173
239
|
|
|
174
240
|
.empty{text-align:center;padding:64px 20px;color:var(--text-sec)}
|
|
@@ -198,89 +264,406 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
198
264
|
.pagination .pg-btn.disabled{opacity:.4;pointer-events:none}
|
|
199
265
|
.pagination .pg-info{font-size:12px;color:var(--text-sec);padding:0 12px}
|
|
200
266
|
|
|
201
|
-
|
|
267
|
+
/* ─── Tasks 视图 ─── */
|
|
268
|
+
.tasks-view{display:none;flex:1;min-width:0;flex-direction:column;gap:16px}
|
|
269
|
+
.tasks-view.show{display:flex}
|
|
270
|
+
.tasks-header{display:flex;flex-direction:column;gap:14px}
|
|
271
|
+
.tasks-stats{display:flex;gap:16px}
|
|
272
|
+
.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}
|
|
273
|
+
.tasks-stat:hover{border-color:var(--border-glow)}
|
|
274
|
+
.tasks-stat-value{font-size:22px;font-weight:700;color:var(--text)}
|
|
275
|
+
.tasks-stat-label{font-size:12px;color:var(--text-sec);font-weight:500}
|
|
276
|
+
.tasks-filters{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
277
|
+
.tasks-list{display:flex;flex-direction:column;gap:10px}
|
|
278
|
+
.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}
|
|
279
|
+
.task-card:hover{border-color:var(--border-glow);background:var(--bg-card-hover);transform:translateY(-1px);box-shadow:var(--shadow)}
|
|
280
|
+
.task-card::before{content:'';position:absolute;top:0;left:0;bottom:0;width:3px;border-radius:3px 0 0 3px}
|
|
281
|
+
.task-card.status-active::before{background:var(--green)}
|
|
282
|
+
.task-card.status-completed::before{background:var(--pri)}
|
|
283
|
+
.task-card.status-skipped::before{background:var(--text-muted)}
|
|
284
|
+
.task-card.status-skipped{opacity:.6}
|
|
285
|
+
.task-card-top{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:8px}
|
|
286
|
+
.task-card-title{font-size:14px;font-weight:600;color:var(--text);line-height:1.4;flex:1;word-break:break-word}
|
|
287
|
+
.task-card-title:empty::after{content:'Untitled Task';color:var(--text-muted);font-style:italic}
|
|
288
|
+
.task-status-badge{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;padding:3px 10px;border-radius:20px;flex-shrink:0}
|
|
289
|
+
.task-status-badge.active{color:var(--green);background:var(--green-bg)}
|
|
290
|
+
.task-status-badge.completed{color:var(--pri);background:var(--pri-glow)}
|
|
291
|
+
.task-status-badge.skipped{color:var(--text-muted);background:rgba(128,128,128,.15)}
|
|
292
|
+
.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}
|
|
293
|
+
.task-card-summary:empty{display:none}
|
|
294
|
+
.task-card-summary.skipped-reason{background:rgba(128,128,128,.08);border-radius:6px;padding:6px 10px;border-left:3px solid var(--text-muted)}
|
|
295
|
+
.task-card-bottom{display:flex;align-items:center;gap:14px;font-size:11px;color:var(--text-muted)}
|
|
296
|
+
.task-card-bottom .tag{display:flex;align-items:center;gap:4px}
|
|
297
|
+
.task-card-bottom .tag .icon{font-size:12px}
|
|
298
|
+
|
|
299
|
+
/* ─── Task Detail Overlay ─── */
|
|
300
|
+
.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)}
|
|
301
|
+
.task-detail-overlay.show{display:flex}
|
|
302
|
+
.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}
|
|
303
|
+
.task-detail-header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:16px}
|
|
304
|
+
.task-detail-header h2{font-size:18px;font-weight:700;color:var(--text);line-height:1.4;flex:1}
|
|
305
|
+
.task-detail-meta{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:20px;font-size:12px;color:var(--text-sec)}
|
|
306
|
+
.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}
|
|
307
|
+
.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}
|
|
308
|
+
.task-detail-summary:empty::after{content:'Summary not yet generated (task still active)';color:var(--text-muted);font-style:italic}
|
|
309
|
+
.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)}
|
|
310
|
+
.task-detail-summary .summary-section-title:first-child{margin-top:0}
|
|
311
|
+
.task-detail-summary ul{margin:4px 0 8px 0;padding-left:20px}
|
|
312
|
+
.task-detail-summary li{margin:3px 0;color:var(--text-sec);line-height:1.6}
|
|
313
|
+
.task-detail-chunks-title{font-size:12px;font-weight:700;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}
|
|
314
|
+
.task-detail-chunks{display:flex;flex-direction:column;gap:14px;padding:8px 0}
|
|
315
|
+
.task-chunk-item{display:flex;flex-direction:column;max-width:82%;font-size:13px;line-height:1.6}
|
|
316
|
+
.task-chunk-item.role-user{align-self:flex-end;align-items:flex-end}
|
|
317
|
+
.task-chunk-item.role-assistant,.task-chunk-item.role-tool{align-self:flex-start;align-items:flex-start}
|
|
318
|
+
.task-chunk-role{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;margin-bottom:3px;padding:0 4px}
|
|
319
|
+
.task-chunk-role.user{color:var(--pri)}
|
|
320
|
+
.task-chunk-role.assistant{color:var(--green)}
|
|
321
|
+
.task-chunk-role.tool{color:var(--amber)}
|
|
322
|
+
.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}
|
|
323
|
+
.task-chunk-bubble.expanded{max-height:none}
|
|
324
|
+
.role-user .task-chunk-bubble{background:var(--pri);color:#000;border-bottom-right-radius:4px}
|
|
325
|
+
.role-assistant .task-chunk-bubble{background:var(--bg-card);border:1px solid var(--border);color:var(--text-sec);border-bottom-left-radius:4px}
|
|
326
|
+
.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}
|
|
327
|
+
.task-chunk-bubble:hover{filter:brightness(1.05)}
|
|
328
|
+
.task-chunk-time{font-size:10px;color:var(--text-muted);margin-top:3px;padding:0 4px}
|
|
329
|
+
[data-theme="light"] .role-user .task-chunk-bubble{background:var(--pri);color:#fff}
|
|
330
|
+
[data-theme="light"] .role-assistant .task-chunk-bubble{background:#f0f0f0;border:none;color:#333}
|
|
331
|
+
[data-theme="light"] .task-detail-panel{background:#fff}
|
|
332
|
+
[data-theme="light"] .task-card{background:#fff}
|
|
333
|
+
[data-theme="light"] .tasks-stat{background:#fff}
|
|
334
|
+
|
|
335
|
+
/* ─── Skills ─── */
|
|
336
|
+
.skills-view{display:none;flex:1;min-width:0;flex-direction:column;gap:16px}
|
|
337
|
+
.skills-view.show{display:flex}
|
|
338
|
+
.skill-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}
|
|
339
|
+
.skill-card:hover{border-color:var(--border-glow);background:var(--bg-card-hover);transform:translateY(-1px);box-shadow:var(--shadow)}
|
|
340
|
+
.skill-card::before{content:'';position:absolute;top:0;left:0;bottom:0;width:3px;border-radius:3px 0 0 3px;background:var(--violet)}
|
|
341
|
+
.skill-card.installed::before{background:var(--green)}
|
|
342
|
+
.skill-card.archived{opacity:.5}
|
|
343
|
+
.skill-card.archived::before{background:var(--text-muted)}
|
|
344
|
+
.skill-card-top{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:6px}
|
|
345
|
+
.skill-card-name{font-size:15px;font-weight:700;color:var(--text);flex:1}
|
|
346
|
+
.skill-card-badges{display:flex;gap:6px;align-items:center}
|
|
347
|
+
.skill-badge{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;padding:3px 10px;border-radius:20px}
|
|
348
|
+
.skill-badge.version{color:var(--violet);background:rgba(139,92,246,.15)}
|
|
349
|
+
.skill-badge.installed{color:var(--green);background:var(--green-bg)}
|
|
350
|
+
.skill-badge.status-active{color:var(--pri);background:var(--pri-glow)}
|
|
351
|
+
.skill-badge.status-archived{color:var(--text-muted);background:rgba(128,128,128,.15)}
|
|
352
|
+
.skill-badge.status-draft{color:var(--amber);background:var(--amber-bg)}
|
|
353
|
+
.skill-badge.quality{font-size:10px;font-weight:700;padding:3px 10px;border-radius:20px}
|
|
354
|
+
.skill-badge.quality.high{color:var(--green);background:var(--green-bg)}
|
|
355
|
+
.skill-badge.quality.mid{color:var(--amber);background:var(--amber-bg)}
|
|
356
|
+
.skill-badge.quality.low{color:var(--rose);background:var(--rose-bg)}
|
|
357
|
+
.skill-card.draft{opacity:.75}
|
|
358
|
+
.skill-card.draft::before{background:var(--amber)}
|
|
359
|
+
.skill-card-desc{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}
|
|
360
|
+
.skill-card-bottom{display:flex;align-items:center;gap:14px;font-size:11px;color:var(--text-muted);flex-wrap:wrap}
|
|
361
|
+
.skill-card-bottom .tag{display:flex;align-items:center;gap:4px}
|
|
362
|
+
.skill-card-tags{display:flex;gap:4px;flex-wrap:wrap}
|
|
363
|
+
.skill-tag{font-size:10px;padding:2px 8px;border-radius:10px;background:rgba(139,92,246,.1);color:var(--violet);font-weight:500}
|
|
364
|
+
.skill-detail-desc{font-size:13px;color:var(--text-sec);line-height:1.6;margin-bottom:16px;padding:12px 16px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius)}
|
|
365
|
+
.skill-version-item{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:12px 16px}
|
|
366
|
+
.skill-version-header{display:flex;align-items:center;gap:10px;margin-bottom:6px}
|
|
367
|
+
.skill-version-badge{font-size:11px;font-weight:700;color:var(--violet);background:rgba(139,92,246,.12);padding:2px 8px;border-radius:8px}
|
|
368
|
+
.skill-version-type{font-size:10px;font-weight:600;text-transform:uppercase;color:var(--text-muted);letter-spacing:.04em}
|
|
369
|
+
.skill-version-changelog{font-size:12px;color:var(--text);line-height:1.5;font-weight:600}
|
|
370
|
+
.skill-version-summary{font-size:12px;color:var(--text-sec);line-height:1.6;margin-top:6px;padding:8px 12px;background:rgba(139,92,246,.04);border-left:2px solid rgba(139,92,246,.2);border-radius:0 6px 6px 0}
|
|
371
|
+
.skill-version-time{font-size:10px;color:var(--text-muted);margin-top:4px}
|
|
372
|
+
.skill-related-task{display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--bg-card);border:1px solid var(--border);border-radius:8px;cursor:pointer;transition:all .2s}
|
|
373
|
+
.skill-related-task:hover{border-color:var(--border-glow);background:var(--bg-card-hover)}
|
|
374
|
+
.skill-related-task .relation{font-size:10px;font-weight:600;text-transform:uppercase;color:var(--text-muted);letter-spacing:.04em;min-width:80px}
|
|
375
|
+
.skill-related-task .task-title{font-size:13px;color:var(--text);flex:1}
|
|
376
|
+
.skill-files-list{display:flex;flex-direction:column;gap:6px;margin-bottom:16px}
|
|
377
|
+
.skill-file-item{display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--bg-card);border:1px solid var(--border);border-radius:8px;font-size:12px}
|
|
378
|
+
.skill-file-icon{font-size:14px;width:20px;text-align:center}
|
|
379
|
+
.skill-file-name{flex:1;color:var(--text);font-family:SF Mono,Monaco,Consolas,monospace}
|
|
380
|
+
.skill-file-type{font-size:10px;font-weight:600;text-transform:uppercase;color:var(--text-muted);letter-spacing:.04em}
|
|
381
|
+
.skill-file-size{font-size:10px;color:var(--text-muted)}
|
|
382
|
+
.skill-download-btn{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:8px;background:var(--pri-grad);color:#fff;font-size:12px;font-weight:600;border:none;cursor:pointer;transition:all .2s}
|
|
383
|
+
.skill-download-btn:hover{opacity:.85;transform:translateY(-1px)}
|
|
384
|
+
.task-skill-section{margin-bottom:16px;padding:14px 16px;border-radius:var(--radius);border:1px solid var(--border)}
|
|
385
|
+
.task-skill-section.status-generated{border-color:var(--green);background:var(--green-bg)}
|
|
386
|
+
.task-skill-section.status-generating{border-color:var(--amber);background:var(--amber-bg)}
|
|
387
|
+
.task-skill-section.status-not_generated,.task-skill-section.status-skipped{border-color:var(--border);background:var(--bg-card)}
|
|
388
|
+
.task-skill-section .skill-status-header{display:flex;align-items:center;gap:8px;margin-bottom:6px;font-size:13px;font-weight:600;color:var(--text)}
|
|
389
|
+
.task-skill-section .skill-status-reason{font-size:12px;color:var(--text-sec);line-height:1.5}
|
|
390
|
+
.task-skill-section .skill-link-card{margin-top:10px;padding:10px 14px;background:var(--bg-card);border:1px solid var(--border);border-radius:8px;cursor:pointer;transition:all .2s}
|
|
391
|
+
.task-skill-section .skill-link-card:hover{border-color:var(--pri);background:var(--bg-card-hover)}
|
|
392
|
+
.task-skill-section .skill-link-name{font-size:13px;font-weight:600;color:var(--pri)}
|
|
393
|
+
.task-skill-section .skill-link-meta{font-size:11px;color:var(--text-sec);margin-top:4px}
|
|
394
|
+
.task-id-full{font-family:monospace;font-size:11px;color:var(--text-muted);word-break:break-all;user-select:all;cursor:text;padding:2px 6px;background:var(--bg-card);border-radius:4px;border:1px solid var(--border)}
|
|
395
|
+
[data-theme="light"] .skill-card{background:#fff}
|
|
396
|
+
[data-theme="light"] .skill-detail-desc{background:#f8fafc}
|
|
397
|
+
[data-theme="light"] .skill-version-item{background:#f8fafc}
|
|
398
|
+
|
|
399
|
+
/* ─── Analytics / 统计 ─── */
|
|
400
|
+
.nav-tabs{display:flex;align-items:center;gap:2px;background:rgba(255,255,255,.06);border-radius:10px;padding:3px}
|
|
401
|
+
.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}
|
|
402
|
+
.nav-tabs .tab:hover{color:var(--text)}
|
|
403
|
+
.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)}
|
|
404
|
+
[data-theme="light"] .nav-tabs{background:rgba(0,0,0,.05)}
|
|
405
|
+
[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)}
|
|
406
|
+
.analytics-view,.settings-view,.logs-view{display:none;flex:1;min-width:0;flex-direction:column;gap:20px}
|
|
407
|
+
.analytics-view.show,.settings-view.show,.logs-view.show{display:flex}
|
|
408
|
+
|
|
409
|
+
/* ─── Logs ─── */
|
|
410
|
+
.logs-toolbar{display:flex;align-items:center;justify-content:space-between;padding:8px 0}
|
|
411
|
+
.logs-toolbar-left{display:flex;align-items:center;gap:8px}
|
|
412
|
+
.logs-toolbar-right{display:flex;align-items:center;gap:8px}
|
|
413
|
+
.logs-list{display:flex;flex-direction:column;gap:8px;overflow-y:auto;flex:1;min-height:0}
|
|
414
|
+
.log-entry{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);overflow:hidden;transition:border-color .2s}
|
|
415
|
+
.log-entry:hover{border-color:var(--border-glow)}
|
|
416
|
+
.log-header{display:flex;align-items:center;gap:10px;padding:12px 16px;cursor:pointer;user-select:none;transition:background .15s}
|
|
417
|
+
.log-header:hover{background:rgba(255,255,255,.03)}
|
|
418
|
+
[data-theme="light"] .log-header:hover{background:rgba(0,0,0,.02)}
|
|
419
|
+
.log-tool-badge{font-family:'SF Mono',Consolas,monospace;font-size:11px;font-weight:700;padding:3px 8px;border-radius:4px;white-space:nowrap;letter-spacing:.3px}
|
|
420
|
+
.log-tool-badge.memory_search{background:rgba(59,130,246,.15);color:#60a5fa}
|
|
421
|
+
.log-tool-badge.memory_add{background:rgba(168,85,247,.15);color:#c084fc}
|
|
422
|
+
.log-tool-badge.auto_recall{background:rgba(168,85,247,.15);color:#c084fc}
|
|
423
|
+
.log-tool-badge.memory_timeline{background:rgba(34,197,94,.15);color:#4ade80}
|
|
424
|
+
.log-tool-badge.memory_get{background:rgba(251,146,60,.15);color:#fb923c}
|
|
425
|
+
.log-tool-badge.task_summary{background:rgba(245,158,11,.15);color:#fbbf24}
|
|
426
|
+
.log-tool-badge.skill_get{background:rgba(236,72,153,.15);color:#f472b6}
|
|
427
|
+
.log-tool-badge.skill_install{background:rgba(14,165,233,.15);color:#38bdf8}
|
|
428
|
+
.log-tool-badge.memory_viewer{background:rgba(100,116,139,.15);color:#94a3b8}
|
|
429
|
+
.log-dur{font-family:'SF Mono',Consolas,monospace;font-size:10px;color:var(--text-sec);opacity:.7}
|
|
430
|
+
.log-time{margin-left:auto;font-size:11px;color:var(--text-sec);font-family:'SF Mono',Consolas,monospace;white-space:nowrap}
|
|
431
|
+
.log-status{width:7px;height:7px;border-radius:50%;flex-shrink:0}
|
|
432
|
+
.log-status.ok{background:#4ade80;box-shadow:0 0 4px rgba(74,222,128,.5)}
|
|
433
|
+
.log-status.fail{background:#f87171;box-shadow:0 0 4px rgba(248,113,113,.5)}
|
|
434
|
+
.log-summary{padding:8px 16px 10px;font-size:12px;color:var(--text-sec);line-height:1.5}
|
|
435
|
+
.log-summary-kv{display:inline-flex;align-items:center;gap:4px;margin-right:12px;font-size:11px}
|
|
436
|
+
.log-summary-kv .kv-label{color:var(--text-sec);opacity:.7}
|
|
437
|
+
.log-summary-kv .kv-val{color:var(--text);font-family:'SF Mono',Consolas,monospace;font-size:11px}
|
|
438
|
+
.log-summary-query{margin-top:4px;padding:6px 10px;background:rgba(59,130,246,.08);border-radius:6px;font-size:12px;color:var(--text);border-left:3px solid rgba(59,130,246,.4);line-height:1.4}
|
|
439
|
+
.log-summary-stats{display:flex;gap:6px;flex-wrap:wrap;margin-top:6px}
|
|
440
|
+
.log-stat-chip{display:inline-flex;align-items:center;gap:3px;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:600;font-family:'SF Mono',Consolas,monospace}
|
|
441
|
+
.log-stat-chip.stored{background:rgba(74,222,128,.12);color:#4ade80}
|
|
442
|
+
.log-stat-chip.skipped{background:rgba(100,116,139,.12);color:#94a3b8}
|
|
443
|
+
.log-stat-chip.dedup{background:rgba(251,146,60,.12);color:#fb923c}
|
|
444
|
+
.log-stat-chip.merged{background:rgba(168,85,247,.12);color:#c084fc}
|
|
445
|
+
.log-stat-chip.errors{background:rgba(248,113,113,.12);color:#f87171}
|
|
446
|
+
.log-msg-list{margin-top:8px;display:flex;flex-direction:column;gap:4px}
|
|
447
|
+
.log-msg-item{display:flex;gap:8px;align-items:flex-start;font-size:11.5px;line-height:1.5;padding:4px 10px;border-radius:6px;background:rgba(255,255,255,.02)}
|
|
448
|
+
[data-theme="light"] .log-msg-item{background:rgba(0,0,0,.02)}
|
|
449
|
+
.log-msg-role{flex-shrink:0;font-size:10px;font-weight:600;padding:1px 6px;border-radius:4px;text-transform:uppercase;letter-spacing:.3px}
|
|
450
|
+
.log-msg-role.user{background:rgba(59,130,246,.12);color:#60a5fa}
|
|
451
|
+
.log-msg-role.assistant{background:rgba(168,85,247,.12);color:#c084fc}
|
|
452
|
+
.log-msg-role.system{background:rgba(100,116,139,.12);color:#94a3b8}
|
|
453
|
+
.log-msg-action{flex-shrink:0;font-size:10px;font-weight:600;padding:1px 6px;border-radius:4px}
|
|
454
|
+
.log-msg-action.stored{color:#4ade80}
|
|
455
|
+
.log-msg-action.exact-dup{color:#94a3b8}
|
|
456
|
+
.log-msg-action.dedup{color:#fb923c}
|
|
457
|
+
.log-msg-action.merged{color:#c084fc}
|
|
458
|
+
.log-msg-action.error{color:#f87171}
|
|
459
|
+
.log-msg-text{color:var(--text);opacity:.85;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}
|
|
460
|
+
.log-detail{display:none;border-top:1px solid var(--border);padding:0}
|
|
461
|
+
.log-detail.open{display:block}
|
|
462
|
+
.log-expand-btn{font-size:10px;color:var(--text-sec);opacity:.5;margin-left:auto;transition:transform .2s,opacity .15s;display:inline-block}
|
|
463
|
+
.log-entry.expanded .log-expand-btn{transform:rotate(180deg);opacity:.8}
|
|
464
|
+
.logs-pagination{display:flex;align-items:center;justify-content:center;gap:4px;padding:12px 0;flex-wrap:wrap}
|
|
465
|
+
.logs-pagination .btn{min-width:32px;padding:4px 8px;font-size:12px}
|
|
466
|
+
.logs-pagination .btn-primary{background:var(--primary);color:#fff;border-color:var(--primary)}
|
|
467
|
+
.logs-pagination .page-ellipsis{color:var(--text-sec);font-size:12px;padding:0 4px}
|
|
468
|
+
.logs-pagination .page-total{font-size:11px;color:var(--text-sec);margin-left:8px}
|
|
469
|
+
.log-io-section{padding:10px 14px}
|
|
470
|
+
.log-io-label{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1px;color:var(--text-sec);margin-bottom:6px}
|
|
471
|
+
.log-io-content{font-family:'SF Mono',Consolas,monospace;font-size:11px;line-height:1.6;color:var(--text);white-space:pre-wrap;word-break:break-all;background:rgba(0,0,0,.2);border-radius:6px;padding:10px 12px;max-height:300px;overflow-y:auto}
|
|
472
|
+
.log-io-section+.log-io-section{border-top:1px dashed var(--border)}
|
|
473
|
+
[data-theme="light"] .log-io-content{background:rgba(0,0,0,.04)}
|
|
474
|
+
[data-theme="light"] .log-summary-query{background:rgba(59,130,246,.06)}
|
|
475
|
+
.settings-section{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:24px 28px}
|
|
476
|
+
.settings-section h3{font-size:13px;font-weight:700;color:var(--text);margin-bottom:16px;display:flex;align-items:center;gap:8px}
|
|
477
|
+
.settings-section h3 .icon{font-size:16px;opacity:.8}
|
|
478
|
+
.settings-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}
|
|
479
|
+
@media(max-width:800px){.settings-grid{grid-template-columns:1fr}}
|
|
480
|
+
.settings-field{display:flex;flex-direction:column;gap:4px}
|
|
481
|
+
.settings-field label{font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.04em}
|
|
482
|
+
.settings-field input,.settings-field select{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:8px 12px;color:var(--text);font-size:13px;font-family:inherit;transition:border-color .15s}
|
|
483
|
+
.settings-field input:focus,.settings-field select:focus{outline:none;border-color:var(--pri)}
|
|
484
|
+
.settings-field input[type="password"]{font-family:'Courier New',monospace;letter-spacing:.05em}
|
|
485
|
+
.settings-field .field-hint{font-size:10px;color:var(--text-muted);margin-top:2px}
|
|
486
|
+
.settings-field.full-width{grid-column:1/-1}
|
|
487
|
+
.settings-toggle{display:flex;align-items:center;gap:10px;padding:4px 0}
|
|
488
|
+
.settings-toggle label{font-size:12px;font-weight:500;color:var(--text-sec);text-transform:none;letter-spacing:0}
|
|
489
|
+
.toggle-switch{position:relative;width:36px;height:20px;cursor:pointer}
|
|
490
|
+
.toggle-switch input{opacity:0;width:0;height:0}
|
|
491
|
+
.toggle-slider{position:absolute;inset:0;background:var(--border);border-radius:20px;transition:.2s}
|
|
492
|
+
.toggle-slider::before{content:'';position:absolute;height:14px;width:14px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}
|
|
493
|
+
.toggle-switch input:checked+.toggle-slider{background:var(--pri)}
|
|
494
|
+
.toggle-switch input:checked+.toggle-slider::before{transform:translateX(16px)}
|
|
495
|
+
.settings-actions{display:flex;gap:12px;justify-content:flex-end;align-items:center;margin-top:16px;padding-top:16px;border-top:1px solid var(--border)}
|
|
496
|
+
.settings-actions .btn{min-width:110px;padding:10px 20px;font-size:13px}
|
|
497
|
+
.settings-actions .btn-primary{background:rgba(99,102,241,.08);color:var(--pri);border:1px solid rgba(99,102,241,.25);font-weight:600}
|
|
498
|
+
.settings-actions .btn-primary:hover{background:rgba(99,102,241,.14);border-color:var(--pri)}
|
|
499
|
+
[data-theme="light"] .settings-actions .btn-primary{background:rgba(79,70,229,.06);color:#4f46e5;border:1px solid rgba(79,70,229,.2)}
|
|
500
|
+
[data-theme="light"] .settings-actions .btn-primary:hover{background:rgba(79,70,229,.1);border-color:#4f46e5}
|
|
501
|
+
.settings-saved{display:inline-flex;align-items:center;gap:6px;color:var(--green);font-size:12px;font-weight:600;opacity:0;transition:opacity .3s}
|
|
502
|
+
.settings-saved.show{opacity:1}
|
|
503
|
+
.feed-wrap{flex:1;min-width:0;display:flex;flex-direction:column}
|
|
504
|
+
.feed-wrap.hide{display:none}
|
|
505
|
+
.analytics-cards{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
|
|
506
|
+
.analytics-card{position:relative;overflow:hidden;border-radius:var(--radius-lg);padding:22px 20px;transition:all .2s ease;border:1px solid var(--border);background:var(--bg-card)}
|
|
507
|
+
.analytics-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:var(--pri);opacity:.5}
|
|
508
|
+
.analytics-card::after{display:none}
|
|
509
|
+
.analytics-card:hover{transform:translateY(-2px);box-shadow:var(--shadow);border-color:var(--border-glow)}
|
|
510
|
+
.analytics-card.green::before{background:var(--green)}
|
|
511
|
+
.analytics-card.amber::before{background:var(--amber)}
|
|
512
|
+
.analytics-card .ac-value{font-size:28px;font-weight:700;letter-spacing:-.03em;color:var(--text);line-height:1;-webkit-text-fill-color:unset;background:none}
|
|
513
|
+
.analytics-card.green .ac-value{color:var(--green);background:none}
|
|
514
|
+
.analytics-card.amber .ac-value{color:var(--amber);background:none}
|
|
515
|
+
.analytics-card .ac-label{font-size:11px;color:var(--text-muted);margin-top:6px;font-weight:500;text-transform:uppercase;letter-spacing:.06em}
|
|
516
|
+
.analytics-section{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:22px 24px;position:relative;overflow:hidden}
|
|
517
|
+
.analytics-section::before{display:none}
|
|
518
|
+
.analytics-section h3{font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:16px;display:flex;align-items:center;gap:8px}
|
|
519
|
+
.analytics-section h3 .icon{font-size:14px;opacity:.6}
|
|
520
|
+
.chart-bars{display:flex;align-items:flex-end;gap:4px;padding:8px 0;overflow-x:auto;justify-content:center}
|
|
521
|
+
.chart-bar-wrap{flex:1;min-width:28px;max-width:80px;display:flex;flex-direction:column;align-items:center;gap:4px;position:relative}
|
|
522
|
+
.chart-bar-col{width:100%;height:160px;display:flex;flex-direction:column;justify-content:flex-end;align-items:stretch}
|
|
523
|
+
.chart-bar-wrap:hover .chart-bar{opacity:1}
|
|
524
|
+
.chart-bar-wrap:hover .chart-bar-label{color:var(--text)}
|
|
525
|
+
.chart-bar-wrap:hover .chart-tip{opacity:1;transform:translateX(-50%) translateY(0)}
|
|
526
|
+
.chart-tip{position:absolute;top:-6px;left:50%;transform:translateX(-50%) translateY(4px);background:var(--bg-card);border:1px solid var(--border-glow);color:var(--text);padding:2px 8px;border-radius:6px;font-size:10px;font-weight:600;white-space:nowrap;z-index:5;pointer-events:none;box-shadow:var(--shadow);opacity:0;transition:all .15s ease}
|
|
527
|
+
.chart-bar{width:100%;border-radius:3px 3px 1px 1px;background:#818cf8;opacity:.75;transition:all .2s ease}
|
|
528
|
+
.chart-bar.violet{background:#6366f1}
|
|
529
|
+
.chart-bar.green{background:var(--green)}
|
|
530
|
+
.chart-bar.zero{background:var(--border);opacity:.3;border-radius:2px}
|
|
531
|
+
.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}
|
|
532
|
+
.chart-legend{display:flex;gap:14px;margin-top:12px;flex-wrap:wrap;font-size:11px;color:var(--text-sec);font-weight:500}
|
|
533
|
+
.chart-legend span{display:inline-flex;align-items:center;gap:5px}
|
|
534
|
+
.chart-legend .dot{width:8px;height:8px;border-radius:2px}
|
|
535
|
+
.chart-legend .dot.pri{background:var(--pri)}
|
|
536
|
+
.tool-chart-svg{width:100%;height:100%;display:block}
|
|
537
|
+
.tool-chart-svg .grid-line{stroke:var(--border);stroke-dasharray:3 3;stroke-width:0.5}
|
|
538
|
+
.tool-chart-svg .axis-label{fill:var(--text-muted);font-size:10px;font-family:var(--mono)}
|
|
539
|
+
.tool-chart-svg .data-line{fill:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:2000;stroke-dashoffset:2000;animation:lineIn .6s ease forwards}
|
|
540
|
+
@keyframes lineIn{to{stroke-dashoffset:0}}
|
|
541
|
+
.tool-chart-svg .data-area{opacity:1}
|
|
542
|
+
.tool-chart-svg .hover-dot{r:3.5;stroke-width:2;stroke:var(--bg);opacity:0;transition:opacity .1s}
|
|
543
|
+
.tool-chart-svg .hover-dot.show{opacity:1}
|
|
544
|
+
.tool-chart-tooltip{position:absolute;top:0;left:0;background:var(--bg-card);border:1px solid var(--border-glow);color:var(--text);padding:8px 12px;border-radius:8px;font-size:11px;font-family:var(--mono);pointer-events:none;opacity:0;transition:opacity .1s;z-index:10;box-shadow:var(--shadow-lg);white-space:nowrap}
|
|
545
|
+
.tool-chart-tooltip.show{opacity:1}
|
|
546
|
+
.tool-chart-tooltip .tt-time{color:var(--text-muted);font-size:10px;margin-bottom:4px;font-weight:500}
|
|
547
|
+
.tool-chart-tooltip .tt-row{display:flex;align-items:center;gap:6px;margin:2px 0}
|
|
548
|
+
.tool-chart-tooltip .tt-dot{width:6px;height:6px;border-radius:2px;flex-shrink:0}
|
|
549
|
+
.tool-chart-tooltip .tt-val{font-weight:600;margin-left:auto;padding-left:12px}
|
|
550
|
+
.tool-agg-table{width:100%;border-collapse:collapse;font-size:12px}
|
|
551
|
+
.tool-agg-table th{text-align:left;font-weight:500;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em;font-size:10px;padding:8px 12px;border-bottom:1px solid var(--border)}
|
|
552
|
+
.tool-agg-table td{padding:8px 12px;color:var(--text-sec);border-bottom:1px solid var(--border)}
|
|
553
|
+
.tool-agg-table tr:hover td{background:rgba(99,102,241,.04);color:var(--text)}
|
|
554
|
+
.tool-agg-table .tool-name{font-weight:600;color:var(--text);display:flex;align-items:center;gap:6px}
|
|
555
|
+
.tool-agg-table .tool-dot{width:8px;height:8px;border-radius:2px;flex-shrink:0}
|
|
556
|
+
.tool-agg-table .ms-val{font-family:var(--mono);font-weight:600}
|
|
557
|
+
.tool-agg-table .ms-val.fast{color:var(--green)}
|
|
558
|
+
.tool-agg-table .ms-val.medium{color:var(--amber)}
|
|
559
|
+
.tool-agg-table .ms-val.slow{color:var(--accent)}
|
|
560
|
+
.chart-legend .dot.violet{background:var(--violet)}
|
|
561
|
+
.chart-legend .dot.green{background:var(--green)}
|
|
562
|
+
.breakdown-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:20px}
|
|
563
|
+
.breakdown-item{display:flex;flex-direction:column;gap:5px;padding:10px 12px;background:rgba(255,255,255,.02);border-radius:8px;border:1px solid var(--border);transition:all .15s}
|
|
564
|
+
.breakdown-item:hover{border-color:var(--border-glow);background:rgba(255,255,255,.04)}
|
|
565
|
+
.breakdown-item .bd-top{display:flex;align-items:center;justify-content:space-between}
|
|
566
|
+
.breakdown-item .label{font-size:12px;color:var(--text-sec);font-weight:500;text-transform:capitalize}
|
|
567
|
+
.breakdown-item .value{font-size:13px;font-weight:600;color:var(--text)}
|
|
568
|
+
.breakdown-bar-wrap{height:3px;background:rgba(255,255,255,.06);border-radius:2px;overflow:hidden}
|
|
569
|
+
.breakdown-bar{height:100%;border-radius:2px;background:var(--pri);transition:width .5s ease}
|
|
570
|
+
.metrics-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:16px;flex-wrap:wrap}
|
|
571
|
+
.range-btn{padding:5px 12px;border-radius:6px;border:1px solid var(--border);background:transparent;color:var(--text-sec);font-size:12px;font-weight:500;cursor:pointer;transition:all .15s}
|
|
572
|
+
.range-btn:hover{border-color:var(--pri);color:var(--pri)}
|
|
573
|
+
.range-btn.active{background:rgba(99,102,241,.08);color:var(--pri);border-color:rgba(99,102,241,.25)}
|
|
574
|
+
|
|
575
|
+
.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
576
|
.theme-toggle .theme-icon-light{display:none}
|
|
203
577
|
.theme-toggle .theme-icon-dark{display:inline}
|
|
204
578
|
[data-theme="light"] .theme-toggle .theme-icon-light{display:inline}
|
|
205
579
|
[data-theme="light"] .theme-toggle .theme-icon-dark{display:none}
|
|
206
580
|
|
|
207
|
-
.auth-
|
|
208
|
-
.auth-theme-toggle:
|
|
581
|
+
.auth-top-actions{position:absolute;top:16px;right:16px;z-index:10;display:flex;align-items:center;gap:2px}
|
|
582
|
+
.auth-theme-toggle{min-width:28px;height:28px;border:none;border-radius:14px;background:rgba(255,255,255,.12);color:rgba(255,255,255,.7);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;transition:all .2s;padding:0 8px;font-weight:600}
|
|
583
|
+
.auth-theme-toggle:hover{background:rgba(255,255,255,.25);color:#fff}
|
|
209
584
|
.auth-theme-toggle .theme-icon-light{display:none}
|
|
210
585
|
.auth-theme-toggle .theme-icon-dark{display:inline}
|
|
211
|
-
[data-theme="light"] .auth-theme-toggle{background:rgba(0,0,0,.
|
|
212
|
-
[data-theme="light"] .auth-theme-toggle:hover{background:rgba(0,0,0,.
|
|
586
|
+
[data-theme="light"] .auth-theme-toggle{color:rgba(0,0,0,.4);background:rgba(0,0,0,.05)}
|
|
587
|
+
[data-theme="light"] .auth-theme-toggle:hover{background:rgba(0,0,0,.1);color:#0f172a}
|
|
588
|
+
[data-theme="light"] .auth-top-actions{background:none}
|
|
213
589
|
[data-theme="light"] .auth-theme-toggle .theme-icon-light{display:inline}
|
|
214
590
|
[data-theme="light"] .auth-theme-toggle .theme-icon-dark{display:none}
|
|
215
591
|
|
|
216
|
-
@media(max-width:
|
|
592
|
+
@media(max-width:1100px){.analytics-cards{grid-template-columns:repeat(3,1fr)}}
|
|
593
|
+
@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
594
|
</style>
|
|
218
595
|
</head>
|
|
219
596
|
<body>
|
|
220
597
|
|
|
221
598
|
<!-- ─── Auth: Setup Password ─── -->
|
|
222
599
|
<div id="setupScreen" class="auth-screen" style="display:none">
|
|
223
|
-
<
|
|
600
|
+
<div class="auth-top-actions">
|
|
601
|
+
<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>
|
|
602
|
+
<button class="auth-theme-toggle" onclick="toggleLang()" aria-label="Switch language"><span data-i18n="lang.switch">EN</span></button>
|
|
603
|
+
</div>
|
|
224
604
|
<div class="auth-card">
|
|
225
605
|
<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>
|
|
606
|
+
<h1 data-i18n="title">OpenClaw Memory</h1>
|
|
607
|
+
<p style="font-size:12px;color:var(--text-sec);margin-bottom:6px" data-i18n="subtitle">Powered by MemOS</p>
|
|
608
|
+
<p data-i18n="setup.desc">Set a password to protect your memories</p>
|
|
609
|
+
<input type="password" id="setupPw" data-i18n-ph="setup.pw" placeholder="Enter a password (4+ characters)" autofocus>
|
|
610
|
+
<input type="password" id="setupPw2" data-i18n-ph="setup.pw2" placeholder="Confirm password">
|
|
611
|
+
<button class="btn-auth" onclick="doSetup()" data-i18n="setup.btn">Set Password & Enter</button>
|
|
232
612
|
<div class="error-msg" id="setupErr"></div>
|
|
233
613
|
</div>
|
|
234
614
|
</div>
|
|
235
615
|
|
|
236
616
|
<!-- ─── Auth: Login ─── -->
|
|
237
617
|
<div id="loginScreen" class="auth-screen" style="display:none">
|
|
238
|
-
<
|
|
618
|
+
<div class="auth-top-actions">
|
|
619
|
+
<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>
|
|
620
|
+
<button class="auth-theme-toggle" onclick="toggleLang()" aria-label="Switch language"><span data-i18n="lang.switch">EN</span></button>
|
|
621
|
+
</div>
|
|
239
622
|
<div class="auth-card">
|
|
240
623
|
<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>
|
|
624
|
+
<h1 data-i18n="title">OpenClaw Memory</h1>
|
|
625
|
+
<p style="font-size:12px;color:var(--text-sec);margin-bottom:6px" data-i18n="subtitle">Powered by MemOS</p>
|
|
626
|
+
<p data-i18n="login.desc">Enter your password to access memories</p>
|
|
244
627
|
<div id="loginForm">
|
|
245
|
-
<input type="password" id="loginPw" placeholder="Password" autofocus>
|
|
246
|
-
<button class="btn-auth" onclick="doLogin()">Unlock</button>
|
|
628
|
+
<input type="password" id="loginPw" data-i18n-ph="login.pw" placeholder="Password" autofocus>
|
|
629
|
+
<button class="btn-auth" onclick="doLogin()" data-i18n="login.btn">Unlock</button>
|
|
247
630
|
<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>
|
|
631
|
+
<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
632
|
</div>
|
|
250
633
|
<div id="resetForm" style="display:none">
|
|
251
634
|
<div class="reset-guide">
|
|
252
635
|
<div class="reset-step">
|
|
253
636
|
<div class="step-num">1</div>
|
|
254
637
|
<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>
|
|
638
|
+
<div class="step-title" data-i18n="reset.step1.title">Open Terminal</div>
|
|
639
|
+
<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
640
|
<div class="cmd-box" onclick="copyCmd(this)">
|
|
258
641
|
<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>
|
|
642
|
+
<span class="copy-hint" data-i18n="copy.hint">Click to copy</span>
|
|
260
643
|
</div>
|
|
261
644
|
</div>
|
|
262
645
|
</div>
|
|
263
646
|
<div class="reset-step">
|
|
264
647
|
<div class="step-num">2</div>
|
|
265
648
|
<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>
|
|
649
|
+
<div class="step-title" data-i18n="reset.step2.title">Find the token</div>
|
|
650
|
+
<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
651
|
</div>
|
|
269
652
|
</div>
|
|
270
653
|
<div class="reset-step">
|
|
271
654
|
<div class="step-num">3</div>
|
|
272
655
|
<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>
|
|
656
|
+
<div class="step-title" data-i18n="reset.step3.title">Paste & reset</div>
|
|
657
|
+
<div class="step-desc" data-i18n="reset.step3.desc">Paste the token below and set your new password.</div>
|
|
275
658
|
</div>
|
|
276
659
|
</div>
|
|
277
660
|
</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>
|
|
661
|
+
<input type="text" id="resetToken" data-i18n-ph="reset.token" placeholder="Paste reset token here" style="margin-bottom:8px;font-family:monospace">
|
|
662
|
+
<input type="password" id="resetNewPw" data-i18n-ph="reset.newpw" placeholder="New password (4+ characters)">
|
|
663
|
+
<input type="password" id="resetNewPw2" data-i18n-ph="reset.newpw2" placeholder="Confirm new password">
|
|
664
|
+
<button class="btn-auth" onclick="doReset()" data-i18n="reset.btn">Reset Password</button>
|
|
282
665
|
<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>
|
|
666
|
+
<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
667
|
</div>
|
|
285
668
|
</div>
|
|
286
669
|
</div>
|
|
@@ -290,78 +673,364 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
290
673
|
<div class="topbar">
|
|
291
674
|
<div class="brand">
|
|
292
675
|
<div class="icon">\u{1F99E}</div>
|
|
293
|
-
<span
|
|
676
|
+
<span data-i18n="title">OpenClaw Memory</span>
|
|
677
|
+
</div>
|
|
678
|
+
<div class="topbar-center">
|
|
679
|
+
<nav class="nav-tabs">
|
|
680
|
+
<button class="tab active" data-view="memories" onclick="switchView('memories')" data-i18n="tab.memories">\u{1F4DA} Memories</button>
|
|
681
|
+
<button class="tab" data-view="tasks" onclick="switchView('tasks')" data-i18n="tab.tasks">\u{1F4CB} Tasks</button>
|
|
682
|
+
<button class="tab" data-view="skills" onclick="switchView('skills')" data-i18n="tab.skills">\u{1F9E0} Skills</button>
|
|
683
|
+
<button class="tab" data-view="analytics" onclick="switchView('analytics')" data-i18n="tab.analytics">\u{1F4CA} Analytics</button>
|
|
684
|
+
<button class="tab" data-view="logs" onclick="switchView('logs')" data-i18n="tab.logs">\u{1F4DD} Logs</button>
|
|
685
|
+
<button class="tab" data-view="settings" onclick="switchView('settings')" data-i18n="tab.settings">\u2699 Settings</button>
|
|
686
|
+
</nav>
|
|
294
687
|
</div>
|
|
295
688
|
<div class="actions">
|
|
689
|
+
<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
690
|
<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>
|
|
691
|
+
<button class="btn btn-ghost btn-sm" onclick="loadAll()" data-i18n="refresh">\u21BB Refresh</button>
|
|
692
|
+
<button class="btn btn-ghost btn-sm" onclick="doLogout()" data-i18n="logout">Logout</button>
|
|
301
693
|
</div>
|
|
302
694
|
</div>
|
|
303
695
|
|
|
304
696
|
<div class="main-content">
|
|
305
697
|
<div class="sidebar" id="sidebar">
|
|
306
698
|
<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>
|
|
699
|
+
<div class="stat-card pri"><div class="stat-value" id="statTotal">-</div><div class="stat-label" data-i18n="stat.memories">Memories</div></div>
|
|
700
|
+
<div class="stat-card green"><div class="stat-value" id="statSessions">-</div><div class="stat-label" data-i18n="stat.sessions">Sessions</div></div>
|
|
701
|
+
<div class="stat-card amber"><div class="stat-value" id="statEmbeddings">-</div><div class="stat-label" data-i18n="stat.embeddings">Embeddings</div></div>
|
|
702
|
+
<div class="stat-card rose"><div class="stat-value" id="statTimeSpan">-</div><div class="stat-label" data-i18n="stat.days">Days</div></div>
|
|
311
703
|
</div>
|
|
312
704
|
<div id="embeddingStatus"></div>
|
|
313
|
-
<div class="section-title">Sessions</div>
|
|
705
|
+
<div class="section-title" data-i18n="sidebar.sessions">Sessions</div>
|
|
314
706
|
<div class="session-list" id="sessionList"></div>
|
|
707
|
+
<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
708
|
</div>
|
|
316
709
|
|
|
710
|
+
<div class="feed-wrap" id="feedWrap">
|
|
317
711
|
<div class="feed">
|
|
318
712
|
<div class="search-bar">
|
|
319
713
|
<span class="search-icon">\u{1F50D}</span>
|
|
320
|
-
<input type="text" id="searchInput" placeholder="Search memories (supports semantic search)..." oninput="debounceSearch()">
|
|
714
|
+
<input type="text" id="searchInput" data-i18n-ph="search.placeholder" placeholder="Search memories (supports semantic search)..." oninput="debounceSearch()">
|
|
321
715
|
</div>
|
|
322
716
|
<div class="search-meta" id="searchMeta"></div>
|
|
323
717
|
<div class="filter-bar" id="filterBar">
|
|
324
|
-
<button class="filter-chip active" data-role="" onclick="setRoleFilter(this,'')">All</button>
|
|
718
|
+
<button class="filter-chip active" data-role="" onclick="setRoleFilter(this,'')" data-i18n="filter.all">All</button>
|
|
325
719
|
<button class="filter-chip" data-role="user" onclick="setRoleFilter(this,'user')">User</button>
|
|
326
720
|
<button class="filter-chip" data-role="assistant" onclick="setRoleFilter(this,'assistant')">Assistant</button>
|
|
327
721
|
<button class="filter-chip" data-role="system" onclick="setRoleFilter(this,'system')">System</button>
|
|
328
722
|
<span class="filter-sep"></span>
|
|
329
723
|
<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>
|
|
724
|
+
<option value="" data-i18n="filter.allkinds">All kinds</option>
|
|
725
|
+
<option value="paragraph" data-i18n="filter.paragraph">Paragraph</option>
|
|
726
|
+
<option value="code_block" data-i18n="filter.code">Code</option>
|
|
727
|
+
<option value="dialog" data-i18n="filter.dialog">Dialog</option>
|
|
728
|
+
<option value="list" data-i18n="filter.list">List</option>
|
|
729
|
+
<option value="error_stack" data-i18n="filter.error">Error</option>
|
|
730
|
+
<option value="command" data-i18n="filter.command">Command</option>
|
|
337
731
|
</select>
|
|
338
732
|
<select id="filterSort" class="filter-select" onchange="applyFilters()">
|
|
339
|
-
<option value="newest">Newest first</option>
|
|
340
|
-
<option value="oldest">Oldest first</option>
|
|
733
|
+
<option value="newest" data-i18n="filter.newest">Newest first</option>
|
|
734
|
+
<option value="oldest" data-i18n="filter.oldest">Oldest first</option>
|
|
341
735
|
</select>
|
|
342
736
|
</div>
|
|
343
737
|
<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>
|
|
738
|
+
<label data-i18n="filter.from">From</label><input type="datetime-local" id="dateFrom" step="1" onchange="applyFilters()">
|
|
739
|
+
<label data-i18n="filter.to">To</label><input type="datetime-local" id="dateTo" step="1" onchange="applyFilters()">
|
|
740
|
+
<button class="btn btn-sm btn-text" onclick="clearDateFilter()" data-i18n="filter.clear">Clear</button>
|
|
347
741
|
</div>
|
|
348
742
|
<div class="memory-list" id="memoryList"><div class="spinner"></div></div>
|
|
349
743
|
<div class="pagination" id="pagination"></div>
|
|
350
744
|
</div>
|
|
745
|
+
</div>
|
|
746
|
+
<div class="tasks-view" id="tasksView">
|
|
747
|
+
<div class="tasks-header">
|
|
748
|
+
<div class="tasks-stats">
|
|
749
|
+
<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>
|
|
750
|
+
<div class="tasks-stat"><span class="tasks-stat-value" id="tasksActiveCount">-</span><span class="tasks-stat-label" data-i18n="tasks.active">Active</span></div>
|
|
751
|
+
<div class="tasks-stat"><span class="tasks-stat-value" id="tasksCompletedCount">-</span><span class="tasks-stat-label" data-i18n="tasks.completed">Completed</span></div>
|
|
752
|
+
<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>
|
|
753
|
+
</div>
|
|
754
|
+
<div class="tasks-filters">
|
|
755
|
+
<button class="filter-chip active" data-task-status="" onclick="setTaskStatusFilter(this,'')" data-i18n="filter.all">All</button>
|
|
756
|
+
<button class="filter-chip" data-task-status="active" onclick="setTaskStatusFilter(this,'active')" data-i18n="tasks.status.active">Active</button>
|
|
757
|
+
<button class="filter-chip" data-task-status="completed" onclick="setTaskStatusFilter(this,'completed')" data-i18n="tasks.status.completed">Completed</button>
|
|
758
|
+
<button class="filter-chip" data-task-status="skipped" onclick="setTaskStatusFilter(this,'skipped')" data-i18n="tasks.status.skipped">Skipped</button>
|
|
759
|
+
<button class="btn btn-sm btn-ghost" onclick="loadTasks()" style="margin-left:auto" data-i18n="refresh">\u21BB Refresh</button>
|
|
760
|
+
</div>
|
|
761
|
+
</div>
|
|
762
|
+
<div class="tasks-list" id="tasksList"><div class="spinner"></div></div>
|
|
763
|
+
<div class="pagination" id="tasksPagination"></div>
|
|
764
|
+
<div class="task-detail-overlay" id="taskDetailOverlay" onclick="closeTaskDetail(event)">
|
|
765
|
+
<div class="task-detail-panel" onclick="event.stopPropagation()">
|
|
766
|
+
<div class="task-detail-header">
|
|
767
|
+
<h2 id="taskDetailTitle"></h2>
|
|
768
|
+
<button class="btn btn-icon" onclick="closeTaskDetail()" title="Close">\u2715</button>
|
|
769
|
+
</div>
|
|
770
|
+
<div class="task-detail-meta" id="taskDetailMeta"></div>
|
|
771
|
+
<div class="task-skill-section" id="taskSkillSection"></div>
|
|
772
|
+
<div class="task-detail-summary" id="taskDetailSummary"></div>
|
|
773
|
+
<div class="task-detail-chunks-title" data-i18n="tasks.chunks">Related Memories</div>
|
|
774
|
+
<div class="task-detail-chunks" id="taskDetailChunks"></div>
|
|
775
|
+
</div>
|
|
776
|
+
</div>
|
|
777
|
+
</div>
|
|
778
|
+
<div class="skills-view" id="skillsView">
|
|
779
|
+
<div class="tasks-header">
|
|
780
|
+
<div class="tasks-stats">
|
|
781
|
+
<div class="tasks-stat"><span class="tasks-stat-value" id="skillsTotalCount">-</span><span class="tasks-stat-label" data-i18n="skills.total">Total Skills</span></div>
|
|
782
|
+
<div class="tasks-stat" style="border-left:3px solid var(--green)"><span class="tasks-stat-value" id="skillsActiveCount">-</span><span class="tasks-stat-label" data-i18n="skills.active">Active</span></div>
|
|
783
|
+
<div class="tasks-stat" style="border-left:3px solid var(--amber)"><span class="tasks-stat-value" id="skillsDraftCount">-</span><span class="tasks-stat-label" data-i18n="skills.draft">Draft</span></div>
|
|
784
|
+
<div class="tasks-stat" style="border-left:3px solid var(--violet)"><span class="tasks-stat-value" id="skillsInstalledCount">-</span><span class="tasks-stat-label" data-i18n="skills.installed">Installed</span></div>
|
|
785
|
+
</div>
|
|
786
|
+
<div class="tasks-filters">
|
|
787
|
+
<button class="filter-chip active" data-skill-status="" onclick="setSkillStatusFilter(this,'')" data-i18n="filter.all">All</button>
|
|
788
|
+
<button class="filter-chip" data-skill-status="active" onclick="setSkillStatusFilter(this,'active')" data-i18n="skills.filter.active">Active</button>
|
|
789
|
+
<button class="filter-chip" data-skill-status="draft" onclick="setSkillStatusFilter(this,'draft')" data-i18n="skills.filter.draft">Draft</button>
|
|
790
|
+
<button class="filter-chip" data-skill-status="archived" onclick="setSkillStatusFilter(this,'archived')" data-i18n="skills.filter.archived">Archived</button>
|
|
791
|
+
<button class="btn btn-sm btn-ghost" onclick="loadSkills()" style="margin-left:auto" data-i18n="refresh">\u21BB Refresh</button>
|
|
792
|
+
</div>
|
|
793
|
+
</div>
|
|
794
|
+
<div class="tasks-list" id="skillsList"><div class="spinner"></div></div>
|
|
795
|
+
</div>
|
|
796
|
+
<div class="task-detail-overlay" id="skillDetailOverlay" onclick="closeSkillDetail(event)">
|
|
797
|
+
<div class="task-detail-panel" onclick="event.stopPropagation()">
|
|
798
|
+
<div class="task-detail-header">
|
|
799
|
+
<h2 id="skillDetailTitle"></h2>
|
|
800
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
801
|
+
<button class="skill-download-btn" id="skillDownloadBtn" onclick="downloadSkill()" data-i18n="skills.download">\u2B07 Download</button>
|
|
802
|
+
<button class="btn btn-icon" onclick="closeSkillDetail()" title="Close">\u2715</button>
|
|
803
|
+
</div>
|
|
804
|
+
</div>
|
|
805
|
+
<div class="task-detail-meta" id="skillDetailMeta"></div>
|
|
806
|
+
<div class="skill-detail-desc" id="skillDetailDesc"></div>
|
|
807
|
+
<div class="task-detail-chunks-title" data-i18n="skills.files">Skill Files</div>
|
|
808
|
+
<div class="skill-files-list" id="skillFilesList"></div>
|
|
809
|
+
<div class="task-detail-chunks-title" id="skillContentTitle" data-i18n="skills.content">SKILL.md Content</div>
|
|
810
|
+
<div class="task-detail-summary" id="skillDetailContent" style="max-height:50vh;overflow-y:auto"></div>
|
|
811
|
+
<div class="task-detail-chunks-title" data-i18n="skills.versions">Version History</div>
|
|
812
|
+
<div class="task-detail-chunks" id="skillVersionsList" style="gap:10px"></div>
|
|
813
|
+
<div class="task-detail-chunks-title" style="margin-top:16px" data-i18n="skills.related">Related Tasks</div>
|
|
814
|
+
<div class="task-detail-chunks" id="skillRelatedTasks" style="gap:8px"></div>
|
|
815
|
+
</div>
|
|
816
|
+
</div>
|
|
817
|
+
<div class="analytics-view" id="analyticsView">
|
|
818
|
+
<div class="metrics-toolbar">
|
|
819
|
+
<span style="font-size:12px;color:var(--text-sec);font-weight:600" data-i18n="range">Range</span>
|
|
820
|
+
<button class="range-btn" data-days="7" onclick="setMetricsDays(7)">7 <span data-i18n="range.days">days</span></button>
|
|
821
|
+
<button class="range-btn active" data-days="30" onclick="setMetricsDays(30)">30 <span data-i18n="range.days">days</span></button>
|
|
822
|
+
<button class="range-btn" data-days="90" onclick="setMetricsDays(90)">90 <span data-i18n="range.days">days</span></button>
|
|
823
|
+
<button class="btn btn-sm" onclick="loadMetrics()" style="margin-left:auto" data-i18n="refresh">\u21BB Refresh</button>
|
|
824
|
+
</div>
|
|
825
|
+
<div class="analytics-cards" id="analyticsCards">
|
|
826
|
+
<div class="analytics-card"><div class="ac-value" id="mTotal">-</div><div class="ac-label" data-i18n="analytics.total">Total Memories</div></div>
|
|
827
|
+
<div class="analytics-card green"><div class="ac-value" id="mTodayWrites">-</div><div class="ac-label" data-i18n="analytics.writes">Writes Today</div></div>
|
|
828
|
+
<div class="analytics-card"><div class="ac-value" id="mSessions">-</div><div class="ac-label" data-i18n="analytics.sessions">Sessions</div></div>
|
|
829
|
+
<div class="analytics-card amber"><div class="ac-value" id="mEmbeddings">-</div><div class="ac-label" data-i18n="analytics.embeddings">Embeddings</div></div>
|
|
830
|
+
</div>
|
|
831
|
+
<div class="analytics-section">
|
|
832
|
+
<h3><span class="icon">\u{1F4CA}</span> <span data-i18n="chart.writes">Memory Writes per Day</span></h3>
|
|
833
|
+
<div class="chart-bars" id="chartWrites"></div>
|
|
834
|
+
</div>
|
|
835
|
+
|
|
836
|
+
<div class="analytics-section" id="toolPerfSection" style="position:relative">
|
|
837
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
838
|
+
<h3 style="margin-bottom:0"><span class="icon">\u26A1</span> <span data-i18n="chart.toolperf">Tool Response Time</span> <span style="font-size:10px;color:var(--text-muted);font-weight:500;text-transform:none;letter-spacing:0;margin-left:4px">(per minute avg)</span></h3>
|
|
839
|
+
<div style="display:flex;gap:6px;align-items:center">
|
|
840
|
+
<button class="range-btn tool-range active" data-mins="60" onclick="setToolMinutes(60)">1h</button>
|
|
841
|
+
<button class="range-btn tool-range" data-mins="360" onclick="setToolMinutes(360)">6h</button>
|
|
842
|
+
<button class="range-btn tool-range" data-mins="1440" onclick="setToolMinutes(1440)">24h</button>
|
|
843
|
+
</div>
|
|
844
|
+
</div>
|
|
845
|
+
<div id="toolChart" style="width:100%;height:280px;position:relative;overflow:hidden;border-radius:12px"></div>
|
|
846
|
+
<div id="toolLegend" class="chart-legend" style="margin-top:14px;padding:0 4px"></div>
|
|
847
|
+
<div id="toolAggTable" style="margin-top:20px"></div>
|
|
848
|
+
</div>
|
|
849
|
+
|
|
850
|
+
<div class="breakdown-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:24px">
|
|
851
|
+
<div class="analytics-section">
|
|
852
|
+
<h3><span class="icon">\u{1F464}</span> <span data-i18n="breakdown.role">By Role</span></h3>
|
|
853
|
+
<div id="breakdownRole"></div>
|
|
854
|
+
</div>
|
|
855
|
+
<div class="analytics-section">
|
|
856
|
+
<h3><span class="icon">\u{1F4DD}</span> <span data-i18n="breakdown.kind">By Kind</span></h3>
|
|
857
|
+
<div id="breakdownKind"></div>
|
|
858
|
+
</div>
|
|
859
|
+
</div>
|
|
860
|
+
</div>
|
|
861
|
+
|
|
862
|
+
<!-- ─── Logs View ─── -->
|
|
863
|
+
<div class="logs-view" id="logsView">
|
|
864
|
+
<div class="logs-toolbar">
|
|
865
|
+
<div class="logs-toolbar-left">
|
|
866
|
+
<select id="logToolFilter" onchange="onLogFilterChange()" style="font-size:12px;padding:4px 8px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);min-width:120px">
|
|
867
|
+
<option value="" data-i18n="logs.allTools">All Tools</option>
|
|
868
|
+
</select>
|
|
869
|
+
<button class="btn btn-sm btn-ghost" onclick="loadLogs()" style="font-size:12px">\u21BB <span data-i18n="logs.refresh">Refresh</span></button>
|
|
870
|
+
</div>
|
|
871
|
+
<div class="logs-toolbar-right">
|
|
872
|
+
<input type="checkbox" id="logAutoRefresh" checked style="display:none">
|
|
873
|
+
</div>
|
|
874
|
+
</div>
|
|
875
|
+
<div class="logs-list" id="logsList"></div>
|
|
876
|
+
<div id="logsPagination"></div>
|
|
877
|
+
</div>
|
|
878
|
+
|
|
879
|
+
<!-- ─── Settings View ─── -->
|
|
880
|
+
<div class="settings-view" id="settingsView">
|
|
881
|
+
<div class="settings-section">
|
|
882
|
+
<h3><span class="icon">\u{1F4E1}</span> <span data-i18n="settings.embedding">Embedding Model</span></h3>
|
|
883
|
+
<div class="settings-grid">
|
|
884
|
+
<div class="settings-field">
|
|
885
|
+
<label data-i18n="settings.provider">Provider</label>
|
|
886
|
+
<select id="cfgEmbProvider">
|
|
887
|
+
<option value="openai_compatible">OpenAI Compatible</option>
|
|
888
|
+
<option value="openai">OpenAI</option>
|
|
889
|
+
<option value="gemini">Gemini</option>
|
|
890
|
+
<option value="azure_openai">Azure OpenAI</option>
|
|
891
|
+
<option value="cohere">Cohere</option>
|
|
892
|
+
<option value="mistral">Mistral</option>
|
|
893
|
+
<option value="voyage">Voyage</option>
|
|
894
|
+
<option value="local">Local</option>
|
|
895
|
+
</select>
|
|
896
|
+
</div>
|
|
897
|
+
<div class="settings-field">
|
|
898
|
+
<label data-i18n="settings.model">Model</label>
|
|
899
|
+
<input type="text" id="cfgEmbModel" placeholder="e.g. bge-m3">
|
|
900
|
+
</div>
|
|
901
|
+
<div class="settings-field full-width">
|
|
902
|
+
<label>Endpoint</label>
|
|
903
|
+
<input type="text" id="cfgEmbEndpoint" placeholder="https://...">
|
|
904
|
+
</div>
|
|
905
|
+
<div class="settings-field">
|
|
906
|
+
<label>API Key</label>
|
|
907
|
+
<input type="password" id="cfgEmbApiKey" placeholder="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022">
|
|
908
|
+
</div>
|
|
909
|
+
</div>
|
|
910
|
+
</div>
|
|
911
|
+
|
|
912
|
+
<div class="settings-section">
|
|
913
|
+
<h3><span class="icon">\u{1F9E0}</span> <span data-i18n="settings.summarizer">Summarizer Model</span></h3>
|
|
914
|
+
<div class="settings-grid">
|
|
915
|
+
<div class="settings-field">
|
|
916
|
+
<label data-i18n="settings.provider">Provider</label>
|
|
917
|
+
<select id="cfgSumProvider">
|
|
918
|
+
<option value="openai_compatible">OpenAI Compatible</option>
|
|
919
|
+
<option value="openai">OpenAI</option>
|
|
920
|
+
<option value="anthropic">Anthropic</option>
|
|
921
|
+
<option value="gemini">Gemini</option>
|
|
922
|
+
<option value="azure_openai">Azure OpenAI</option>
|
|
923
|
+
<option value="bedrock">Bedrock</option>
|
|
924
|
+
</select>
|
|
925
|
+
</div>
|
|
926
|
+
<div class="settings-field">
|
|
927
|
+
<label data-i18n="settings.model">Model</label>
|
|
928
|
+
<input type="text" id="cfgSumModel" placeholder="e.g. gpt-4o-mini">
|
|
929
|
+
</div>
|
|
930
|
+
<div class="settings-field full-width">
|
|
931
|
+
<label>Endpoint</label>
|
|
932
|
+
<input type="text" id="cfgSumEndpoint" placeholder="https://...">
|
|
933
|
+
</div>
|
|
934
|
+
<div class="settings-field">
|
|
935
|
+
<label>API Key</label>
|
|
936
|
+
<input type="password" id="cfgSumApiKey" placeholder="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022">
|
|
937
|
+
</div>
|
|
938
|
+
<div class="settings-field">
|
|
939
|
+
<label data-i18n="settings.temperature">Temperature</label>
|
|
940
|
+
<input type="number" id="cfgSumTemp" step="0.1" min="0" max="2" placeholder="0">
|
|
941
|
+
</div>
|
|
942
|
+
</div>
|
|
943
|
+
</div>
|
|
944
|
+
|
|
945
|
+
<div class="settings-section">
|
|
946
|
+
<h3><span class="icon">\u{1F527}</span> <span data-i18n="settings.skill">Skill Evolution</span></h3>
|
|
947
|
+
<div class="settings-grid">
|
|
948
|
+
<div class="settings-toggle">
|
|
949
|
+
<label class="toggle-switch"><input type="checkbox" id="cfgSkillEnabled"><span class="toggle-slider"></span></label>
|
|
950
|
+
<label data-i18n="settings.skill.enabled">Enable Skill Evolution</label>
|
|
951
|
+
</div>
|
|
952
|
+
<div class="settings-toggle">
|
|
953
|
+
<label class="toggle-switch"><input type="checkbox" id="cfgSkillAutoInstall"><span class="toggle-slider"></span></label>
|
|
954
|
+
<label data-i18n="settings.skill.autoinstall">Auto Install Skills</label>
|
|
955
|
+
</div>
|
|
956
|
+
<div class="settings-field">
|
|
957
|
+
<label data-i18n="settings.skill.confidence">Min Confidence</label>
|
|
958
|
+
<input type="number" id="cfgSkillConfidence" step="0.1" min="0" max="1" placeholder="0.7">
|
|
959
|
+
</div>
|
|
960
|
+
<div class="settings-field">
|
|
961
|
+
<label data-i18n="settings.skill.minchunks">Min Chunks</label>
|
|
962
|
+
<input type="number" id="cfgSkillMinChunks" placeholder="6">
|
|
963
|
+
</div>
|
|
964
|
+
</div>
|
|
965
|
+
<div style="margin-top:16px;padding-top:16px;border-top:1px dashed var(--border)">
|
|
966
|
+
<h3 style="font-size:12px;color:var(--text-muted);margin-bottom:4px;display:flex;align-items:center;gap:8px"><span class="icon">\u{1F680}</span> <span data-i18n="settings.skill.model">Skill Dedicated Model</span><span style="font-size:10px;font-weight:500;color:var(--amber);background:var(--amber-bg);padding:2px 8px;border-radius:10px" data-i18n="settings.optional">Optional</span></h3>
|
|
967
|
+
<div style="font-size:11px;color:var(--text-muted);margin-bottom:12px;line-height:1.5" data-i18n="settings.skill.model.hint">If not configured, the main Summarizer Model above will be used for skill generation. Configure a dedicated model here for higher quality skill output.</div>
|
|
968
|
+
<div class="settings-grid">
|
|
969
|
+
<div class="settings-field">
|
|
970
|
+
<label data-i18n="settings.provider">Provider</label>
|
|
971
|
+
<select id="cfgSkillProvider">
|
|
972
|
+
<option value="" id="cfgSkillProviderDefault">\u2014</option>
|
|
973
|
+
<option value="openai_compatible">OpenAI Compatible</option>
|
|
974
|
+
<option value="openai">OpenAI</option>
|
|
975
|
+
<option value="anthropic">Anthropic</option>
|
|
976
|
+
<option value="gemini">Gemini</option>
|
|
977
|
+
<option value="azure_openai">Azure OpenAI</option>
|
|
978
|
+
<option value="bedrock">Bedrock</option>
|
|
979
|
+
</select>
|
|
980
|
+
</div>
|
|
981
|
+
<div class="settings-field">
|
|
982
|
+
<label data-i18n="settings.model">Model</label>
|
|
983
|
+
<input type="text" id="cfgSkillModel" placeholder="e.g. claude-4.6-opus">
|
|
984
|
+
</div>
|
|
985
|
+
<div class="settings-field full-width">
|
|
986
|
+
<label>Endpoint</label>
|
|
987
|
+
<input type="text" id="cfgSkillEndpoint" placeholder="https://...">
|
|
988
|
+
</div>
|
|
989
|
+
<div class="settings-field">
|
|
990
|
+
<label>API Key</label>
|
|
991
|
+
<input type="password" id="cfgSkillApiKey" placeholder="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022">
|
|
992
|
+
</div>
|
|
993
|
+
<div class="settings-field">
|
|
994
|
+
<label data-i18n="settings.temperature">Temperature</label>
|
|
995
|
+
<input type="number" id="cfgSkillTemp" step="0.1" min="0" max="2" placeholder="0.2">
|
|
996
|
+
</div>
|
|
997
|
+
</div>
|
|
998
|
+
</div>
|
|
999
|
+
</div>
|
|
1000
|
+
|
|
1001
|
+
<div class="settings-section">
|
|
1002
|
+
<h3><span class="icon">\u{1F4BE}</span> <span data-i18n="settings.general">General</span></h3>
|
|
1003
|
+
<div class="settings-grid">
|
|
1004
|
+
<div class="settings-field">
|
|
1005
|
+
<label data-i18n="settings.viewerport">Viewer Port</label>
|
|
1006
|
+
<input type="number" id="cfgViewerPort" placeholder="18799">
|
|
1007
|
+
<div class="field-hint" data-i18n="settings.viewerport.hint">Requires restart to take effect</div>
|
|
1008
|
+
</div>
|
|
1009
|
+
</div>
|
|
1010
|
+
</div>
|
|
1011
|
+
|
|
1012
|
+
<div class="settings-actions">
|
|
1013
|
+
<span class="settings-saved" id="settingsSaved">\u2713 <span data-i18n="settings.saved">Saved</span></span>
|
|
1014
|
+
<button class="btn btn-ghost" onclick="loadConfig()" data-i18n="settings.reset">Reset</button>
|
|
1015
|
+
<button class="btn btn-primary" onclick="saveConfig()" data-i18n="settings.save">Save Settings</button>
|
|
1016
|
+
</div>
|
|
1017
|
+
<div style="font-size:11px;color:var(--text-muted);text-align:right;margin-top:4px" data-i18n="settings.restart.hint">Some changes require restarting the OpenClaw gateway to take effect.</div>
|
|
1018
|
+
</div>
|
|
1019
|
+
|
|
351
1020
|
</div>
|
|
352
1021
|
</div>
|
|
353
1022
|
|
|
354
1023
|
<!-- ─── Memory Modal ─── -->
|
|
355
1024
|
<div class="modal-overlay" id="modalOverlay">
|
|
356
1025
|
<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>
|
|
1026
|
+
<h2 id="modalTitle" data-i18n="modal.new">New Memory</h2>
|
|
1027
|
+
<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>
|
|
1028
|
+
<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>
|
|
1029
|
+
<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>
|
|
1030
|
+
<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
1031
|
<div class="modal-actions">
|
|
363
|
-
<button class="btn" onclick="closeModal()">Cancel</button>
|
|
364
|
-
<button class="btn btn-primary" id="modalSubmit" onclick="submitModal()">Create</button>
|
|
1032
|
+
<button class="btn btn-ghost" onclick="closeModal()" data-i18n="modal.cancel">Cancel</button>
|
|
1033
|
+
<button class="btn btn-primary" id="modalSubmit" onclick="submitModal()" data-i18n="modal.create">Create</button>
|
|
365
1034
|
</div>
|
|
366
1035
|
</div>
|
|
367
1036
|
</div>
|
|
@@ -370,7 +1039,434 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
370
1039
|
<div class="toast-container" id="toasts"></div>
|
|
371
1040
|
|
|
372
1041
|
<script>
|
|
373
|
-
let activeSession=null,activeRole='',editingId=null,searchTimer=null,memoryCache={},currentPage=1,totalPages=1,totalCount=0,PAGE_SIZE=30;
|
|
1042
|
+
let activeSession=null,activeRole='',editingId=null,searchTimer=null,memoryCache={},currentPage=1,totalPages=1,totalCount=0,PAGE_SIZE=40,metricsDays=30;
|
|
1043
|
+
|
|
1044
|
+
/* ─── i18n ─── */
|
|
1045
|
+
const I18N={
|
|
1046
|
+
en:{
|
|
1047
|
+
'title':'OpenClaw Memory',
|
|
1048
|
+
'subtitle':'Powered by MemOS',
|
|
1049
|
+
'setup.desc':'Set a password to protect your memories',
|
|
1050
|
+
'setup.pw':'Enter a password (4+ characters)',
|
|
1051
|
+
'setup.pw2':'Confirm password',
|
|
1052
|
+
'setup.btn':'Set Password & Enter',
|
|
1053
|
+
'setup.err.short':'Password must be at least 4 characters',
|
|
1054
|
+
'setup.err.mismatch':'Passwords do not match',
|
|
1055
|
+
'setup.err.fail':'Setup failed',
|
|
1056
|
+
'login.desc':'Enter your password to access memories',
|
|
1057
|
+
'login.pw':'Password',
|
|
1058
|
+
'login.btn':'Unlock',
|
|
1059
|
+
'login.err':'Incorrect password',
|
|
1060
|
+
'login.forgot':'Forgot password?',
|
|
1061
|
+
'reset.step1.title':'Open Terminal',
|
|
1062
|
+
'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):',
|
|
1063
|
+
'reset.step2.title':'Find the token',
|
|
1064
|
+
'reset.step2.desc.pre':'In the output, find ',
|
|
1065
|
+
'reset.step2.desc.post':' (plain line or inside JSON). Copy the 32-character hex string after the colon.',
|
|
1066
|
+
'reset.step3.title':'Paste & reset',
|
|
1067
|
+
'reset.step3.desc':'Paste the token below and set your new password.',
|
|
1068
|
+
'reset.token':'Paste reset token here',
|
|
1069
|
+
'reset.newpw':'New password (4+ characters)',
|
|
1070
|
+
'reset.newpw2':'Confirm new password',
|
|
1071
|
+
'reset.btn':'Reset Password',
|
|
1072
|
+
'reset.err.token':'Please enter the reset token',
|
|
1073
|
+
'reset.err.short':'Password must be at least 4 characters',
|
|
1074
|
+
'reset.err.mismatch':'Passwords do not match',
|
|
1075
|
+
'reset.err.fail':'Reset failed',
|
|
1076
|
+
'reset.back':'\\u2190 Back to login',
|
|
1077
|
+
'copy.hint':'Click to copy',
|
|
1078
|
+
'copy.done':'Copied!',
|
|
1079
|
+
'tab.memories':'\\u{1F4DA} Memories',
|
|
1080
|
+
'tab.tasks':'\\u{1F4CB} Tasks',
|
|
1081
|
+
'tab.skills':'\\u{1F9E0} Skills',
|
|
1082
|
+
'tab.analytics':'\\u{1F4CA} Analytics',
|
|
1083
|
+
'skills.total':'Total Skills',
|
|
1084
|
+
'skills.active':'Active',
|
|
1085
|
+
'skills.installed':'Installed',
|
|
1086
|
+
'tasks.total':'Total Tasks',
|
|
1087
|
+
'tasks.active':'Active',
|
|
1088
|
+
'tasks.completed':'Completed',
|
|
1089
|
+
'tasks.status.active':'Active',
|
|
1090
|
+
'tasks.status.completed':'Completed',
|
|
1091
|
+
'tasks.status.skipped':'Skipped',
|
|
1092
|
+
'tasks.empty':'No tasks yet. Tasks are automatically created as you converse.',
|
|
1093
|
+
'tasks.loading':'Loading...',
|
|
1094
|
+
'tasks.untitled':'Untitled Task',
|
|
1095
|
+
'tasks.chunks':'Related Memories',
|
|
1096
|
+
'tasks.nochunks':'No memories in this task yet.',
|
|
1097
|
+
'tasks.skipped.default':'This conversation was too brief to generate a summary. It will not appear in search results.',
|
|
1098
|
+
'refresh':'\\u21BB Refresh',
|
|
1099
|
+
'logout':'Logout',
|
|
1100
|
+
'stat.memories':'Memories',
|
|
1101
|
+
'stat.sessions':'Sessions',
|
|
1102
|
+
'stat.embeddings':'Embeddings',
|
|
1103
|
+
'stat.days':'Days',
|
|
1104
|
+
'stat.active':'active',
|
|
1105
|
+
'stat.deduped':'deduped',
|
|
1106
|
+
'sidebar.sessions':'Sessions',
|
|
1107
|
+
'sidebar.allsessions':'All Sessions',
|
|
1108
|
+
'sidebar.clear':'\\u{1F5D1} Clear All Data',
|
|
1109
|
+
'search.placeholder':'Search memories (supports semantic search)...',
|
|
1110
|
+
'search.meta.total':' memories total',
|
|
1111
|
+
'search.meta.semantic':' semantic',
|
|
1112
|
+
'search.meta.text':' text',
|
|
1113
|
+
'search.meta.results':' results',
|
|
1114
|
+
'filter.all':'All',
|
|
1115
|
+
'filter.allkinds':'All kinds',
|
|
1116
|
+
'filter.paragraph':'Paragraph',
|
|
1117
|
+
'filter.code':'Code',
|
|
1118
|
+
'filter.dialog':'Dialog',
|
|
1119
|
+
'filter.list':'List',
|
|
1120
|
+
'filter.error':'Error',
|
|
1121
|
+
'filter.command':'Command',
|
|
1122
|
+
'filter.newest':'Newest first',
|
|
1123
|
+
'filter.oldest':'Oldest first',
|
|
1124
|
+
'filter.from':'From',
|
|
1125
|
+
'filter.to':'To',
|
|
1126
|
+
'filter.clear':'Clear',
|
|
1127
|
+
'empty.text':'No memories found',
|
|
1128
|
+
'card.expand':'Expand',
|
|
1129
|
+
'card.edit':'Edit',
|
|
1130
|
+
'card.delete':'Delete',
|
|
1131
|
+
'card.evolved':'Evolved',
|
|
1132
|
+
'card.times':'times',
|
|
1133
|
+
'card.updated':'updated',
|
|
1134
|
+
'card.evolveHistory':'Evolution History',
|
|
1135
|
+
'card.oldSummary':'Old',
|
|
1136
|
+
'card.dedupDuplicate':'Duplicate',
|
|
1137
|
+
'card.dedupMerged':'Merged',
|
|
1138
|
+
'card.dedupTarget':'Target: ',
|
|
1139
|
+
'card.dedupReason':'Reason: ',
|
|
1140
|
+
'card.newSummary':'New',
|
|
1141
|
+
'pagination.total':' total',
|
|
1142
|
+
'range':'Range',
|
|
1143
|
+
'range.days':'days',
|
|
1144
|
+
'analytics.total':'Total Memories',
|
|
1145
|
+
'analytics.writes':'Writes Today',
|
|
1146
|
+
'analytics.calls':'Viewer Calls Today',
|
|
1147
|
+
'analytics.sessions':'Sessions',
|
|
1148
|
+
'analytics.embeddings':'Embeddings',
|
|
1149
|
+
'chart.writes':'Memory Writes per Day',
|
|
1150
|
+
'chart.calls':'Viewer API Calls per Day (List / Search)',
|
|
1151
|
+
'chart.nodata':'No data in this range',
|
|
1152
|
+
'chart.nocalls':'No viewer calls in this range',
|
|
1153
|
+
'chart.toolperf':'Tool Response Time',
|
|
1154
|
+
'chart.list':'List',
|
|
1155
|
+
'chart.search':'Search',
|
|
1156
|
+
'breakdown.role':'By Role',
|
|
1157
|
+
'breakdown.kind':'By Kind',
|
|
1158
|
+
'modal.new':'New Memory',
|
|
1159
|
+
'modal.edit':'Edit Memory',
|
|
1160
|
+
'modal.role':'Role',
|
|
1161
|
+
'modal.content':'Content',
|
|
1162
|
+
'modal.content.ph':'Memory content...',
|
|
1163
|
+
'modal.summary':'Summary',
|
|
1164
|
+
'modal.summary.ph':'Brief summary (optional)',
|
|
1165
|
+
'modal.kind':'Kind',
|
|
1166
|
+
'modal.cancel':'Cancel',
|
|
1167
|
+
'modal.create':'Create',
|
|
1168
|
+
'modal.save':'Save',
|
|
1169
|
+
'modal.err.empty':'Please enter content',
|
|
1170
|
+
'toast.created':'Memory created',
|
|
1171
|
+
'toast.updated':'Memory updated',
|
|
1172
|
+
'toast.deleted':'Memory deleted',
|
|
1173
|
+
'toast.opfail':'Operation failed',
|
|
1174
|
+
'toast.delfail':'Delete failed',
|
|
1175
|
+
'toast.cleared':'All memories cleared',
|
|
1176
|
+
'toast.clearfail':'Clear failed',
|
|
1177
|
+
'toast.notfound':'Memory not found in cache',
|
|
1178
|
+
'confirm.delete':'Delete this memory?',
|
|
1179
|
+
'confirm.clearall':'Delete ALL memories? This cannot be undone.',
|
|
1180
|
+
'confirm.clearall2':'Are you absolutely sure?',
|
|
1181
|
+
'embed.on':'Embedding: ',
|
|
1182
|
+
'embed.off':'No embedding model',
|
|
1183
|
+
'lang.switch':'中',
|
|
1184
|
+
'tab.logs':'\u{1F4DD} Logs',
|
|
1185
|
+
'logs.allTools':'All Tools',
|
|
1186
|
+
'logs.refresh':'Refresh',
|
|
1187
|
+
'logs.autoRefresh':'Auto-refresh',
|
|
1188
|
+
'logs.input':'INPUT',
|
|
1189
|
+
'logs.output':'OUTPUT',
|
|
1190
|
+
'logs.empty':'No logs yet. Logs will appear here when tools are called.',
|
|
1191
|
+
'logs.ago':'ago',
|
|
1192
|
+
'tab.settings':'\u2699 Settings',
|
|
1193
|
+
'settings.embedding':'Embedding Model',
|
|
1194
|
+
'settings.summarizer':'Summarizer Model',
|
|
1195
|
+
'settings.skill':'Skill Evolution',
|
|
1196
|
+
'settings.general':'General',
|
|
1197
|
+
'settings.provider':'Provider',
|
|
1198
|
+
'settings.model':'Model',
|
|
1199
|
+
'settings.temperature':'Temperature',
|
|
1200
|
+
'settings.skill.enabled':'Enable Skill Evolution',
|
|
1201
|
+
'settings.skill.autoinstall':'Auto Install Skills',
|
|
1202
|
+
'settings.skill.confidence':'Min Confidence',
|
|
1203
|
+
'settings.skill.minchunks':'Min Chunks',
|
|
1204
|
+
'settings.skill.model':'Skill Dedicated Model',
|
|
1205
|
+
'settings.skill.model.hint':'If not configured, the main Summarizer Model above will be used for skill generation. Configure a dedicated model here for higher quality skill output.',
|
|
1206
|
+
'settings.optional':'Optional',
|
|
1207
|
+
'settings.skill.usemain':'Use Main Summarizer',
|
|
1208
|
+
'settings.viewerport':'Viewer Port',
|
|
1209
|
+
'settings.viewerport.hint':'Requires restart to take effect',
|
|
1210
|
+
'settings.save':'Save Settings',
|
|
1211
|
+
'settings.reset':'Reset',
|
|
1212
|
+
'settings.saved':'Saved',
|
|
1213
|
+
'settings.restart.hint':'Some changes require restarting the OpenClaw gateway to take effect.',
|
|
1214
|
+
'settings.save.fail':'Failed to save settings',
|
|
1215
|
+
'skills.draft':'Draft',
|
|
1216
|
+
'skills.filter.active':'Active',
|
|
1217
|
+
'skills.filter.draft':'Draft',
|
|
1218
|
+
'skills.filter.archived':'Archived',
|
|
1219
|
+
'skills.files':'Skill Files',
|
|
1220
|
+
'skills.content':'SKILL.md Content',
|
|
1221
|
+
'skills.versions':'Version History',
|
|
1222
|
+
'skills.related':'Related Tasks',
|
|
1223
|
+
'skills.download':'\u2B07 Download',
|
|
1224
|
+
'skills.installed.badge':'Installed',
|
|
1225
|
+
'skills.empty':'No skills yet. Skills are automatically generated from completed tasks that contain reusable experience.',
|
|
1226
|
+
'skills.loading':'Loading...',
|
|
1227
|
+
'skills.error':'Error loading skill',
|
|
1228
|
+
'skills.error.detail':'Failed to load skill: ',
|
|
1229
|
+
'skills.nofiles':'No files found',
|
|
1230
|
+
'skills.noversions':'No versions recorded',
|
|
1231
|
+
'skills.norelated':'No related tasks',
|
|
1232
|
+
'skills.nocontent':'No content available',
|
|
1233
|
+
'skills.nochangelog':'No changelog',
|
|
1234
|
+
'skills.status.active':'Active',
|
|
1235
|
+
'skills.status.draft':'Draft',
|
|
1236
|
+
'skills.status.archived':'Archived',
|
|
1237
|
+
'skills.updated':'Updated: ',
|
|
1238
|
+
'skills.task.prefix':'Task: ',
|
|
1239
|
+
'tasks.chunks.label':'chunks',
|
|
1240
|
+
'tasks.taskid':'Task ID: ',
|
|
1241
|
+
'tasks.role.user':'You',
|
|
1242
|
+
'tasks.role.assistant':'Assistant',
|
|
1243
|
+
'tasks.error':'Error',
|
|
1244
|
+
'tasks.error.detail':'Failed to load task details',
|
|
1245
|
+
'tasks.untitled.related':'Untitled'
|
|
1246
|
+
},
|
|
1247
|
+
zh:{
|
|
1248
|
+
'title':'OpenClaw 记忆',
|
|
1249
|
+
'subtitle':'由 MemOS 驱动',
|
|
1250
|
+
'setup.desc':'设置密码以保护你的记忆数据',
|
|
1251
|
+
'setup.pw':'输入密码(至少4位)',
|
|
1252
|
+
'setup.pw2':'确认密码',
|
|
1253
|
+
'setup.btn':'设置密码并进入',
|
|
1254
|
+
'setup.err.short':'密码至少需要4个字符',
|
|
1255
|
+
'setup.err.mismatch':'两次密码不一致',
|
|
1256
|
+
'setup.err.fail':'设置失败',
|
|
1257
|
+
'login.desc':'输入密码以访问记忆',
|
|
1258
|
+
'login.pw':'密码',
|
|
1259
|
+
'login.btn':'解锁',
|
|
1260
|
+
'login.err':'密码错误',
|
|
1261
|
+
'login.forgot':'忘记密码?',
|
|
1262
|
+
'reset.step1.title':'打开终端',
|
|
1263
|
+
'reset.step1.desc':'运行以下命令获取重置令牌:',
|
|
1264
|
+
'reset.step2.title':'找到令牌',
|
|
1265
|
+
'reset.step2.desc.pre':'在输出中找到 ',
|
|
1266
|
+
'reset.step2.desc.post':'(纯文本行或 JSON 内)。复制冒号后的32位十六进制字符串。',
|
|
1267
|
+
'reset.step3.title':'粘贴并重置',
|
|
1268
|
+
'reset.step3.desc':'将令牌粘贴到下方并设置新密码。',
|
|
1269
|
+
'reset.token':'在此粘贴重置令牌',
|
|
1270
|
+
'reset.newpw':'新密码(至少4位)',
|
|
1271
|
+
'reset.newpw2':'确认新密码',
|
|
1272
|
+
'reset.btn':'重置密码',
|
|
1273
|
+
'reset.err.token':'请输入重置令牌',
|
|
1274
|
+
'reset.err.short':'密码至少需要4个字符',
|
|
1275
|
+
'reset.err.mismatch':'两次密码不一致',
|
|
1276
|
+
'reset.err.fail':'重置失败',
|
|
1277
|
+
'reset.back':'\\u2190 返回登录',
|
|
1278
|
+
'copy.hint':'点击复制',
|
|
1279
|
+
'copy.done':'已复制!',
|
|
1280
|
+
'tab.memories':'\\u{1F4DA} 记忆',
|
|
1281
|
+
'tab.tasks':'\\u{1F4CB} 任务',
|
|
1282
|
+
'tab.skills':'\\u{1F9E0} 技能',
|
|
1283
|
+
'tab.analytics':'\\u{1F4CA} 分析',
|
|
1284
|
+
'skills.total':'技能总数',
|
|
1285
|
+
'skills.active':'生效中',
|
|
1286
|
+
'skills.installed':'已安装',
|
|
1287
|
+
'tasks.total':'任务总数',
|
|
1288
|
+
'tasks.active':'进行中',
|
|
1289
|
+
'tasks.completed':'已完成',
|
|
1290
|
+
'tasks.status.active':'进行中',
|
|
1291
|
+
'tasks.status.completed':'已完成',
|
|
1292
|
+
'tasks.status.skipped':'已跳过',
|
|
1293
|
+
'tasks.empty':'暂无任务。任务会随着对话自动创建。',
|
|
1294
|
+
'tasks.loading':'加载中...',
|
|
1295
|
+
'tasks.untitled':'未命名任务',
|
|
1296
|
+
'tasks.chunks':'关联记忆',
|
|
1297
|
+
'tasks.nochunks':'此任务暂无关联记忆。',
|
|
1298
|
+
'tasks.skipped.default':'对话内容过少,未生成摘要。该任务不会出现在检索结果中。',
|
|
1299
|
+
'refresh':'\\u21BB 刷新',
|
|
1300
|
+
'logout':'退出',
|
|
1301
|
+
'stat.memories':'记忆',
|
|
1302
|
+
'stat.sessions':'会话',
|
|
1303
|
+
'stat.embeddings':'嵌入',
|
|
1304
|
+
'stat.days':'天数',
|
|
1305
|
+
'stat.active':'活跃',
|
|
1306
|
+
'stat.deduped':'已去重',
|
|
1307
|
+
'sidebar.sessions':'会话列表',
|
|
1308
|
+
'sidebar.allsessions':'全部会话',
|
|
1309
|
+
'sidebar.clear':'\\u{1F5D1} 清除所有数据',
|
|
1310
|
+
'search.placeholder':'搜索记忆(支持语义搜索)...',
|
|
1311
|
+
'search.meta.total':' 条记忆',
|
|
1312
|
+
'search.meta.semantic':' 语义',
|
|
1313
|
+
'search.meta.text':' 文本',
|
|
1314
|
+
'search.meta.results':' 条结果',
|
|
1315
|
+
'filter.all':'全部',
|
|
1316
|
+
'filter.allkinds':'所有类型',
|
|
1317
|
+
'filter.paragraph':'段落',
|
|
1318
|
+
'filter.code':'代码',
|
|
1319
|
+
'filter.dialog':'对话',
|
|
1320
|
+
'filter.list':'列表',
|
|
1321
|
+
'filter.error':'错误',
|
|
1322
|
+
'filter.command':'命令',
|
|
1323
|
+
'filter.newest':'最新优先',
|
|
1324
|
+
'filter.oldest':'最早优先',
|
|
1325
|
+
'filter.from':'起始',
|
|
1326
|
+
'filter.to':'截止',
|
|
1327
|
+
'filter.clear':'清除',
|
|
1328
|
+
'empty.text':'暂无记忆',
|
|
1329
|
+
'card.expand':'展开',
|
|
1330
|
+
'card.edit':'编辑',
|
|
1331
|
+
'card.delete':'删除',
|
|
1332
|
+
'card.evolved':'已演化',
|
|
1333
|
+
'card.times':'次',
|
|
1334
|
+
'card.updated':'更新于',
|
|
1335
|
+
'card.evolveHistory':'演化记录',
|
|
1336
|
+
'card.oldSummary':'旧摘要',
|
|
1337
|
+
'card.dedupDuplicate':'重复',
|
|
1338
|
+
'card.dedupMerged':'已合并',
|
|
1339
|
+
'card.dedupTarget':'关联: ',
|
|
1340
|
+
'card.dedupReason':'原因: ',
|
|
1341
|
+
'card.newSummary':'新摘要',
|
|
1342
|
+
'pagination.total':' 条',
|
|
1343
|
+
'range':'范围',
|
|
1344
|
+
'range.days':'天',
|
|
1345
|
+
'analytics.total':'总记忆数',
|
|
1346
|
+
'analytics.writes':'今日写入',
|
|
1347
|
+
'analytics.calls':'今日查看器调用',
|
|
1348
|
+
'analytics.sessions':'会话数',
|
|
1349
|
+
'analytics.embeddings':'嵌入数',
|
|
1350
|
+
'chart.writes':'每日记忆写入',
|
|
1351
|
+
'chart.calls':'每日查看器 API 调用(列表 / 搜索)',
|
|
1352
|
+
'chart.nodata':'此范围内暂无数据',
|
|
1353
|
+
'chart.nocalls':'此范围内暂无查看器调用',
|
|
1354
|
+
'chart.toolperf':'工具响应耗时',
|
|
1355
|
+
'chart.list':'列表',
|
|
1356
|
+
'chart.search':'搜索',
|
|
1357
|
+
'breakdown.role':'按角色',
|
|
1358
|
+
'breakdown.kind':'按类型',
|
|
1359
|
+
'modal.new':'新建记忆',
|
|
1360
|
+
'modal.edit':'编辑记忆',
|
|
1361
|
+
'modal.role':'角色',
|
|
1362
|
+
'modal.content':'内容',
|
|
1363
|
+
'modal.content.ph':'记忆内容...',
|
|
1364
|
+
'modal.summary':'摘要',
|
|
1365
|
+
'modal.summary.ph':'简要摘要(可选)',
|
|
1366
|
+
'modal.kind':'类型',
|
|
1367
|
+
'modal.cancel':'取消',
|
|
1368
|
+
'modal.create':'创建',
|
|
1369
|
+
'modal.save':'保存',
|
|
1370
|
+
'modal.err.empty':'请输入内容',
|
|
1371
|
+
'toast.created':'记忆已创建',
|
|
1372
|
+
'toast.updated':'记忆已更新',
|
|
1373
|
+
'toast.deleted':'记忆已删除',
|
|
1374
|
+
'toast.opfail':'操作失败',
|
|
1375
|
+
'toast.delfail':'删除失败',
|
|
1376
|
+
'toast.cleared':'所有记忆已清除',
|
|
1377
|
+
'toast.clearfail':'清除失败',
|
|
1378
|
+
'toast.notfound':'缓存中未找到此记忆',
|
|
1379
|
+
'confirm.delete':'确定要删除这条记忆吗?',
|
|
1380
|
+
'confirm.clearall':'确定要删除所有记忆?此操作不可撤销。',
|
|
1381
|
+
'confirm.clearall2':'你真的确定吗?',
|
|
1382
|
+
'embed.on':'嵌入模型:',
|
|
1383
|
+
'embed.off':'无嵌入模型',
|
|
1384
|
+
'lang.switch':'EN',
|
|
1385
|
+
'tab.logs':'\u{1F4DD} 日志',
|
|
1386
|
+
'logs.allTools':'全部工具',
|
|
1387
|
+
'logs.refresh':'刷新',
|
|
1388
|
+
'logs.autoRefresh':'自动刷新',
|
|
1389
|
+
'logs.input':'输入',
|
|
1390
|
+
'logs.output':'输出',
|
|
1391
|
+
'logs.empty':'暂无日志。当工具被调用时日志会显示在这里。',
|
|
1392
|
+
'logs.ago':'前',
|
|
1393
|
+
'tab.settings':'\u2699 设置',
|
|
1394
|
+
'settings.embedding':'嵌入模型',
|
|
1395
|
+
'settings.summarizer':'摘要模型',
|
|
1396
|
+
'settings.skill':'技能进化',
|
|
1397
|
+
'settings.general':'通用设置',
|
|
1398
|
+
'settings.provider':'服务商',
|
|
1399
|
+
'settings.model':'模型',
|
|
1400
|
+
'settings.temperature':'温度',
|
|
1401
|
+
'settings.skill.enabled':'启用技能进化',
|
|
1402
|
+
'settings.skill.autoinstall':'自动安装技能',
|
|
1403
|
+
'settings.skill.confidence':'最低置信度',
|
|
1404
|
+
'settings.skill.minchunks':'最少记忆片段',
|
|
1405
|
+
'settings.skill.model':'技能专用模型',
|
|
1406
|
+
'settings.skill.model.hint':'不配置时默认使用上方的摘要模型进行技能生成。如需更高质量的技能输出,可在此单独配置一个更强的模型。',
|
|
1407
|
+
'settings.optional':'可选',
|
|
1408
|
+
'settings.skill.usemain':'使用主摘要模型',
|
|
1409
|
+
'settings.viewerport':'Viewer 端口',
|
|
1410
|
+
'settings.viewerport.hint':'修改后需重启网关生效',
|
|
1411
|
+
'settings.save':'保存设置',
|
|
1412
|
+
'settings.reset':'重置',
|
|
1413
|
+
'settings.saved':'已保存',
|
|
1414
|
+
'settings.restart.hint':'部分设置修改后需要重启 OpenClaw 网关才能生效。',
|
|
1415
|
+
'settings.save.fail':'保存设置失败',
|
|
1416
|
+
'skills.draft':'草稿',
|
|
1417
|
+
'skills.filter.active':'生效中',
|
|
1418
|
+
'skills.filter.draft':'草稿',
|
|
1419
|
+
'skills.filter.archived':'已归档',
|
|
1420
|
+
'skills.files':'技能文件',
|
|
1421
|
+
'skills.content':'SKILL.md 内容',
|
|
1422
|
+
'skills.versions':'版本历史',
|
|
1423
|
+
'skills.related':'关联任务',
|
|
1424
|
+
'skills.download':'\u2B07 下载',
|
|
1425
|
+
'skills.installed.badge':'已安装',
|
|
1426
|
+
'skills.empty':'暂无技能。技能会从已完成的、包含可复用经验的任务中自动生成。',
|
|
1427
|
+
'skills.loading':'加载中...',
|
|
1428
|
+
'skills.error':'加载技能失败',
|
|
1429
|
+
'skills.error.detail':'加载技能失败:',
|
|
1430
|
+
'skills.nofiles':'暂无文件',
|
|
1431
|
+
'skills.noversions':'暂无版本记录',
|
|
1432
|
+
'skills.norelated':'暂无关联任务',
|
|
1433
|
+
'skills.nocontent':'暂无内容',
|
|
1434
|
+
'skills.nochangelog':'暂无变更记录',
|
|
1435
|
+
'skills.status.active':'生效中',
|
|
1436
|
+
'skills.status.draft':'草稿',
|
|
1437
|
+
'skills.status.archived':'已归档',
|
|
1438
|
+
'skills.updated':'更新于:',
|
|
1439
|
+
'skills.task.prefix':'任务:',
|
|
1440
|
+
'tasks.chunks.label':'条记忆',
|
|
1441
|
+
'tasks.taskid':'任务 ID:',
|
|
1442
|
+
'tasks.role.user':'你',
|
|
1443
|
+
'tasks.role.assistant':'助手',
|
|
1444
|
+
'tasks.error':'出错了',
|
|
1445
|
+
'tasks.error.detail':'加载任务详情失败',
|
|
1446
|
+
'tasks.untitled.related':'未命名'
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
const LANG_KEY='memos-viewer-lang';
|
|
1450
|
+
let curLang=localStorage.getItem(LANG_KEY)||(navigator.language.startsWith('zh')?'zh':'en');
|
|
1451
|
+
function t(key){return (I18N[curLang]||I18N.en)[key]||key;}
|
|
1452
|
+
function setLang(lang){curLang=lang;localStorage.setItem(LANG_KEY,lang);applyI18n();}
|
|
1453
|
+
function toggleLang(){setLang(curLang==='zh'?'en':'zh');}
|
|
1454
|
+
|
|
1455
|
+
function applyI18n(){
|
|
1456
|
+
document.querySelectorAll('[data-i18n]').forEach(el=>{
|
|
1457
|
+
const key=el.getAttribute('data-i18n');
|
|
1458
|
+
if(key) el.textContent=t(key);
|
|
1459
|
+
});
|
|
1460
|
+
document.querySelectorAll('[data-i18n-ph]').forEach(el=>{
|
|
1461
|
+
const key=el.getAttribute('data-i18n-ph');
|
|
1462
|
+
if(key) el.placeholder=t(key);
|
|
1463
|
+
});
|
|
1464
|
+
const step2=document.getElementById('resetStep2Desc');
|
|
1465
|
+
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');
|
|
1466
|
+
document.title=t('title')+' - MemOS';
|
|
1467
|
+
if(typeof loadStats==='function' && document.getElementById('app').style.display==='flex'){loadStats();}
|
|
1468
|
+
if(document.querySelector('.analytics-view.show') && typeof loadMetrics==='function'){loadMetrics();}
|
|
1469
|
+
}
|
|
374
1470
|
|
|
375
1471
|
/* ─── Auth flow ─── */
|
|
376
1472
|
async function checkAuth(){
|
|
@@ -392,12 +1488,12 @@ async function doSetup(){
|
|
|
392
1488
|
const pw=document.getElementById('setupPw').value;
|
|
393
1489
|
const pw2=document.getElementById('setupPw2').value;
|
|
394
1490
|
const err=document.getElementById('setupErr');
|
|
395
|
-
if(pw.length<4){err.textContent='
|
|
396
|
-
if(pw!==pw2){err.textContent='
|
|
1491
|
+
if(pw.length<4){err.textContent=t('setup.err.short');return}
|
|
1492
|
+
if(pw!==pw2){err.textContent=t('setup.err.mismatch');return}
|
|
397
1493
|
const r=await fetch('/api/auth/setup',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({password:pw})});
|
|
398
1494
|
const d=await r.json();
|
|
399
1495
|
if(d.ok){document.getElementById('setupScreen').style.display='none';enterApp();}
|
|
400
|
-
else{err.textContent=d.error||'
|
|
1496
|
+
else{err.textContent=d.error||t('setup.err.fail')}
|
|
401
1497
|
}
|
|
402
1498
|
|
|
403
1499
|
async function doLogin(){
|
|
@@ -406,7 +1502,7 @@ async function doLogin(){
|
|
|
406
1502
|
const r=await fetch('/api/auth/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({password:pw})});
|
|
407
1503
|
const d=await r.json();
|
|
408
1504
|
if(d.ok){document.getElementById('loginScreen').style.display='none';enterApp();}
|
|
409
|
-
else{err.textContent='
|
|
1505
|
+
else{err.textContent=t('login.err');document.getElementById('loginPw').value='';document.getElementById('loginPw').focus();}
|
|
410
1506
|
}
|
|
411
1507
|
|
|
412
1508
|
async function doLogout(){
|
|
@@ -430,8 +1526,8 @@ function copyCmd(el){
|
|
|
430
1526
|
const code=el.querySelector('code').textContent;
|
|
431
1527
|
navigator.clipboard.writeText(code).then(()=>{
|
|
432
1528
|
el.classList.add('copied');
|
|
433
|
-
el.querySelector('.copy-hint').textContent='
|
|
434
|
-
setTimeout(()=>{el.classList.remove('copied');el.querySelector('.copy-hint').textContent='
|
|
1529
|
+
el.querySelector('.copy-hint').textContent=t('copy.done');
|
|
1530
|
+
setTimeout(()=>{el.classList.remove('copied');el.querySelector('.copy-hint').textContent=t('copy.hint')},2000);
|
|
435
1531
|
});
|
|
436
1532
|
}
|
|
437
1533
|
|
|
@@ -440,13 +1536,13 @@ async function doReset(){
|
|
|
440
1536
|
const pw=document.getElementById('resetNewPw').value;
|
|
441
1537
|
const pw2=document.getElementById('resetNewPw2').value;
|
|
442
1538
|
const err=document.getElementById('resetErr');
|
|
443
|
-
if(!token){err.textContent='
|
|
444
|
-
if(pw.length<4){err.textContent='
|
|
445
|
-
if(pw!==pw2){err.textContent='
|
|
1539
|
+
if(!token){err.textContent=t('reset.err.token');return}
|
|
1540
|
+
if(pw.length<4){err.textContent=t('reset.err.short');return}
|
|
1541
|
+
if(pw!==pw2){err.textContent=t('reset.err.mismatch');return}
|
|
446
1542
|
const r=await fetch('/api/auth/reset',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token,newPassword:pw})});
|
|
447
1543
|
const d=await r.json();
|
|
448
1544
|
if(d.ok){document.getElementById('loginScreen').style.display='none';enterApp();}
|
|
449
|
-
else{err.textContent=d.error||'
|
|
1545
|
+
else{err.textContent=d.error||t('reset.err.fail')}
|
|
450
1546
|
}
|
|
451
1547
|
|
|
452
1548
|
function enterApp(){
|
|
@@ -454,6 +1550,1003 @@ function enterApp(){
|
|
|
454
1550
|
loadAll();
|
|
455
1551
|
}
|
|
456
1552
|
|
|
1553
|
+
function switchView(view){
|
|
1554
|
+
document.querySelectorAll('.nav-tabs .tab').forEach(t=>t.classList.toggle('active',t.dataset.view===view));
|
|
1555
|
+
const feedWrap=document.getElementById('feedWrap');
|
|
1556
|
+
const analyticsView=document.getElementById('analyticsView');
|
|
1557
|
+
const tasksView=document.getElementById('tasksView');
|
|
1558
|
+
const skillsView=document.getElementById('skillsView');
|
|
1559
|
+
const logsView=document.getElementById('logsView');
|
|
1560
|
+
const settingsView=document.getElementById('settingsView');
|
|
1561
|
+
feedWrap.classList.add('hide');
|
|
1562
|
+
analyticsView.classList.remove('show');
|
|
1563
|
+
tasksView.classList.remove('show');
|
|
1564
|
+
skillsView.classList.remove('show');
|
|
1565
|
+
logsView.classList.remove('show');
|
|
1566
|
+
settingsView.classList.remove('show');
|
|
1567
|
+
if(view==='analytics'){
|
|
1568
|
+
analyticsView.classList.add('show');
|
|
1569
|
+
loadMetrics();
|
|
1570
|
+
} else if(view==='tasks'){
|
|
1571
|
+
tasksView.classList.add('show');
|
|
1572
|
+
loadTasks();
|
|
1573
|
+
} else if(view==='skills'){
|
|
1574
|
+
skillsView.classList.add('show');
|
|
1575
|
+
loadSkills();
|
|
1576
|
+
} else if(view==='logs'){
|
|
1577
|
+
logsView.classList.add('show');
|
|
1578
|
+
loadLogs();
|
|
1579
|
+
} else if(view==='settings'){
|
|
1580
|
+
settingsView.classList.add('show');
|
|
1581
|
+
loadConfig();
|
|
1582
|
+
} else {
|
|
1583
|
+
feedWrap.classList.remove('hide');
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// ─── Logs ───
|
|
1588
|
+
let logAutoTimer=null;
|
|
1589
|
+
let logPage=1;
|
|
1590
|
+
const LOG_PAGE_SIZE=20;
|
|
1591
|
+
async function loadLogs(page){
|
|
1592
|
+
if(typeof page==='number') logPage=page;
|
|
1593
|
+
try{
|
|
1594
|
+
const toolFilter=document.getElementById('logToolFilter').value;
|
|
1595
|
+
const offset=(logPage-1)*LOG_PAGE_SIZE;
|
|
1596
|
+
const url='/api/logs?limit='+LOG_PAGE_SIZE+'&offset='+offset+(toolFilter?'&tool='+encodeURIComponent(toolFilter):'');
|
|
1597
|
+
const [logsRes,toolsRes]=await Promise.all([fetch(url),fetch('/api/log-tools')]);
|
|
1598
|
+
if(!logsRes.ok) return;
|
|
1599
|
+
const logsData=await logsRes.json();
|
|
1600
|
+
const toolsData=await toolsRes.json();
|
|
1601
|
+
renderLogToolFilter(toolsData.tools||[],toolFilter);
|
|
1602
|
+
renderLogs(logsData.logs||[]);
|
|
1603
|
+
renderLogPagination(logsData.page||1,logsData.totalPages||1,logsData.total||0);
|
|
1604
|
+
startLogAutoRefresh();
|
|
1605
|
+
}catch(e){console.error('loadLogs',e)}
|
|
1606
|
+
}
|
|
1607
|
+
function onLogFilterChange(){logPage=1;loadLogs(1);}
|
|
1608
|
+
function renderLogPagination(page,totalPages,total){
|
|
1609
|
+
const el=document.getElementById('logsPagination');
|
|
1610
|
+
if(!el||totalPages<=1){if(el)el.innerHTML='';return;}
|
|
1611
|
+
const pages=[];
|
|
1612
|
+
const range=2;
|
|
1613
|
+
for(let i=1;i<=totalPages;i++){
|
|
1614
|
+
if(i===1||i===totalPages||Math.abs(i-page)<=range){
|
|
1615
|
+
pages.push(i);
|
|
1616
|
+
}else if(pages[pages.length-1]!=='...'){
|
|
1617
|
+
pages.push('...');
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
let html='<div class="logs-pagination">';
|
|
1621
|
+
html+='<button class="btn btn-sm btn-ghost" '+(page<=1?'disabled':'')+' onclick="loadLogs('+(page-1)+')">\u2039</button>';
|
|
1622
|
+
pages.forEach(p=>{
|
|
1623
|
+
if(p==='...'){html+='<span class="page-ellipsis">\u2026</span>';}
|
|
1624
|
+
else{html+='<button class="btn btn-sm '+(p===page?'btn-primary':'btn-ghost')+'" onclick="loadLogs('+p+')">'+p+'</button>';}
|
|
1625
|
+
});
|
|
1626
|
+
html+='<button class="btn btn-sm btn-ghost" '+(page>=totalPages?'disabled':'')+' onclick="loadLogs('+(page+1)+')">\u203A</button>';
|
|
1627
|
+
html+='<span class="page-total">'+total+' total</span>';
|
|
1628
|
+
html+='</div>';
|
|
1629
|
+
el.innerHTML=html;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
function renderLogToolFilter(tools,current){
|
|
1633
|
+
const sel=document.getElementById('logToolFilter');
|
|
1634
|
+
const opts=['<option value="">'+t('logs.allTools')+'</option>'];
|
|
1635
|
+
tools.forEach(tn=>{
|
|
1636
|
+
opts.push('<option value="'+tn+'"'+(tn===current?' selected':'')+'>'+tn+'</option>');
|
|
1637
|
+
});
|
|
1638
|
+
sel.innerHTML=opts.join('');
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
function formatLogTime(ts){
|
|
1642
|
+
const d=new Date(ts);
|
|
1643
|
+
const time=d.toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit',second:'2-digit',hour12:false});
|
|
1644
|
+
const y=d.getFullYear();
|
|
1645
|
+
const m=String(d.getMonth()+1).padStart(2,'0');
|
|
1646
|
+
const day=String(d.getDate()).padStart(2,'0');
|
|
1647
|
+
return y+'-'+m+'-'+day+' '+time;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
function buildLogSummary(lg){
|
|
1651
|
+
let inputObj=null;
|
|
1652
|
+
try{inputObj=JSON.parse(lg.input);}catch(_){}
|
|
1653
|
+
let html='';
|
|
1654
|
+
const tn=lg.toolName;
|
|
1655
|
+
if(tn==='memory_search'&&inputObj){
|
|
1656
|
+
const q=inputObj.query||'';
|
|
1657
|
+
if(q) html+='<div class="log-summary-query">'+escapeHtml(q.length>200?q.slice(0,200)+'...':q)+'</div>';
|
|
1658
|
+
const outLines=(lg.output||'').split('\\n');
|
|
1659
|
+
const memCount=outLines.filter(l=>l.match(/^\\d+\\.\\s*\\[/)).length;
|
|
1660
|
+
if(memCount>0) html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u{1F4CE} '+memCount+' memories retrieved</div>';
|
|
1661
|
+
else if(lg.output&&lg.output.includes('no hits')) html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u2205 No matching memories</div>';
|
|
1662
|
+
}else if(tn==='memory_add'&&inputObj){
|
|
1663
|
+
const out=lg.output||'';
|
|
1664
|
+
const statsMatch=out.match(/^([^\\n]+)/);
|
|
1665
|
+
if(statsMatch){
|
|
1666
|
+
html+='<div class="log-summary-stats">';
|
|
1667
|
+
const pairs=statsMatch[1].split(',').map(s=>s.trim());
|
|
1668
|
+
pairs.forEach(p=>{
|
|
1669
|
+
const m=p.match(/^(\\w+)=(\\d+)/);
|
|
1670
|
+
if(m){html+='<span class="log-stat-chip '+m[1]+'">'+m[1]+' '+m[2]+'</span>';}
|
|
1671
|
+
});
|
|
1672
|
+
html+='</div>';
|
|
1673
|
+
}
|
|
1674
|
+
const outLines=out.split('\\n').filter(l=>l.startsWith('['));
|
|
1675
|
+
if(outLines.length>0){
|
|
1676
|
+
html+='<div class="log-msg-list">';
|
|
1677
|
+
outLines.forEach(function(l){
|
|
1678
|
+
var rm=l.match(/^\\[(\\w+)\\]\\s*([^\u2192]+)\u2192\\s*(.*)/);
|
|
1679
|
+
if(rm){
|
|
1680
|
+
var role=rm[1],actionRaw=rm[2].trim(),text=rm[3].trim();
|
|
1681
|
+
var actionCls='stored';
|
|
1682
|
+
if(actionRaw.indexOf('exact-dup')>=0||actionRaw.indexOf('\u23ED')>=0) actionCls='exact-dup';
|
|
1683
|
+
else if(actionRaw.indexOf('dedup')>=0||actionRaw.indexOf('\uD83D\uDD01')>=0) actionCls='dedup';
|
|
1684
|
+
else if(actionRaw.indexOf('merged')>=0||actionRaw.indexOf('\uD83D\uDD00')>=0) actionCls='merged';
|
|
1685
|
+
else if(actionRaw.indexOf('error')>=0||actionRaw.indexOf('\u274C')>=0) actionCls='error';
|
|
1686
|
+
var actionLabel={'stored':'\u2713 stored','exact-dup':'\u23ED skip','dedup':'\uD83D\uDD01 dedup','merged':'\uD83D\uDD00 merged','error':'\u2717 error'}[actionCls]||actionCls;
|
|
1687
|
+
html+='<div class="log-msg-item">'+
|
|
1688
|
+
'<span class="log-msg-role '+role+'">'+role+'</span>'+
|
|
1689
|
+
'<span class="log-msg-action '+actionCls+'">'+actionLabel+'</span>'+
|
|
1690
|
+
'<span class="log-msg-text">'+escapeHtml(text.length>150?text.slice(0,150)+'...':text)+'</span>'+
|
|
1691
|
+
'</div>';
|
|
1692
|
+
}else{
|
|
1693
|
+
html+='<div class="log-msg-item"><span class="log-msg-text">'+escapeHtml(l.length>200?l.slice(0,200)+'...':l)+'</span></div>';
|
|
1694
|
+
}
|
|
1695
|
+
});
|
|
1696
|
+
html+='</div>';
|
|
1697
|
+
}else if(inputObj.details&&Array.isArray(inputObj.details)&&inputObj.details.length>0){
|
|
1698
|
+
html+='<div class="log-msg-list">';
|
|
1699
|
+
inputObj.details.forEach(function(d){
|
|
1700
|
+
var s=typeof d==='string'?d:String(d);
|
|
1701
|
+
var dm=s.match(/^\\[(\\w+)\\]\\s*(.*)/);
|
|
1702
|
+
if(dm){
|
|
1703
|
+
html+='<div class="log-msg-item"><span class="log-msg-role '+dm[1]+'">'+dm[1]+'</span><span class="log-msg-text">'+escapeHtml(dm[2].length>150?dm[2].slice(0,150)+'...':dm[2])+'</span></div>';
|
|
1704
|
+
}else{
|
|
1705
|
+
html+='<div class="log-msg-item"><span class="log-msg-text">'+escapeHtml(s.length>150?s.slice(0,150)+'...':s)+'</span></div>';
|
|
1706
|
+
}
|
|
1707
|
+
});
|
|
1708
|
+
html+='</div>';
|
|
1709
|
+
}
|
|
1710
|
+
}else if(inputObj){
|
|
1711
|
+
const keys=Object.keys(inputObj);
|
|
1712
|
+
keys.slice(0,4).forEach(k=>{
|
|
1713
|
+
const v=String(inputObj[k]);
|
|
1714
|
+
html+='<span class="log-summary-kv"><span class="kv-label">'+escapeHtml(k)+':</span><span class="kv-val">'+escapeHtml(v.length>60?v.slice(0,60)+'...':v)+'</span></span>';
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
return html;
|
|
1718
|
+
}
|
|
1719
|
+
function renderLogs(logs){
|
|
1720
|
+
const el=document.getElementById('logsList');
|
|
1721
|
+
if(!logs.length){
|
|
1722
|
+
el.innerHTML='<div style="text-align:center;padding:60px 20px;color:var(--text-sec)">'+
|
|
1723
|
+
'<div style="font-size:32px;margin-bottom:12px;opacity:.5">\u{1F4CB}</div>'+
|
|
1724
|
+
'<div style="font-size:13px">'+t('logs.empty')+'</div></div>';
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
el.innerHTML=logs.map((lg,i)=>{
|
|
1728
|
+
const toolCls=lg.toolName.replace(/[^a-zA-Z0-9_]/g,'_');
|
|
1729
|
+
const dur=lg.durationMs<1000?Math.round(lg.durationMs)+'ms':(lg.durationMs/1000).toFixed(1)+'s';
|
|
1730
|
+
let inputDisplay='';
|
|
1731
|
+
try{const parsed=JSON.parse(lg.input);inputDisplay=JSON.stringify(parsed,null,2);}catch(_){inputDisplay=lg.input;}
|
|
1732
|
+
const summary=buildLogSummary(lg);
|
|
1733
|
+
return '<div class="log-entry" id="log-'+i+'">'+
|
|
1734
|
+
'<div class="log-header" onclick="toggleLog('+i+')">'+
|
|
1735
|
+
'<span class="log-status '+(lg.success?'ok':'fail')+'"></span>'+
|
|
1736
|
+
'<span class="log-tool-badge '+toolCls+'">'+lg.toolName+'</span>'+
|
|
1737
|
+
'<span class="log-dur">'+dur+'</span>'+
|
|
1738
|
+
'<span class="log-expand-btn" style="margin-left:4px">\u25BC</span>'+
|
|
1739
|
+
'<span class="log-time">'+formatLogTime(lg.calledAt)+'</span>'+
|
|
1740
|
+
'</div>'+
|
|
1741
|
+
(summary?'<div class="log-summary">'+summary+'</div>':'')+
|
|
1742
|
+
'<div class="log-detail" id="log-detail-'+i+'">'+
|
|
1743
|
+
'<div class="log-io-section">'+
|
|
1744
|
+
'<div class="log-io-label">\u25B6 '+t('logs.input')+'</div>'+
|
|
1745
|
+
'<pre class="log-io-content">'+escapeHtml(inputDisplay)+'</pre>'+
|
|
1746
|
+
'</div>'+
|
|
1747
|
+
'<div class="log-io-section">'+
|
|
1748
|
+
'<div class="log-io-label">\u25C0 '+t('logs.output')+'</div>'+
|
|
1749
|
+
'<pre class="log-io-content">'+escapeHtml(lg.output)+'</pre>'+
|
|
1750
|
+
'</div>'+
|
|
1751
|
+
'</div>'+
|
|
1752
|
+
'</div>';
|
|
1753
|
+
}).join('');
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
function toggleLog(i){
|
|
1757
|
+
const entry=document.getElementById('log-'+i);
|
|
1758
|
+
const d=document.getElementById('log-detail-'+i);
|
|
1759
|
+
if(d) d.classList.toggle('open');
|
|
1760
|
+
if(entry) entry.classList.toggle('expanded');
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
function startLogAutoRefresh(){
|
|
1764
|
+
if(logAutoTimer) clearInterval(logAutoTimer);
|
|
1765
|
+
logAutoTimer=setInterval(()=>{
|
|
1766
|
+
const cb=document.getElementById('logAutoRefresh');
|
|
1767
|
+
const logsView=document.getElementById('logsView');
|
|
1768
|
+
if(cb&&cb.checked&&logsView&&logsView.classList.contains('show')){
|
|
1769
|
+
loadLogs();
|
|
1770
|
+
}
|
|
1771
|
+
},5000);
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
function escapeHtml(s){
|
|
1775
|
+
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
function setMetricsDays(d){
|
|
1779
|
+
metricsDays=d;
|
|
1780
|
+
document.querySelectorAll('.metrics-toolbar .range-btn').forEach(btn=>btn.classList.toggle('active',Number(btn.dataset.days)===d));
|
|
1781
|
+
loadMetrics();
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
async function loadMetrics(){
|
|
1785
|
+
const r=await fetch('/api/metrics?days='+metricsDays);
|
|
1786
|
+
const d=await r.json();
|
|
1787
|
+
document.getElementById('mTotal').textContent=formatNum(d.totals.memories);
|
|
1788
|
+
document.getElementById('mTodayWrites').textContent=formatNum(d.totals.todayWrites);
|
|
1789
|
+
document.getElementById('mSessions').textContent=formatNum(d.totals.sessions);
|
|
1790
|
+
document.getElementById('mEmbeddings').textContent=formatNum(d.totals.embeddings);
|
|
1791
|
+
renderChartWrites(d.writesPerDay);
|
|
1792
|
+
renderBreakdown(d.roleBreakdown,'breakdownRole');
|
|
1793
|
+
renderBreakdown(d.kindBreakdown,'breakdownKind');
|
|
1794
|
+
loadToolMetrics();
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
function formatNum(n){return n>=1e6?(n/1e6).toFixed(1)+'M':n>=1e3?(n/1e3).toFixed(1)+'k':String(n);}
|
|
1798
|
+
|
|
1799
|
+
/* ─── Tasks View Logic ─── */
|
|
1800
|
+
let tasksStatusFilter='';
|
|
1801
|
+
let tasksPage=0;
|
|
1802
|
+
const TASKS_PER_PAGE=20;
|
|
1803
|
+
|
|
1804
|
+
function setTaskStatusFilter(btn,status){
|
|
1805
|
+
document.querySelectorAll('.tasks-filters .filter-chip').forEach(c=>c.classList.remove('active'));
|
|
1806
|
+
btn.classList.add('active');
|
|
1807
|
+
tasksStatusFilter=status;
|
|
1808
|
+
tasksPage=0;
|
|
1809
|
+
loadTasks();
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
async function loadTasks(){
|
|
1813
|
+
const list=document.getElementById('tasksList');
|
|
1814
|
+
list.innerHTML='<div class="spinner"></div>';
|
|
1815
|
+
try{
|
|
1816
|
+
const params=new URLSearchParams({limit:String(TASKS_PER_PAGE),offset:String(tasksPage*TASKS_PER_PAGE)});
|
|
1817
|
+
if(tasksStatusFilter) params.set('status',tasksStatusFilter);
|
|
1818
|
+
const r=await fetch('/api/tasks?'+params);
|
|
1819
|
+
const data=await r.json();
|
|
1820
|
+
|
|
1821
|
+
// stats
|
|
1822
|
+
const allR=await fetch('/api/tasks?limit=1&offset=0');
|
|
1823
|
+
const allD=await allR.json();
|
|
1824
|
+
document.getElementById('tasksTotalCount').textContent=formatNum(allD.total);
|
|
1825
|
+
|
|
1826
|
+
const activeR=await fetch('/api/tasks?status=active&limit=1&offset=0');
|
|
1827
|
+
const activeD=await activeR.json();
|
|
1828
|
+
document.getElementById('tasksActiveCount').textContent=formatNum(activeD.total);
|
|
1829
|
+
|
|
1830
|
+
const compR=await fetch('/api/tasks?status=completed&limit=1&offset=0');
|
|
1831
|
+
const compD=await compR.json();
|
|
1832
|
+
document.getElementById('tasksCompletedCount').textContent=formatNum(compD.total);
|
|
1833
|
+
|
|
1834
|
+
const skipR=await fetch('/api/tasks?status=skipped&limit=1&offset=0');
|
|
1835
|
+
const skipD=await skipR.json();
|
|
1836
|
+
document.getElementById('tasksSkippedCount').textContent=formatNum(skipD.total);
|
|
1837
|
+
|
|
1838
|
+
if(!data.tasks||data.tasks.length===0){
|
|
1839
|
+
list.innerHTML='<div style="text-align:center;padding:48px;color:var(--text-muted);font-size:14px" data-i18n="tasks.empty">'+t('tasks.empty')+'</div>';
|
|
1840
|
+
document.getElementById('tasksPagination').innerHTML='';
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
list.innerHTML=data.tasks.map(task=>{
|
|
1845
|
+
const timeStr=formatTime(task.startedAt);
|
|
1846
|
+
const endStr=task.endedAt?formatTime(task.endedAt):'';
|
|
1847
|
+
const durationStr=task.endedAt?formatDuration(task.endedAt-task.startedAt):'';
|
|
1848
|
+
return '<div class="task-card status-'+task.status+'" onclick="openTaskDetail(\\''+task.id+'\\')">'+
|
|
1849
|
+
'<div class="task-card-top">'+
|
|
1850
|
+
'<div class="task-card-title">'+esc(task.title)+'</div>'+
|
|
1851
|
+
'<span class="task-status-badge '+task.status+'">'+t('tasks.status.'+task.status)+'</span>'+
|
|
1852
|
+
'</div>'+
|
|
1853
|
+
(task.summary?'<div class="task-card-summary'+(task.status==='skipped'?' skipped-reason':'')+'">'+esc(task.summary)+'</div>':'')+
|
|
1854
|
+
'<div class="task-card-bottom">'+
|
|
1855
|
+
'<span class="tag"><span class="icon">\\u{1F4C5}</span> '+timeStr+'</span>'+
|
|
1856
|
+
(durationStr?'<span class="tag"><span class="icon">\\u23F1</span> '+durationStr+'</span>':'')+
|
|
1857
|
+
'<span class="tag"><span class="icon">\\u{1F4DD}</span> '+task.chunkCount+' '+t('tasks.chunks.label')+'</span>'+
|
|
1858
|
+
'<span class="tag"><span class="icon">\\u{1F4C2}</span> '+(task.sessionKey||'').slice(0,12)+'</span>'+
|
|
1859
|
+
'</div>'+
|
|
1860
|
+
'</div>';
|
|
1861
|
+
}).join('');
|
|
1862
|
+
|
|
1863
|
+
renderTasksPagination(data.total);
|
|
1864
|
+
}catch(e){
|
|
1865
|
+
console.error('loadTasks error:',e);
|
|
1866
|
+
list.innerHTML='<div style="text-align:center;padding:24px;color:var(--rose)">Failed to load tasks: '+String(e)+'</div>';
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
function renderTasksPagination(total){
|
|
1871
|
+
const el=document.getElementById('tasksPagination');
|
|
1872
|
+
const pages=Math.ceil(total/TASKS_PER_PAGE);
|
|
1873
|
+
if(pages<=1){el.innerHTML='';return;}
|
|
1874
|
+
let html='<button class="pg-btn'+(tasksPage===0?' disabled':'')+'" onclick="tasksPage=Math.max(0,tasksPage-1);loadTasks()">\\u2190</button>';
|
|
1875
|
+
const start=Math.max(0,tasksPage-2),end=Math.min(pages,tasksPage+3);
|
|
1876
|
+
for(let i=start;i<end;i++){
|
|
1877
|
+
html+='<button class="pg-btn'+(i===tasksPage?' active':'')+'" onclick="tasksPage='+i+';loadTasks()">'+(i+1)+'</button>';
|
|
1878
|
+
}
|
|
1879
|
+
html+='<button class="pg-btn'+(tasksPage>=pages-1?' disabled':'')+'" onclick="tasksPage=Math.min('+(pages-1)+',tasksPage+1);loadTasks()">\\u2192</button>';
|
|
1880
|
+
html+='<span class="pg-info">'+total+' '+t('pagination.total')+'</span>';
|
|
1881
|
+
el.innerHTML=html;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
async function openTaskDetail(taskId){
|
|
1885
|
+
const overlay=document.getElementById('taskDetailOverlay');
|
|
1886
|
+
overlay.classList.add('show');
|
|
1887
|
+
document.getElementById('taskDetailTitle').textContent=t('tasks.loading');
|
|
1888
|
+
document.getElementById('taskDetailMeta').innerHTML='';
|
|
1889
|
+
document.getElementById('taskSkillSection').innerHTML='';
|
|
1890
|
+
document.getElementById('taskSkillSection').className='task-skill-section';
|
|
1891
|
+
document.getElementById('taskDetailSummary').textContent='';
|
|
1892
|
+
document.getElementById('taskDetailChunks').innerHTML='<div class="spinner"></div>';
|
|
1893
|
+
|
|
1894
|
+
try{
|
|
1895
|
+
const r=await fetch('/api/task/'+taskId);
|
|
1896
|
+
const task=await r.json();
|
|
1897
|
+
|
|
1898
|
+
document.getElementById('taskDetailTitle').textContent=task.title||t('tasks.untitled');
|
|
1899
|
+
|
|
1900
|
+
const meta=[
|
|
1901
|
+
'<span class="meta-item"><span class="task-status-badge '+task.status+'">'+t('tasks.status.'+task.status)+'</span></span>',
|
|
1902
|
+
'<span class="meta-item">\\u{1F4C5} '+formatTime(task.startedAt)+'</span>',
|
|
1903
|
+
];
|
|
1904
|
+
if(task.endedAt) meta.push('<span class="meta-item">\\u2192 '+formatTime(task.endedAt)+'</span>');
|
|
1905
|
+
meta.push('<span class="meta-item">\\u{1F4C2} '+task.sessionKey+'</span>');
|
|
1906
|
+
meta.push('<span class="meta-item">\\u{1F4DD} '+task.chunks.length+' '+t('tasks.chunks.label')+'</span>');
|
|
1907
|
+
meta.push('<div style="width:100%;margin-top:4px"><span class="meta-item" style="width:100%">'+t('tasks.taskid')+'<span class="task-id-full">'+esc(task.id)+'</span></span></div>');
|
|
1908
|
+
document.getElementById('taskDetailMeta').innerHTML=meta.join('');
|
|
1909
|
+
|
|
1910
|
+
// ── Skill status section ──
|
|
1911
|
+
renderTaskSkillSection(task);
|
|
1912
|
+
|
|
1913
|
+
var summaryEl=document.getElementById('taskDetailSummary');
|
|
1914
|
+
if(task.status==='skipped'){
|
|
1915
|
+
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>';
|
|
1916
|
+
}else{
|
|
1917
|
+
summaryEl.innerHTML=renderSummaryHtml(task.summary);
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
if(task.chunks.length===0){
|
|
1921
|
+
document.getElementById('taskDetailChunks').innerHTML='<div style="color:var(--text-muted);padding:12px;font-size:13px">'+t('tasks.nochunks')+'</div>';
|
|
1922
|
+
}else{
|
|
1923
|
+
document.getElementById('taskDetailChunks').innerHTML=task.chunks.map(c=>{
|
|
1924
|
+
var roleLabel=c.role==='user'?t('tasks.role.user'):c.role==='assistant'?t('tasks.role.assistant'):c.role.toUpperCase();
|
|
1925
|
+
return '<div class="task-chunk-item role-'+c.role+'">'+
|
|
1926
|
+
'<div class="task-chunk-role '+c.role+'">'+roleLabel+'</div>'+
|
|
1927
|
+
'<div class="task-chunk-bubble" onclick="this.classList.toggle(\\\'expanded\\\')">'+esc(c.content)+'</div>'+
|
|
1928
|
+
'<div class="task-chunk-time">'+formatTime(c.createdAt)+'</div>'+
|
|
1929
|
+
'</div>';
|
|
1930
|
+
}).join('');
|
|
1931
|
+
}
|
|
1932
|
+
}catch(e){
|
|
1933
|
+
document.getElementById('taskDetailTitle').textContent=t('tasks.error');
|
|
1934
|
+
document.getElementById('taskDetailChunks').innerHTML='<div style="color:var(--rose)">'+t('tasks.error.detail')+'</div>';
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
function renderTaskSkillSection(task){
|
|
1939
|
+
const section=document.getElementById('taskSkillSection');
|
|
1940
|
+
const ss=task.skillStatus;
|
|
1941
|
+
const links=task.skillLinks||[];
|
|
1942
|
+
|
|
1943
|
+
if(links.length>0){
|
|
1944
|
+
section.className='task-skill-section status-generated';
|
|
1945
|
+
var html='<div class="skill-status-header">\\u{1F527} \u5DF2\u751F\u6210\u6280\u80FD</div>';
|
|
1946
|
+
html+=links.map(function(lk){
|
|
1947
|
+
var relLabel={'generated_from':'\u7531\u6B64\u4EFB\u52A1\u751F\u6210','evolved_from':'\u7531\u6B64\u4EFB\u52A1\u5347\u7EA7','applied_to':'\u5173\u8054\u4F7F\u7528'}[lk.relation]||lk.relation;
|
|
1948
|
+
var statusLabel={'active':'\u6D3B\u8DC3','draft':'\u8349\u7A3F','archived':'\u5DF2\u5F52\u6863'}[lk.status]||lk.status;
|
|
1949
|
+
return '<div class="skill-link-card" onclick="event.stopPropagation();closeTaskDetail();switchView(\\'skills\\');setTimeout(function(){openSkillDetail(\\''+lk.skillId+'\\')},300)">'+
|
|
1950
|
+
'<div class="skill-link-name">'+esc(lk.skillName)+' <span style="font-size:11px;color:var(--text-sec)">('+relLabel+', v'+lk.versionAt+')</span></div>'+
|
|
1951
|
+
'<div class="skill-link-meta">'+
|
|
1952
|
+
'\u72B6\u6001: <span class="task-status-badge '+(lk.status||'active')+'">'+statusLabel+'</span>'+
|
|
1953
|
+
(lk.qualityScore!=null?' · \u8D28\u91CF\u5206: '+lk.qualityScore+'/10':'')+
|
|
1954
|
+
'</div>'+
|
|
1955
|
+
'<div style="margin-top:4px"><span class="task-id-full">Skill ID: '+esc(lk.skillId)+'</span></div>'+
|
|
1956
|
+
'</div>';
|
|
1957
|
+
}).join('');
|
|
1958
|
+
section.innerHTML=html;
|
|
1959
|
+
}else if(ss==='generating'){
|
|
1960
|
+
section.className='task-skill-section status-generating';
|
|
1961
|
+
section.innerHTML='<div class="skill-status-header">\\u23F3 \u6280\u80FD\u751F\u6210\u4E2D...</div>'+
|
|
1962
|
+
'<div class="skill-status-reason">'+esc(task.skillReason||'')+'</div>';
|
|
1963
|
+
}else if(ss==='not_generated'){
|
|
1964
|
+
section.className='task-skill-section status-not_generated';
|
|
1965
|
+
section.innerHTML='<div class="skill-status-header">\\u274C \u672A\u751F\u6210\u6280\u80FD</div>'+
|
|
1966
|
+
'<div class="skill-status-reason">\u539F\u56E0\uFF1A'+esc(task.skillReason||'\u7ECF LLM \u8BC4\u4F30\uFF0C\u8BE5\u4EFB\u52A1\u4E0D\u9002\u5408\u63D0\u70BC\u4E3A\u53EF\u590D\u7528\u6280\u80FD\u3002')+'</div>';
|
|
1967
|
+
}else if(ss==='skipped'){
|
|
1968
|
+
section.className='task-skill-section status-skipped';
|
|
1969
|
+
section.innerHTML='<div class="skill-status-header">\\u23ED \u8DF3\u8FC7\u6280\u80FD\u8BC4\u4F30</div>'+
|
|
1970
|
+
'<div class="skill-status-reason">\u539F\u56E0\uFF1A'+esc(task.skillReason||'')+'</div>';
|
|
1971
|
+
}else if(task.status==='active'){
|
|
1972
|
+
section.className='task-skill-section status-skipped';
|
|
1973
|
+
section.innerHTML='<div class="skill-status-header">\\u23F8 \u4EFB\u52A1\u8FDB\u884C\u4E2D</div>'+
|
|
1974
|
+
'<div class="skill-status-reason">\u6280\u80FD\u8BC4\u4F30\u5728\u4EFB\u52A1\u5B8C\u6210\u540E\u81EA\u52A8\u8FD0\u884C\u3002</div>';
|
|
1975
|
+
}else{
|
|
1976
|
+
section.className='task-skill-section status-skipped';
|
|
1977
|
+
section.innerHTML='<div class="skill-status-header">\\u2014 \u65E0\u6280\u80FD\u4FE1\u606F</div>'+
|
|
1978
|
+
'<div class="skill-status-reason">\u8BE5\u4EFB\u52A1\u5728\u6280\u80FD\u8FDB\u5316\u7CFB\u7EDF\u542F\u7528\u4E4B\u524D\u5B8C\u6210\uFF0C\u65E0\u6280\u80FD\u8BC4\u4F30\u8BB0\u5F55\u3002</div>';
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
function closeTaskDetail(event){
|
|
1983
|
+
if(event && event.target!==document.getElementById('taskDetailOverlay')) return;
|
|
1984
|
+
document.getElementById('taskDetailOverlay').classList.remove('show');
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
/* ─── Skills View Logic ─── */
|
|
1988
|
+
let skillsStatusFilter='';
|
|
1989
|
+
|
|
1990
|
+
function setSkillStatusFilter(btn,status){
|
|
1991
|
+
document.querySelectorAll('.skills-view .tasks-filters .filter-chip').forEach(c=>c.classList.remove('active'));
|
|
1992
|
+
btn.classList.add('active');
|
|
1993
|
+
skillsStatusFilter=status;
|
|
1994
|
+
loadSkills();
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
async function loadSkills(){
|
|
1998
|
+
const list=document.getElementById('skillsList');
|
|
1999
|
+
list.innerHTML='<div class="spinner"></div>';
|
|
2000
|
+
try{
|
|
2001
|
+
const params=new URLSearchParams();
|
|
2002
|
+
if(skillsStatusFilter) params.set('status',skillsStatusFilter);
|
|
2003
|
+
const r=await fetch('/api/skills?'+params);
|
|
2004
|
+
const data=await r.json();
|
|
2005
|
+
|
|
2006
|
+
document.getElementById('skillsTotalCount').textContent=formatNum(data.skills.length);
|
|
2007
|
+
document.getElementById('skillsActiveCount').textContent=formatNum(data.skills.filter(s=>s.status==='active').length);
|
|
2008
|
+
document.getElementById('skillsDraftCount').textContent=formatNum(data.skills.filter(s=>s.status==='draft').length);
|
|
2009
|
+
document.getElementById('skillsInstalledCount').textContent=formatNum(data.skills.filter(s=>s.installed).length);
|
|
2010
|
+
|
|
2011
|
+
if(!data.skills||data.skills.length===0){
|
|
2012
|
+
list.innerHTML='<div style="text-align:center;padding:48px;color:var(--text-muted);font-size:14px">'+t('skills.empty')+'</div>';
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
list.innerHTML=data.skills.map(skill=>{
|
|
2017
|
+
const timeStr=formatTime(skill.createdAt);
|
|
2018
|
+
const tags=parseTags(skill.tags);
|
|
2019
|
+
const installedClass=skill.installed?'installed':'';
|
|
2020
|
+
const statusClass=skill.status==='archived'?'archived':(skill.status==='draft'?'draft':'');
|
|
2021
|
+
const qs=skill.qualityScore;
|
|
2022
|
+
const qsBadge=qs!==null&&qs!==undefined?'<span class="skill-badge quality '+(qs>=7?'high':qs>=5?'mid':'low')+'">\\u2605 '+qs.toFixed(1)+'</span>':'';
|
|
2023
|
+
return '<div class="skill-card '+installedClass+' '+statusClass+'" onclick="openSkillDetail(\\''+skill.id+'\\')">'+
|
|
2024
|
+
'<div class="skill-card-top">'+
|
|
2025
|
+
'<div class="skill-card-name">\\u{1F9E0} '+esc(skill.name)+'</div>'+
|
|
2026
|
+
'<div class="skill-card-badges">'+
|
|
2027
|
+
qsBadge+
|
|
2028
|
+
'<span class="skill-badge version">v'+skill.version+'</span>'+
|
|
2029
|
+
(skill.installed?'<span class="skill-badge installed">'+t('skills.installed.badge')+'</span>':'')+
|
|
2030
|
+
'<span class="skill-badge status-'+skill.status+'">'+t('skills.status.'+skill.status)+'</span>'+
|
|
2031
|
+
'</div>'+
|
|
2032
|
+
'</div>'+
|
|
2033
|
+
'<div class="skill-card-desc">'+esc(skill.description)+'</div>'+
|
|
2034
|
+
'<div class="skill-card-bottom">'+
|
|
2035
|
+
'<span class="tag"><span class="icon">\\u{1F4C5}</span> '+timeStr+'</span>'+
|
|
2036
|
+
'<span class="tag"><span class="icon">\\u{1F4E6}</span> '+skill.sourceType+'</span>'+
|
|
2037
|
+
(tags.length>0?'<div class="skill-card-tags">'+tags.map(t=>'<span class="skill-tag">'+esc(t)+'</span>').join('')+'</div>':'')+
|
|
2038
|
+
'</div>'+
|
|
2039
|
+
'</div>';
|
|
2040
|
+
}).join('');
|
|
2041
|
+
}catch(e){
|
|
2042
|
+
list.innerHTML='<div style="text-align:center;padding:24px;color:var(--rose)">Failed to load skills: '+esc(String(e))+'</div>';
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
function parseTags(tagsStr){
|
|
2047
|
+
try{ const arr=JSON.parse(tagsStr||'[]'); return Array.isArray(arr)?arr:[]; }catch{ return []; }
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
let currentSkillId='';
|
|
2051
|
+
|
|
2052
|
+
async function openSkillDetail(skillId){
|
|
2053
|
+
currentSkillId=skillId;
|
|
2054
|
+
const overlay=document.getElementById('skillDetailOverlay');
|
|
2055
|
+
overlay.classList.add('show');
|
|
2056
|
+
document.getElementById('skillDetailTitle').textContent=t('skills.loading');
|
|
2057
|
+
document.getElementById('skillDetailMeta').innerHTML='';
|
|
2058
|
+
document.getElementById('skillDetailDesc').textContent='';
|
|
2059
|
+
document.getElementById('skillFilesList').innerHTML='';
|
|
2060
|
+
document.getElementById('skillDetailContent').innerHTML='<div class="spinner"></div>';
|
|
2061
|
+
document.getElementById('skillVersionsList').innerHTML='<div class="spinner"></div>';
|
|
2062
|
+
document.getElementById('skillRelatedTasks').innerHTML='';
|
|
2063
|
+
|
|
2064
|
+
try{
|
|
2065
|
+
const r=await fetch('/api/skill/'+skillId);
|
|
2066
|
+
if(!r.ok){
|
|
2067
|
+
const errText=await r.text();
|
|
2068
|
+
throw new Error('API '+r.status+': '+errText);
|
|
2069
|
+
}
|
|
2070
|
+
const data=await r.json();
|
|
2071
|
+
if(!data.skill){
|
|
2072
|
+
throw new Error('No skill data in response: '+JSON.stringify(data).slice(0,200));
|
|
2073
|
+
}
|
|
2074
|
+
const skill=data.skill;
|
|
2075
|
+
const versions=data.versions||[];
|
|
2076
|
+
const relatedTasks=data.relatedTasks||[];
|
|
2077
|
+
const files=data.files||[];
|
|
2078
|
+
|
|
2079
|
+
document.getElementById('skillDetailTitle').textContent='\\u{1F9E0} '+skill.name;
|
|
2080
|
+
|
|
2081
|
+
const qs=skill.qualityScore;
|
|
2082
|
+
const qsBadge=qs!==null&&qs!==undefined?'<span class="meta-item"><span class="skill-badge quality '+(qs>=7?'high':qs>=5?'mid':'low')+'">\\u2605 '+qs.toFixed(1)+'/10</span></span>':'';
|
|
2083
|
+
document.getElementById('skillDetailMeta').innerHTML=[
|
|
2084
|
+
'<span class="meta-item"><span class="skill-badge version">v'+skill.version+'</span></span>',
|
|
2085
|
+
'<span class="meta-item"><span class="skill-badge status-'+skill.status+'">'+t('skills.status.'+skill.status)+'</span></span>',
|
|
2086
|
+
qsBadge,
|
|
2087
|
+
skill.installed?'<span class="meta-item"><span class="skill-badge installed">'+t('skills.installed.badge')+'</span></span>':'',
|
|
2088
|
+
'<span class="meta-item">\\u{1F4C5} '+formatTime(skill.createdAt)+'</span>',
|
|
2089
|
+
'<span class="meta-item">\\u270F '+t('skills.updated')+formatTime(skill.updatedAt)+'</span>',
|
|
2090
|
+
].filter(Boolean).join('');
|
|
2091
|
+
|
|
2092
|
+
document.getElementById('skillDetailDesc').textContent=skill.description;
|
|
2093
|
+
|
|
2094
|
+
if(files.length>0){
|
|
2095
|
+
const fileIcons={'skill':'\\u{1F4D6}','script':'\\u{2699}','reference':'\\u{1F4CE}','file':'\\u{1F4C4}'};
|
|
2096
|
+
document.getElementById('skillFilesList').innerHTML=files.map(f=>
|
|
2097
|
+
'<div class="skill-file-item">'+
|
|
2098
|
+
'<span class="skill-file-icon">'+(fileIcons[f.type]||'\\u{1F4C4}')+'</span>'+
|
|
2099
|
+
'<span class="skill-file-name">'+esc(f.path)+'</span>'+
|
|
2100
|
+
'<span class="skill-file-type">'+f.type+'</span>'+
|
|
2101
|
+
'<span class="skill-file-size">'+(f.size>1024?(f.size/1024).toFixed(1)+'KB':f.size+'B')+'</span>'+
|
|
2102
|
+
'</div>'
|
|
2103
|
+
).join('');
|
|
2104
|
+
} else {
|
|
2105
|
+
document.getElementById('skillFilesList').innerHTML='<div style="color:var(--text-muted);font-size:12px">'+t('skills.nofiles')+'</div>';
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
const latestVersion=versions[0];
|
|
2109
|
+
document.getElementById('skillContentTitle').textContent=latestVersion?'SKILL.md (v'+latestVersion.version+')':t('skills.content');
|
|
2110
|
+
document.getElementById('skillDetailContent').innerHTML=latestVersion?renderSkillMarkdown(latestVersion.content):'<span style="color:var(--text-muted)">'+t('skills.nocontent')+'</span>';
|
|
2111
|
+
|
|
2112
|
+
if(versions.length===0){
|
|
2113
|
+
document.getElementById('skillVersionsList').innerHTML='<div style="color:var(--text-muted);font-size:13px">'+t('skills.noversions')+'</div>';
|
|
2114
|
+
} else {
|
|
2115
|
+
document.getElementById('skillVersionsList').innerHTML=versions.map(v=>{
|
|
2116
|
+
const vqs=v.qualityScore;
|
|
2117
|
+
const vqsBadge=vqs!==null&&vqs!==undefined?'<span class="skill-badge quality '+(vqs>=7?'high':vqs>=5?'mid':'low')+'">\\u2605 '+vqs.toFixed(1)+'</span>':'';
|
|
2118
|
+
const summaryHtml=v.changeSummary?'<div class="skill-version-summary">'+esc(v.changeSummary)+'</div>':'';
|
|
2119
|
+
return '<div class="skill-version-item">'+
|
|
2120
|
+
'<div class="skill-version-header">'+
|
|
2121
|
+
'<span class="skill-version-badge">v'+v.version+'</span>'+
|
|
2122
|
+
'<span class="skill-version-type">'+v.upgradeType+'</span>'+
|
|
2123
|
+
vqsBadge+
|
|
2124
|
+
'</div>'+
|
|
2125
|
+
'<div class="skill-version-changelog">'+esc(v.changelog||t('skills.nochangelog'))+'</div>'+
|
|
2126
|
+
summaryHtml+
|
|
2127
|
+
'<div class="skill-version-time">'+formatTime(v.createdAt)+(v.sourceTaskId?' \\u2022 '+t('skills.task.prefix')+v.sourceTaskId.slice(0,8)+'...':'')+'</div>'+
|
|
2128
|
+
'</div>';
|
|
2129
|
+
}).join('');
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
if(relatedTasks.length===0){
|
|
2133
|
+
document.getElementById('skillRelatedTasks').innerHTML='<div style="color:var(--text-muted);font-size:13px">'+t('skills.norelated')+'</div>';
|
|
2134
|
+
} else {
|
|
2135
|
+
document.getElementById('skillRelatedTasks').innerHTML=relatedTasks.map(rt=>
|
|
2136
|
+
'<div class="skill-related-task" onclick="event.stopPropagation();closeSkillDetail();switchView(\\'tasks\\');setTimeout(()=>openTaskDetail(\\''+rt.task.id+'\\'),300)">'+
|
|
2137
|
+
'<span class="relation">'+rt.relation+'</span>'+
|
|
2138
|
+
'<span class="task-title">'+esc(rt.task.title||t('tasks.untitled.related'))+'</span>'+
|
|
2139
|
+
'<span style="font-size:11px;color:var(--text-muted)">'+formatTime(rt.task.startedAt)+'</span>'+
|
|
2140
|
+
'</div>'
|
|
2141
|
+
).join('');
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
}catch(e){
|
|
2145
|
+
document.getElementById('skillDetailTitle').textContent=t('skills.error');
|
|
2146
|
+
document.getElementById('skillDetailContent').innerHTML='<div style="color:var(--rose);padding:16px">'+t('skills.error.detail')+esc(String(e))+'</div>';
|
|
2147
|
+
document.getElementById('skillFilesList').innerHTML='';
|
|
2148
|
+
document.getElementById('skillVersionsList').innerHTML='';
|
|
2149
|
+
document.getElementById('skillRelatedTasks').innerHTML='';
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
function downloadSkill(){
|
|
2154
|
+
if(!currentSkillId) return;
|
|
2155
|
+
window.open('/api/skill/'+currentSkillId+'/download','_blank');
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
/* ─── Settings / Config ─── */
|
|
2159
|
+
async function loadConfig(){
|
|
2160
|
+
try{
|
|
2161
|
+
const r=await fetch('/api/config');
|
|
2162
|
+
if(!r.ok) return;
|
|
2163
|
+
const cfg=await r.json();
|
|
2164
|
+
const emb=cfg.embedding||{};
|
|
2165
|
+
document.getElementById('cfgEmbProvider').value=emb.provider||'openai_compatible';
|
|
2166
|
+
document.getElementById('cfgEmbModel').value=emb.model||'';
|
|
2167
|
+
document.getElementById('cfgEmbEndpoint').value=emb.endpoint||'';
|
|
2168
|
+
document.getElementById('cfgEmbApiKey').value=emb.apiKey||'';
|
|
2169
|
+
|
|
2170
|
+
const sum=cfg.summarizer||{};
|
|
2171
|
+
document.getElementById('cfgSumProvider').value=sum.provider||'openai_compatible';
|
|
2172
|
+
document.getElementById('cfgSumModel').value=sum.model||'';
|
|
2173
|
+
document.getElementById('cfgSumEndpoint').value=sum.endpoint||'';
|
|
2174
|
+
document.getElementById('cfgSumApiKey').value=sum.apiKey||'';
|
|
2175
|
+
document.getElementById('cfgSumTemp').value=sum.temperature!=null?sum.temperature:'';
|
|
2176
|
+
|
|
2177
|
+
const sk=cfg.skillEvolution||{};
|
|
2178
|
+
document.getElementById('cfgSkillEnabled').checked=sk.enabled!==false;
|
|
2179
|
+
document.getElementById('cfgSkillAutoInstall').checked=!!sk.autoInstall;
|
|
2180
|
+
document.getElementById('cfgSkillConfidence').value=sk.minConfidence||'';
|
|
2181
|
+
document.getElementById('cfgSkillMinChunks').value=sk.minChunksForEval||'';
|
|
2182
|
+
|
|
2183
|
+
const skSum=sk.summarizer||{};
|
|
2184
|
+
document.getElementById('cfgSkillProviderDefault').textContent='\u2014 '+t('settings.skill.usemain');
|
|
2185
|
+
document.getElementById('cfgSkillProvider').value=skSum.provider||'';
|
|
2186
|
+
document.getElementById('cfgSkillModel').value=skSum.model||'';
|
|
2187
|
+
document.getElementById('cfgSkillEndpoint').value=skSum.endpoint||'';
|
|
2188
|
+
document.getElementById('cfgSkillApiKey').value=skSum.apiKey||'';
|
|
2189
|
+
document.getElementById('cfgSkillTemp').value=skSum.temperature!=null?skSum.temperature:'';
|
|
2190
|
+
|
|
2191
|
+
document.getElementById('cfgViewerPort').value=cfg.viewerPort||'';
|
|
2192
|
+
}catch(e){
|
|
2193
|
+
console.error('loadConfig error',e);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
async function saveConfig(){
|
|
2198
|
+
const cfg={};
|
|
2199
|
+
const embP=document.getElementById('cfgEmbProvider').value;
|
|
2200
|
+
if(embP){
|
|
2201
|
+
cfg.embedding={provider:embP};
|
|
2202
|
+
const v=document.getElementById('cfgEmbModel').value.trim();if(v) cfg.embedding.model=v;
|
|
2203
|
+
const e=document.getElementById('cfgEmbEndpoint').value.trim();if(e) cfg.embedding.endpoint=e;
|
|
2204
|
+
const k=document.getElementById('cfgEmbApiKey').value.trim();if(k) cfg.embedding.apiKey=k;
|
|
2205
|
+
}
|
|
2206
|
+
const sumP=document.getElementById('cfgSumProvider').value;
|
|
2207
|
+
if(sumP){
|
|
2208
|
+
cfg.summarizer={provider:sumP};
|
|
2209
|
+
const v=document.getElementById('cfgSumModel').value.trim();if(v) cfg.summarizer.model=v;
|
|
2210
|
+
const e=document.getElementById('cfgSumEndpoint').value.trim();if(e) cfg.summarizer.endpoint=e;
|
|
2211
|
+
const k=document.getElementById('cfgSumApiKey').value.trim();if(k) cfg.summarizer.apiKey=k;
|
|
2212
|
+
const tp=document.getElementById('cfgSumTemp').value.trim();if(tp!=='') cfg.summarizer.temperature=Number(tp);
|
|
2213
|
+
}
|
|
2214
|
+
cfg.skillEvolution={
|
|
2215
|
+
enabled:document.getElementById('cfgSkillEnabled').checked,
|
|
2216
|
+
autoInstall:document.getElementById('cfgSkillAutoInstall').checked
|
|
2217
|
+
};
|
|
2218
|
+
const mc=document.getElementById('cfgSkillConfidence').value.trim();if(mc) cfg.skillEvolution.minConfidence=Number(mc);
|
|
2219
|
+
const mk=document.getElementById('cfgSkillMinChunks').value.trim();if(mk) cfg.skillEvolution.minChunksForEval=Number(mk);
|
|
2220
|
+
|
|
2221
|
+
const skP=document.getElementById('cfgSkillProvider').value;
|
|
2222
|
+
if(skP){
|
|
2223
|
+
cfg.skillEvolution.summarizer={provider:skP};
|
|
2224
|
+
const v=document.getElementById('cfgSkillModel').value.trim();if(v) cfg.skillEvolution.summarizer.model=v;
|
|
2225
|
+
const e=document.getElementById('cfgSkillEndpoint').value.trim();if(e) cfg.skillEvolution.summarizer.endpoint=e;
|
|
2226
|
+
const k=document.getElementById('cfgSkillApiKey').value.trim();if(k) cfg.skillEvolution.summarizer.apiKey=k;
|
|
2227
|
+
const tp=document.getElementById('cfgSkillTemp').value.trim();if(tp!=='') cfg.skillEvolution.summarizer.temperature=Number(tp);
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
const vp=document.getElementById('cfgViewerPort').value.trim();
|
|
2231
|
+
if(vp) cfg.viewerPort=Number(vp);
|
|
2232
|
+
|
|
2233
|
+
try{
|
|
2234
|
+
const r=await fetch('/api/config',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(cfg)});
|
|
2235
|
+
if(!r.ok) throw new Error(await r.text());
|
|
2236
|
+
const el=document.getElementById('settingsSaved');
|
|
2237
|
+
el.classList.add('show');
|
|
2238
|
+
setTimeout(()=>el.classList.remove('show'),2500);
|
|
2239
|
+
}catch(e){
|
|
2240
|
+
showToast(t('settings.save.fail')+': '+e.message,'error');
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
function renderSkillMarkdown(md){
|
|
2245
|
+
let content=md;
|
|
2246
|
+
// Strip YAML frontmatter
|
|
2247
|
+
content=content.replace(/^---[\\s\\S]*?---\\s*/,'');
|
|
2248
|
+
// Code blocks
|
|
2249
|
+
content=content.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g,function(_,lang,code){
|
|
2250
|
+
return '<pre style="background:rgba(0,0,0,.3);border:1px solid var(--border);border-radius:8px;padding:12px 16px;overflow-x:auto;font-size:12px;line-height:1.5;font-family:SF Mono,Monaco,Consolas,monospace"><code>'+esc(code.trim())+'</code></pre>';
|
|
2251
|
+
});
|
|
2252
|
+
// Inline code
|
|
2253
|
+
content=content.replace(/\`([^\`]+)\`/g,'<code style="background:rgba(139,92,246,.1);color:var(--violet);padding:1px 6px;border-radius:4px;font-size:12px">$1</code>');
|
|
2254
|
+
// Headers
|
|
2255
|
+
content=content.replace(/^### (.+)$/gm,'<div class="summary-section-title" style="font-size:13px;margin-top:12px">$1</div>');
|
|
2256
|
+
content=content.replace(/^## (.+)$/gm,'<div class="summary-section-title">$1</div>');
|
|
2257
|
+
content=content.replace(/^# (.+)$/gm,'<div style="font-size:16px;font-weight:700;color:var(--text);margin:8px 0">$1</div>');
|
|
2258
|
+
// Bold
|
|
2259
|
+
content=content.replace(/\\*\\*(.+?)\\*\\*/g,'<strong>$1</strong>');
|
|
2260
|
+
// List items
|
|
2261
|
+
content=content.replace(/^- (.+)$/gm,'<div style="padding-left:16px;position:relative;margin:3px 0"><span style="position:absolute;left:4px;color:var(--text-muted)">•</span>$1</div>');
|
|
2262
|
+
// HTML comments (version markers)
|
|
2263
|
+
content=content.replace(/<!--[\\s\\S]*?-->/g,'');
|
|
2264
|
+
// Line breaks
|
|
2265
|
+
content=content.replace(/\\n\\n/g,'<div style="height:10px"></div>');
|
|
2266
|
+
content=content.replace(/\\n/g,'<br>');
|
|
2267
|
+
return content;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
function closeSkillDetail(event){
|
|
2271
|
+
if(event && event.target!==document.getElementById('skillDetailOverlay')) return;
|
|
2272
|
+
document.getElementById('skillDetailOverlay').classList.remove('show');
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
function formatDuration(ms){
|
|
2276
|
+
const s=Math.floor(ms/1000);
|
|
2277
|
+
if(s<60) return s+'s';
|
|
2278
|
+
const m=Math.floor(s/60);
|
|
2279
|
+
if(m<60) return m+'min';
|
|
2280
|
+
const h=Math.floor(m/60);
|
|
2281
|
+
if(h<24) return h+'h '+((m%60)>0?(m%60)+'min':'');
|
|
2282
|
+
const d=Math.floor(h/24);
|
|
2283
|
+
return d+'d '+((h%24)>0?(h%24)+'h':'');
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
function formatTime(ts){
|
|
2287
|
+
if(!ts) return '-';
|
|
2288
|
+
return new Date(ts).toLocaleString('zh-CN',{month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'});
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
function fillDays(rows,days){
|
|
2292
|
+
const map=new Map((rows||[]).map(r=>[r.date,{...r}]));
|
|
2293
|
+
const out=[];const now=new Date();
|
|
2294
|
+
for(let i=days-1;i>=0;i--){
|
|
2295
|
+
const d=new Date(now);d.setDate(d.getDate()-i);
|
|
2296
|
+
const dateStr=d.toISOString().slice(0,10);
|
|
2297
|
+
const row=map.get(dateStr)||{};
|
|
2298
|
+
out.push({date:dateStr,count:row.count??0,list:row.list??0,search:row.search??0,total:(row.list??0)+(row.search??0)});
|
|
2299
|
+
}
|
|
2300
|
+
if(days>21){
|
|
2301
|
+
const weeks=[];let i=0;
|
|
2302
|
+
while(i<out.length){
|
|
2303
|
+
const chunk=out.slice(i,i+7);
|
|
2304
|
+
const first=chunk[0].date,last=chunk[chunk.length-1].date;
|
|
2305
|
+
const c=chunk.reduce((s,r)=>s+r.count,0);
|
|
2306
|
+
const l=chunk.reduce((s,r)=>s+r.list,0);
|
|
2307
|
+
const se=chunk.reduce((s,r)=>s+r.search,0);
|
|
2308
|
+
const label=first.slice(5,10)+'~'+last.slice(8,10);
|
|
2309
|
+
weeks.push({date:label,count:c,list:l,search:se,total:l+se});
|
|
2310
|
+
i+=7;
|
|
2311
|
+
}
|
|
2312
|
+
return weeks;
|
|
2313
|
+
}
|
|
2314
|
+
return out;
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
function renderBars(el,data,valueKey,H){
|
|
2318
|
+
const vals=data.map(d=>d[valueKey]??0);
|
|
2319
|
+
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;}
|
|
2320
|
+
const max=Math.max(1,...vals);
|
|
2321
|
+
const nonZero=vals.filter(v=>v>0).length;
|
|
2322
|
+
const barStyle=data.length<=7?'min-width:40px;max-width:120px':'';
|
|
2323
|
+
el.innerHTML=data.map(r=>{
|
|
2324
|
+
const v=r[valueKey]??0;
|
|
2325
|
+
const label=r.date.includes('~')?r.date:(r.date.length>5?r.date.slice(5):r.date);
|
|
2326
|
+
if(v===0){
|
|
2327
|
+
return '<div class="chart-bar-wrap" style="'+barStyle+'"><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>';
|
|
2328
|
+
}
|
|
2329
|
+
const h=Math.max(8,Math.round((v/max)*H));
|
|
2330
|
+
return '<div class="chart-bar-wrap" style="'+barStyle+'"><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>';
|
|
2331
|
+
}).join('');
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
function renderChartWrites(rows){
|
|
2335
|
+
const el=document.getElementById('chartWrites');
|
|
2336
|
+
const filled=fillDays(rows?.map(r=>({date:r.date,count:r.count})),metricsDays);
|
|
2337
|
+
renderBars(el,filled,'count',160);
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
function renderChartCalls(rows){
|
|
2341
|
+
const el=document.getElementById('chartCalls');
|
|
2342
|
+
const filled=fillDays(rows?.map(r=>({date:r.date,list:r.list,search:r.search})),metricsDays);
|
|
2343
|
+
const vals=filled.map(f=>f.total);
|
|
2344
|
+
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;}
|
|
2345
|
+
const max=Math.max(1,...vals);
|
|
2346
|
+
const H=160;
|
|
2347
|
+
el.innerHTML=filled.map(r=>{
|
|
2348
|
+
const label=r.date.includes('~')?r.date:(r.date.length>5?r.date.slice(5):r.date);
|
|
2349
|
+
if(r.total===0){
|
|
2350
|
+
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>';
|
|
2351
|
+
}
|
|
2352
|
+
const totalH=Math.max(8,Math.round((r.total/max)*H));
|
|
2353
|
+
const listH=r.list?Math.max(3,Math.round((r.list/r.total)*totalH)):0;
|
|
2354
|
+
const searchH=r.search?totalH-listH:0;
|
|
2355
|
+
const tip='List: '+r.list+', Search: '+r.search;
|
|
2356
|
+
let bars='';
|
|
2357
|
+
if(searchH>0) bars+='<div class="chart-bar violet" style="height:'+searchH+'px"></div>';
|
|
2358
|
+
if(listH>0) bars+='<div class="chart-bar" style="height:'+listH+'px"></div>';
|
|
2359
|
+
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>';
|
|
2360
|
+
}).join('');
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
/* ─── Tool Performance Chart ─── */
|
|
2364
|
+
let toolMinutes=60;
|
|
2365
|
+
const TOOL_COLORS=['#818cf8','#34d399','#fbbf24','#f87171','#38bdf8','#a78bfa','#fb923c'];
|
|
2366
|
+
|
|
2367
|
+
function setToolMinutes(m){
|
|
2368
|
+
toolMinutes=m;
|
|
2369
|
+
document.querySelectorAll('.tool-range').forEach(b=>{
|
|
2370
|
+
b.classList.toggle('active',Number(b.dataset.mins)===m);
|
|
2371
|
+
});
|
|
2372
|
+
loadToolMetrics();
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
async function loadToolMetrics(){
|
|
2376
|
+
try{
|
|
2377
|
+
const r=await fetch('/api/tool-metrics?minutes='+toolMinutes);
|
|
2378
|
+
if(!r.ok) return;
|
|
2379
|
+
const d=await r.json();
|
|
2380
|
+
if(d.error) return;
|
|
2381
|
+
renderToolChart(d);
|
|
2382
|
+
renderToolAgg(d);
|
|
2383
|
+
}catch(e){
|
|
2384
|
+
console.warn('loadToolMetrics error:',e);
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
function renderToolChart(data){
|
|
2389
|
+
const container=document.getElementById('toolChart');
|
|
2390
|
+
const legend=document.getElementById('toolLegend');
|
|
2391
|
+
const {tools,series}=data;
|
|
2392
|
+
|
|
2393
|
+
if(!series||series.length===0||tools.length===0){
|
|
2394
|
+
container.innerHTML='<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:12px;color:var(--text-muted)"><div style="font-size:36px;opacity:.25">\u{1F4CA}</div><div style="font-size:13px;font-weight:500">Waiting for tool calls...</div><div style="font-size:11px;opacity:.6">Charts will render once the agent uses memory tools</div></div>';
|
|
2395
|
+
legend.innerHTML='';
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
const W=container.clientWidth||800;
|
|
2400
|
+
const H=280;
|
|
2401
|
+
const pad={t:20,r:20,b:36,l:52};
|
|
2402
|
+
const cw=W-pad.l-pad.r;
|
|
2403
|
+
const ch=H-pad.t-pad.b;
|
|
2404
|
+
|
|
2405
|
+
let maxVal=0;
|
|
2406
|
+
for(const s of series){for(const t of tools){const v=s[t]||0;if(v>maxVal)maxVal=v;}}
|
|
2407
|
+
if(maxVal===0)maxVal=100;
|
|
2408
|
+
maxVal=Math.ceil(maxVal*1.15);
|
|
2409
|
+
|
|
2410
|
+
const gridLines=5;
|
|
2411
|
+
let gridHtml='';
|
|
2412
|
+
for(let i=0;i<=gridLines;i++){
|
|
2413
|
+
const y=pad.t+ch-(ch/gridLines)*i;
|
|
2414
|
+
const val=Math.round((maxVal/gridLines)*i);
|
|
2415
|
+
gridHtml+='<line class="grid-line" x1="'+pad.l+'" y1="'+y+'" x2="'+(W-pad.r)+'" y2="'+y+'"/>';
|
|
2416
|
+
gridHtml+='<text class="axis-label" x="'+(pad.l-8)+'" y="'+(y+3)+'" text-anchor="end">'+val+'ms</text>';
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
const step=cw/(series.length-1||1);
|
|
2420
|
+
const labelEvery=Math.max(1,Math.floor(series.length/8));
|
|
2421
|
+
let labelsHtml='';
|
|
2422
|
+
series.forEach((s,i)=>{
|
|
2423
|
+
if(i%labelEvery===0||i===series.length-1){
|
|
2424
|
+
const x=pad.l+i*step;
|
|
2425
|
+
const time=s.minute.slice(11);
|
|
2426
|
+
labelsHtml+='<text class="axis-label" x="'+x+'" y="'+(H-4)+'" text-anchor="middle">'+time+'</text>';
|
|
2427
|
+
}
|
|
2428
|
+
});
|
|
2429
|
+
|
|
2430
|
+
let pathsHtml='';
|
|
2431
|
+
let dotsHtml='';
|
|
2432
|
+
tools.forEach((toolName,ti)=>{
|
|
2433
|
+
const color=TOOL_COLORS[ti%TOOL_COLORS.length];
|
|
2434
|
+
const pts=series.map((s,i)=>{
|
|
2435
|
+
const x=pad.l+i*step;
|
|
2436
|
+
const v=s[toolName]||0;
|
|
2437
|
+
const y=pad.t+ch-((v/maxVal)*ch);
|
|
2438
|
+
return {x,y,v};
|
|
2439
|
+
});
|
|
2440
|
+
let line='M'+pts[0].x.toFixed(1)+' '+pts[0].y.toFixed(1);
|
|
2441
|
+
for(let i=1;i<pts.length;i++){
|
|
2442
|
+
const p0=pts[Math.max(0,i-2)],p1=pts[i-1],p2=pts[i],p3=pts[Math.min(pts.length-1,i+1)];
|
|
2443
|
+
const cp1x=(p1.x+(p2.x-p0.x)/6).toFixed(1),cp1y=(p1.y+(p2.y-p0.y)/6).toFixed(1);
|
|
2444
|
+
const cp2x=(p2.x-(p3.x-p1.x)/6).toFixed(1),cp2y=(p2.y-(p3.y-p1.y)/6).toFixed(1);
|
|
2445
|
+
line+=' C'+cp1x+' '+cp1y+','+cp2x+' '+cp2y+','+p2.x.toFixed(1)+' '+p2.y.toFixed(1);
|
|
2446
|
+
}
|
|
2447
|
+
pathsHtml+='<path class="data-line" d="'+line+'" stroke="'+color+'" />';
|
|
2448
|
+
const area=line+' L'+pts[pts.length-1].x.toFixed(1)+' '+(pad.t+ch)+' L'+pts[0].x.toFixed(1)+' '+(pad.t+ch)+' Z';
|
|
2449
|
+
pathsHtml+='<path class="data-area" d="'+area+'" fill="url(#tg'+ti+')" />';
|
|
2450
|
+
pts.forEach((p,i)=>{
|
|
2451
|
+
dotsHtml+='<circle class="hover-dot" cx="'+p.x.toFixed(1)+'" cy="'+p.y.toFixed(1)+'" fill="'+color+'" data-tool="'+toolName+'" data-idx="'+i+'" data-val="'+p.v+'" />';
|
|
2452
|
+
});
|
|
2453
|
+
});
|
|
2454
|
+
|
|
2455
|
+
const svg='<svg class="tool-chart-svg" viewBox="0 0 '+W+' '+H+'" preserveAspectRatio="xMidYMid meet">'+
|
|
2456
|
+
'<defs>'+
|
|
2457
|
+
tools.map((t,i)=>{
|
|
2458
|
+
const c=TOOL_COLORS[i%TOOL_COLORS.length];
|
|
2459
|
+
return '<linearGradient id="tg'+i+'" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="'+c+'" stop-opacity=".08"/><stop offset="1" stop-color="'+c+'" stop-opacity="0"/></linearGradient>'+
|
|
2460
|
+
'';
|
|
2461
|
+
}).join('')+'</defs>'+
|
|
2462
|
+
|
|
2463
|
+
gridHtml+labelsHtml+pathsHtml+dotsHtml+
|
|
2464
|
+
'<line class="crosshair" x1="0" y1="'+pad.t+'" x2="0" y2="'+(pad.t+ch)+'" stroke="var(--text-muted)" stroke-width="0.5" stroke-dasharray="3 3" opacity="0" />'+
|
|
2465
|
+
'<rect class="hover-rect" x="'+pad.l+'" y="'+pad.t+'" width="'+cw+'" height="'+ch+'" fill="transparent" />'+
|
|
2466
|
+
'</svg><div class="tool-chart-tooltip" id="toolTooltip"></div>';
|
|
2467
|
+
|
|
2468
|
+
container.innerHTML=svg;
|
|
2469
|
+
|
|
2470
|
+
legend.innerHTML=tools.map((t,i)=>{
|
|
2471
|
+
const c=TOOL_COLORS[i%TOOL_COLORS.length];
|
|
2472
|
+
return '<span><span class="dot" style="background:'+c+'"></span>'+t+'</span>';
|
|
2473
|
+
}).join('');
|
|
2474
|
+
|
|
2475
|
+
const svgEl=container.querySelector('svg');
|
|
2476
|
+
const tooltip=document.getElementById('toolTooltip');
|
|
2477
|
+
const rect=svgEl.querySelector('.hover-rect');
|
|
2478
|
+
|
|
2479
|
+
rect.addEventListener('mousemove',function(e){
|
|
2480
|
+
const r=svgEl.getBoundingClientRect();
|
|
2481
|
+
const mx=e.clientX-r.left;
|
|
2482
|
+
const scale=W/r.width;
|
|
2483
|
+
const dataX=(mx*scale-pad.l)/step;
|
|
2484
|
+
const idx=Math.max(0,Math.min(series.length-1,Math.round(dataX)));
|
|
2485
|
+
const s=series[idx];
|
|
2486
|
+
if(!s)return;
|
|
2487
|
+
|
|
2488
|
+
svgEl.querySelectorAll('.hover-dot').forEach(d=>{
|
|
2489
|
+
d.classList.toggle('show',Number(d.dataset.idx)===idx);
|
|
2490
|
+
});
|
|
2491
|
+
const crosshair=svgEl.querySelector('.crosshair');
|
|
2492
|
+
const cx=pad.l+idx*step;
|
|
2493
|
+
crosshair.setAttribute('x1',cx);crosshair.setAttribute('x2',cx);crosshair.setAttribute('opacity','0.5');
|
|
2494
|
+
|
|
2495
|
+
let rows='<div class="tt-time">'+s.minute+'</div>';
|
|
2496
|
+
tools.forEach((t,ti)=>{
|
|
2497
|
+
const v=s[t]||0;
|
|
2498
|
+
const c=TOOL_COLORS[ti%TOOL_COLORS.length];
|
|
2499
|
+
rows+='<div class="tt-row"><span class="tt-dot" style="background:'+c+'"></span>'+t+'<span class="tt-val">'+v+'ms</span></div>';
|
|
2500
|
+
});
|
|
2501
|
+
tooltip.innerHTML=rows;
|
|
2502
|
+
tooltip.classList.add('show');
|
|
2503
|
+
|
|
2504
|
+
const tx=e.clientX-container.getBoundingClientRect().left;
|
|
2505
|
+
const ty=e.clientY-container.getBoundingClientRect().top;
|
|
2506
|
+
tooltip.style.left=(tx+15)+'px';
|
|
2507
|
+
tooltip.style.top=(ty-10)+'px';
|
|
2508
|
+
if(tx>container.clientWidth*0.7) tooltip.style.left=(tx-tooltip.offsetWidth-15)+'px';
|
|
2509
|
+
});
|
|
2510
|
+
|
|
2511
|
+
rect.addEventListener('mouseleave',function(){
|
|
2512
|
+
svgEl.querySelectorAll('.hover-dot').forEach(d=>d.classList.remove('show'));
|
|
2513
|
+
svgEl.querySelector('.crosshair').setAttribute('opacity','0');
|
|
2514
|
+
tooltip.classList.remove('show');
|
|
2515
|
+
});
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
function renderToolAgg(data){
|
|
2519
|
+
const el=document.getElementById('toolAggTable');
|
|
2520
|
+
const {aggregated}=data;
|
|
2521
|
+
if(!aggregated||aggregated.length===0){el.innerHTML='';return;}
|
|
2522
|
+
|
|
2523
|
+
const msClass=v=>v<100?'fast':v<500?'medium':'slow';
|
|
2524
|
+
|
|
2525
|
+
el.innerHTML='<table class="tool-agg-table"><thead><tr><th>Tool</th><th>Calls</th><th>Avg</th><th>P95</th><th>Errors</th></tr></thead><tbody>'+
|
|
2526
|
+
aggregated.map((a,i)=>{
|
|
2527
|
+
const c=TOOL_COLORS[i%TOOL_COLORS.length];
|
|
2528
|
+
return '<tr>'+
|
|
2529
|
+
'<td><span class="tool-name"><span class="tool-dot" style="background:'+c+'"></span>'+a.tool+'</span></td>'+
|
|
2530
|
+
'<td>'+a.totalCalls+'</td>'+
|
|
2531
|
+
'<td><span class="ms-val '+msClass(a.avgMs)+'">'+a.avgMs+'ms</span></td>'+
|
|
2532
|
+
'<td><span class="ms-val '+msClass(a.p95Ms)+'">'+a.p95Ms+'ms</span></td>'+
|
|
2533
|
+
'<td>'+(a.errorCount>0?'<span style="color:var(--accent)">'+a.errorCount+'</span>':'<span style="color:var(--text-muted)">0</span>')+'</td>'+
|
|
2534
|
+
'</tr>';
|
|
2535
|
+
}).join('')+
|
|
2536
|
+
'</tbody></table>';
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
function renderBreakdown(obj,containerId){
|
|
2540
|
+
const el=document.getElementById(containerId);
|
|
2541
|
+
if(!el)return;
|
|
2542
|
+
const entries=Object.entries(obj||{}).sort((a,b)=>b[1]-a[1]);
|
|
2543
|
+
const total=entries.reduce((s,[,v])=>s+v,0)||1;
|
|
2544
|
+
el.innerHTML=entries.map(([label,value])=>{
|
|
2545
|
+
const pct=Math.round((value/total)*100);
|
|
2546
|
+
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>';
|
|
2547
|
+
}).join('');
|
|
2548
|
+
}
|
|
2549
|
+
|
|
457
2550
|
/* ─── Data loading ─── */
|
|
458
2551
|
async function loadAll(){
|
|
459
2552
|
await Promise.all([loadStats(),loadMemories()]);
|
|
@@ -462,7 +2555,13 @@ async function loadAll(){
|
|
|
462
2555
|
async function loadStats(){
|
|
463
2556
|
const r=await fetch('/api/stats');
|
|
464
2557
|
const d=await r.json();
|
|
2558
|
+
const dedupB=d.dedupBreakdown||{};
|
|
2559
|
+
const activeCount=dedupB.active||d.totalMemories;
|
|
2560
|
+
const inactiveCount=(dedupB.duplicate||0)+(dedupB.merged||0);
|
|
465
2561
|
document.getElementById('statTotal').textContent=d.totalMemories;
|
|
2562
|
+
if(inactiveCount>0){
|
|
2563
|
+
document.getElementById('statTotal').title=activeCount+' '+t('stat.active')+', '+inactiveCount+' '+t('stat.deduped');
|
|
2564
|
+
}
|
|
466
2565
|
document.getElementById('statSessions').textContent=d.totalSessions;
|
|
467
2566
|
document.getElementById('statEmbeddings').textContent=d.totalEmbeddings;
|
|
468
2567
|
const days=d.timeRange.earliest?Math.max(1,Math.round((new Date(d.timeRange.latest)-new Date(d.timeRange.earliest))/(86400000))):0;
|
|
@@ -470,13 +2569,13 @@ async function loadStats(){
|
|
|
470
2569
|
|
|
471
2570
|
const provEl=document.getElementById('embeddingStatus');
|
|
472
2571
|
if(d.embeddingProvider && d.embeddingProvider!=='none'){
|
|
473
|
-
provEl.innerHTML='<div class="provider-badge"><span>\\u2713</span>
|
|
2572
|
+
provEl.innerHTML='<div class="provider-badge"><span>\\u2713</span> '+t('embed.on')+d.embeddingProvider+'</div>';
|
|
474
2573
|
} else {
|
|
475
|
-
provEl.innerHTML='<div class="provider-badge offline"><span>\\u26A0</span>
|
|
2574
|
+
provEl.innerHTML='<div class="provider-badge offline"><span>\\u26A0</span> '+t('embed.off')+'</div>';
|
|
476
2575
|
}
|
|
477
2576
|
|
|
478
2577
|
const sl=document.getElementById('sessionList');
|
|
479
|
-
sl.innerHTML='<div class="session-item'+(activeSession===null?' active':'')+'" onclick="filterSession(null)"><span>
|
|
2578
|
+
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
2579
|
(d.sessions||[]).forEach(s=>{
|
|
481
2580
|
const isActive=activeSession===s.session_key;
|
|
482
2581
|
const name=s.session_key.length>20?s.session_key.slice(0,8)+'...'+s.session_key.slice(-8):s.session_key;
|
|
@@ -510,7 +2609,7 @@ async function loadMemories(page){
|
|
|
510
2609
|
const d=await r.json();
|
|
511
2610
|
totalPages=d.totalPages||1;
|
|
512
2611
|
totalCount=d.total||0;
|
|
513
|
-
document.getElementById('searchMeta').textContent=totalCount+'
|
|
2612
|
+
document.getElementById('searchMeta').textContent=totalCount+t('search.meta.total');
|
|
514
2613
|
renderMemories(d.memories||[]);
|
|
515
2614
|
renderPagination();
|
|
516
2615
|
}
|
|
@@ -524,9 +2623,9 @@ async function doSearch(q){
|
|
|
524
2623
|
const r=await fetch('/api/search?'+p.toString());
|
|
525
2624
|
const d=await r.json();
|
|
526
2625
|
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+'
|
|
2626
|
+
if(d.vectorCount>0) meta.push(d.vectorCount+t('search.meta.semantic'));
|
|
2627
|
+
if(d.ftsCount>0) meta.push(d.ftsCount+t('search.meta.text'));
|
|
2628
|
+
meta.push(d.total+t('search.meta.results'));
|
|
530
2629
|
document.getElementById('searchMeta').textContent=meta.join(' \\u00B7 ');
|
|
531
2630
|
renderMemories(d.results||[]);
|
|
532
2631
|
document.getElementById('pagination').innerHTML='';
|
|
@@ -570,7 +2669,7 @@ function clearDateFilter(){
|
|
|
570
2669
|
function renderMemories(items){
|
|
571
2670
|
const list=document.getElementById('memoryList');
|
|
572
2671
|
if(!items.length){
|
|
573
|
-
list.innerHTML='<div class="empty"><div class="icon">\\u{1F4ED}</div><p>
|
|
2672
|
+
list.innerHTML='<div class="empty"><div class="icon">\\u{1F4ED}</div><p>'+t('empty.text')+'</p></div>';
|
|
574
2673
|
return;
|
|
575
2674
|
}
|
|
576
2675
|
items.forEach(m=>{memoryCache[m.id]=m});
|
|
@@ -584,14 +2683,46 @@ function renderMemories(items){
|
|
|
584
2683
|
const vscore=m._vscore?'<span class="vscore-badge">'+Math.round(m._vscore*100)+'%</span>':'';
|
|
585
2684
|
const sid=m.session_key||'';
|
|
586
2685
|
const sidShort=sid.length>18?sid.slice(0,6)+'..'+sid.slice(-6):sid;
|
|
587
|
-
|
|
588
|
-
|
|
2686
|
+
const mc=m.merge_count||0;
|
|
2687
|
+
const mergeBadge=mc>0?'<span class="merge-badge">\\u{1F504} '+t('card.evolved')+' '+mc+t('card.times')+'</span>':'';
|
|
2688
|
+
const updatedAt=(m.updated_at&&m.updated_at>m.created_at)?'<span class="card-updated">'+t('card.updated')+' '+new Date(m.updated_at).toLocaleString('zh-CN')+'</span>':'';
|
|
2689
|
+
const ds=m.dedup_status||'active';
|
|
2690
|
+
const isInactive=ds==='duplicate'||ds==='merged';
|
|
2691
|
+
const dedupBadge=ds==='duplicate'?'<span class="dedup-badge duplicate">'+t('card.dedupDuplicate')+'</span>':ds==='merged'?'<span class="dedup-badge merged">'+t('card.dedupMerged')+'</span>':'';
|
|
2692
|
+
let dedupInfo='';
|
|
2693
|
+
if(isInactive){
|
|
2694
|
+
const reason=m.dedup_reason?'<span style="font-size:11px;color:var(--text-muted)">'+t('card.dedupReason')+esc(m.dedup_reason)+'</span>':'';
|
|
2695
|
+
const target=m.dedup_target?'<span class="dedup-target-link" onclick="scrollToMemory(\\''+m.dedup_target+'\\')">'+t('card.dedupTarget')+m.dedup_target.slice(0,8)+'...</span>':'';
|
|
2696
|
+
dedupInfo='<div style="margin-top:6px;font-size:11px">'+target+' '+reason+'</div>';
|
|
2697
|
+
}
|
|
2698
|
+
let historyHtml='';
|
|
2699
|
+
if(mc>0){
|
|
2700
|
+
try{
|
|
2701
|
+
const hist=JSON.parse(m.merge_history||'[]');
|
|
2702
|
+
if(hist.length>0){
|
|
2703
|
+
historyHtml='<div class="merge-history" id="history-'+id+'" style="display:none"><div style="font-weight:600;margin-bottom:8px;font-size:12px">'+t('card.evolveHistory')+' ('+hist.length+')</div>';
|
|
2704
|
+
hist.forEach(function(h){
|
|
2705
|
+
const ht=h.at?new Date(h.at).toLocaleString('zh-CN'):'';
|
|
2706
|
+
historyHtml+='<div class="merge-history-item"><span class="merge-action '+h.action+'">'+h.action+'</span> <span style="color:var(--text-muted)">'+ht+'</span><br>'+esc(h.reason||'');
|
|
2707
|
+
if(h.from) historyHtml+='<br><span style="opacity:.6">'+t('card.oldSummary')+':</span> '+esc(h.from);
|
|
2708
|
+
if(h.to) historyHtml+='<br><span style="opacity:.6">'+t('card.newSummary')+':</span> '+esc(h.to);
|
|
2709
|
+
historyHtml+='</div>';
|
|
2710
|
+
});
|
|
2711
|
+
historyHtml+='</div>';
|
|
2712
|
+
}
|
|
2713
|
+
}catch(e){}
|
|
2714
|
+
}
|
|
2715
|
+
return '<div class="memory-card'+(isInactive?' dedup-inactive':'')+'">'+
|
|
2716
|
+
'<div class="card-header"><div class="meta"><span class="role-tag '+role+'">'+role+'</span><span class="kind-tag">'+kind+'</span>'+dedupBadge+mergeBadge+'</div><span class="card-time"><span class="session-tag" title="'+esc(sid)+'">'+esc(sidShort)+'</span> '+time+updatedAt+'</span></div>'+
|
|
589
2717
|
'<div class="card-summary">'+summary+'</div>'+
|
|
2718
|
+
dedupInfo+
|
|
590
2719
|
'<div class="card-content" id="content-'+id+'"><pre>'+content+'</pre></div>'+
|
|
2720
|
+
historyHtml+
|
|
591
2721
|
'<div class="card-actions">'+
|
|
592
|
-
'<button class="btn btn-sm btn-
|
|
593
|
-
'<button class="btn btn-sm" onclick="
|
|
594
|
-
'<button class="btn btn-sm btn-
|
|
2722
|
+
'<button class="btn btn-sm btn-ghost" onclick="toggleContent(\\''+id+'\\')">'+t('card.expand')+'</button>'+
|
|
2723
|
+
(mc>0?'<button class="btn btn-sm btn-ghost" onclick="toggleHistory(\\''+id+'\\')">'+t('card.evolveHistory')+'</button>':'')+
|
|
2724
|
+
'<button class="btn btn-sm btn-ghost" onclick="openEditModal(\\''+id+'\\')">'+t('card.edit')+'</button>'+
|
|
2725
|
+
'<button class="btn btn-sm btn-ghost" style="color:var(--accent)" onclick="deleteMemory(\\''+id+'\\')">'+t('card.delete')+'</button>'+
|
|
595
2726
|
vscore+
|
|
596
2727
|
'</div></div>';
|
|
597
2728
|
}).join('');
|
|
@@ -614,7 +2745,7 @@ function renderPagination(){
|
|
|
614
2745
|
prev=p;
|
|
615
2746
|
}
|
|
616
2747
|
h+='<button class="pg-btn'+(currentPage>=totalPages?' disabled':'')+'" onclick="goPage('+(currentPage+1)+')">\u203A</button>';
|
|
617
|
-
h+='<span class="pg-info">'+totalCount+'
|
|
2748
|
+
h+='<span class="pg-info">'+totalCount+t('pagination.total')+'</span>';
|
|
618
2749
|
el.innerHTML=h;
|
|
619
2750
|
}
|
|
620
2751
|
|
|
@@ -625,21 +2756,109 @@ function goPage(p){
|
|
|
625
2756
|
document.getElementById('memoryList').scrollIntoView({behavior:'smooth',block:'start'});
|
|
626
2757
|
}
|
|
627
2758
|
|
|
2759
|
+
function toggleHistory(id){
|
|
2760
|
+
const el=document.getElementById('history-'+id);
|
|
2761
|
+
if(el) el.style.display=el.style.display==='none'?'block':'none';
|
|
2762
|
+
}
|
|
2763
|
+
|
|
628
2764
|
function toggleContent(id){
|
|
629
2765
|
const el=document.getElementById('content-'+id);
|
|
630
2766
|
el.classList.toggle('show');
|
|
631
2767
|
}
|
|
632
2768
|
|
|
2769
|
+
function scrollToMemory(targetId){
|
|
2770
|
+
const cards=document.querySelectorAll('.memory-card');
|
|
2771
|
+
for(const card of cards){
|
|
2772
|
+
const contentEl=card.querySelector('[id^="content-"]');
|
|
2773
|
+
if(contentEl&&contentEl.id==='content-'+targetId){
|
|
2774
|
+
card.scrollIntoView({behavior:'smooth',block:'center'});
|
|
2775
|
+
card.style.transition='box-shadow .3s';
|
|
2776
|
+
card.style.boxShadow='0 0 0 2px var(--pri)';
|
|
2777
|
+
setTimeout(()=>{card.style.boxShadow='';},2000);
|
|
2778
|
+
return;
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
showMemoryModal(targetId);
|
|
2782
|
+
}
|
|
2783
|
+
async function showMemoryModal(chunkId){
|
|
2784
|
+
const overlay=document.getElementById('memoryModal');
|
|
2785
|
+
const body=document.getElementById('memoryModalBody');
|
|
2786
|
+
body.innerHTML='<div style="text-align:center;padding:40px;color:var(--text-sec)">Loading...</div>';
|
|
2787
|
+
overlay.classList.add('show');
|
|
2788
|
+
try{
|
|
2789
|
+
const res=await fetch('/api/memory/'+encodeURIComponent(chunkId));
|
|
2790
|
+
if(!res.ok){body.innerHTML='<div style="text-align:center;padding:40px;color:#f87171">Memory not found</div>';return;}
|
|
2791
|
+
const data=await res.json();
|
|
2792
|
+
const m=data.memory;
|
|
2793
|
+
const role=(m.role||'unknown').toUpperCase();
|
|
2794
|
+
const roleCls=(m.role||'').toLowerCase();
|
|
2795
|
+
const kind=m.kind||'paragraph';
|
|
2796
|
+
const ds=m.dedup_status||'active';
|
|
2797
|
+
const time=new Date(m.created_at).toLocaleString('zh-CN');
|
|
2798
|
+
const updated=m.updated_at?new Date(m.updated_at).toLocaleString('zh-CN'):'';
|
|
2799
|
+
let html='<div class="modal-memory-card">';
|
|
2800
|
+
html+='<div class="modal-header-row"><span class="role-tag '+roleCls+'">'+role+'</span><span class="kind-tag">'+kind+'</span>';
|
|
2801
|
+
if(ds!=='active') html+='<span class="dedup-badge '+(ds==='duplicate'?'duplicate':'merged')+'">'+ds+'</span>';
|
|
2802
|
+
html+='</div>';
|
|
2803
|
+
html+='<div class="modal-field"><div class="modal-field-label">ID</div><div class="modal-field-val" style="font-family:monospace;font-size:11px">'+esc(m.id)+'</div></div>';
|
|
2804
|
+
html+='<div class="modal-field"><div class="modal-field-label">Summary</div><div class="modal-field-val" style="font-size:14px;font-weight:600">'+esc(m.summary||'')+'</div></div>';
|
|
2805
|
+
html+='<div class="modal-field"><div class="modal-field-label">Content</div><pre class="modal-field-content">'+esc(m.content||'')+'</pre></div>';
|
|
2806
|
+
html+='<div class="modal-meta-row">';
|
|
2807
|
+
html+='<span><strong>Session:</strong> '+esc(m.session_key||'')+'</span>';
|
|
2808
|
+
html+='<span><strong>Created:</strong> '+time+'</span>';
|
|
2809
|
+
if(updated) html+='<span><strong>Updated:</strong> '+updated+'</span>';
|
|
2810
|
+
html+='</div>';
|
|
2811
|
+
if(m.dedup_reason) html+='<div class="modal-field"><div class="modal-field-label">Dedup Reason</div><div class="modal-field-val">'+esc(m.dedup_reason)+'</div></div>';
|
|
2812
|
+
if(m.dedup_target&&m.dedup_target!==chunkId) html+='<div class="modal-field"><span class="dedup-target-link" onclick="closeMemoryModal();scrollToMemory(\\''+m.dedup_target+'\\')">View target: '+m.dedup_target.slice(0,8)+'...</span></div>';
|
|
2813
|
+
html+='</div>';
|
|
2814
|
+
body.innerHTML=html;
|
|
2815
|
+
}catch(e){body.innerHTML='<div style="text-align:center;padding:40px;color:#f87171">Error: '+esc(String(e))+'</div>';}
|
|
2816
|
+
}
|
|
2817
|
+
function closeMemoryModal(){document.getElementById('memoryModal').classList.remove('show');}
|
|
2818
|
+
|
|
2819
|
+
|
|
633
2820
|
function esc(s){
|
|
634
2821
|
if(!s)return'';
|
|
635
2822
|
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
636
2823
|
}
|
|
637
2824
|
|
|
2825
|
+
function renderSummaryHtml(raw){
|
|
2826
|
+
if(!raw)return'';
|
|
2827
|
+
var lines=raw.split('\\n');
|
|
2828
|
+
var html=[];
|
|
2829
|
+
var inList=false;
|
|
2830
|
+
var sectionRe=new RegExp('^(\u{1F3AF}|\u{1F4CB}|\u2705|\u{1F4A1})\\\\s+(.+)$');
|
|
2831
|
+
var listRe=new RegExp('^- (.+)$');
|
|
2832
|
+
for(var i=0;i<lines.length;i++){
|
|
2833
|
+
var line=lines[i];
|
|
2834
|
+
var hm=line.match(sectionRe);
|
|
2835
|
+
if(hm){
|
|
2836
|
+
if(inList){html.push('</ul>');inList=false;}
|
|
2837
|
+
html.push('<div class="summary-section-title">'+esc(line)+'</div>');
|
|
2838
|
+
continue;
|
|
2839
|
+
}
|
|
2840
|
+
var lm=line.match(listRe);
|
|
2841
|
+
if(lm){
|
|
2842
|
+
if(!inList){html.push('<ul>');inList=true;}
|
|
2843
|
+
html.push('<li>'+esc(lm[1])+'</li>');
|
|
2844
|
+
continue;
|
|
2845
|
+
}
|
|
2846
|
+
if(line.trim()===''){
|
|
2847
|
+
if(inList){html.push('</ul>');inList=false;}
|
|
2848
|
+
continue;
|
|
2849
|
+
}
|
|
2850
|
+
if(inList){html.push('</ul>');inList=false;}
|
|
2851
|
+
html.push('<p style="margin:4px 0">'+esc(line)+'</p>');
|
|
2852
|
+
}
|
|
2853
|
+
if(inList)html.push('</ul>');
|
|
2854
|
+
return html.join('');
|
|
2855
|
+
}
|
|
2856
|
+
|
|
638
2857
|
/* ─── CRUD ─── */
|
|
639
2858
|
function openCreateModal(){
|
|
640
2859
|
editingId=null;
|
|
641
|
-
document.getElementById('modalTitle').textContent='
|
|
642
|
-
document.getElementById('modalSubmit').textContent='
|
|
2860
|
+
document.getElementById('modalTitle').textContent=t('modal.new');
|
|
2861
|
+
document.getElementById('modalSubmit').textContent=t('modal.create');
|
|
643
2862
|
document.getElementById('mRole').value='user';
|
|
644
2863
|
document.getElementById('mContent').value='';
|
|
645
2864
|
document.getElementById('mSummary').value='';
|
|
@@ -649,10 +2868,10 @@ function openCreateModal(){
|
|
|
649
2868
|
|
|
650
2869
|
function openEditModal(id){
|
|
651
2870
|
const m=memoryCache[id];
|
|
652
|
-
if(!m){toast('
|
|
2871
|
+
if(!m){toast(t('toast.notfound'),'error');return}
|
|
653
2872
|
editingId=id;
|
|
654
|
-
document.getElementById('modalTitle').textContent='
|
|
655
|
-
document.getElementById('modalSubmit').textContent='
|
|
2873
|
+
document.getElementById('modalTitle').textContent=t('modal.edit');
|
|
2874
|
+
document.getElementById('modalSubmit').textContent=t('modal.save');
|
|
656
2875
|
document.getElementById('mRole').value=m.role||'user';
|
|
657
2876
|
document.getElementById('mContent').value=m.content||'';
|
|
658
2877
|
document.getElementById('mSummary').value=m.summary||'';
|
|
@@ -671,7 +2890,7 @@ async function submitModal(){
|
|
|
671
2890
|
summary:document.getElementById('mSummary').value,
|
|
672
2891
|
kind:document.getElementById('mKind').value,
|
|
673
2892
|
};
|
|
674
|
-
if(!data.content.trim()){toast('
|
|
2893
|
+
if(!data.content.trim()){toast(t('modal.err.empty'),'error');return}
|
|
675
2894
|
let r;
|
|
676
2895
|
if(editingId){
|
|
677
2896
|
r=await fetch('/api/memory/'+editingId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
|
|
@@ -679,25 +2898,25 @@ async function submitModal(){
|
|
|
679
2898
|
r=await fetch('/api/memory',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
|
|
680
2899
|
}
|
|
681
2900
|
const d=await r.json();
|
|
682
|
-
if(d.ok){toast(editingId?'
|
|
683
|
-
else{toast(d.error||'
|
|
2901
|
+
if(d.ok){toast(editingId?t('toast.updated'):t('toast.created'),'success');closeModal();loadAll();}
|
|
2902
|
+
else{toast(d.error||t('toast.opfail'),'error')}
|
|
684
2903
|
}
|
|
685
2904
|
|
|
686
2905
|
async function deleteMemory(id){
|
|
687
|
-
if(!confirm('
|
|
2906
|
+
if(!confirm(t('confirm.delete')))return;
|
|
688
2907
|
const r=await fetch('/api/memory/'+id,{method:'DELETE'});
|
|
689
2908
|
const d=await r.json();
|
|
690
|
-
if(d.ok){toast('
|
|
691
|
-
else{toast('
|
|
2909
|
+
if(d.ok){toast(t('toast.deleted'),'success');loadAll();}
|
|
2910
|
+
else{toast(t('toast.delfail'),'error')}
|
|
692
2911
|
}
|
|
693
2912
|
|
|
694
2913
|
async function clearAll(){
|
|
695
|
-
if(!confirm('
|
|
696
|
-
if(!confirm('
|
|
2914
|
+
if(!confirm(t('confirm.clearall')))return;
|
|
2915
|
+
if(!confirm(t('confirm.clearall2')))return;
|
|
697
2916
|
const r=await fetch('/api/memories',{method:'DELETE'});
|
|
698
2917
|
const d=await r.json();
|
|
699
|
-
if(d.ok){toast('
|
|
700
|
-
else{toast('
|
|
2918
|
+
if(d.ok){toast(t('toast.cleared'),'success');loadAll();}
|
|
2919
|
+
else{toast(t('toast.clearfail'),'error')}
|
|
701
2920
|
}
|
|
702
2921
|
|
|
703
2922
|
/* ─── Toast ─── */
|
|
@@ -720,7 +2939,20 @@ initViewerTheme();
|
|
|
720
2939
|
/* ─── Init ─── */
|
|
721
2940
|
document.getElementById('modalOverlay').addEventListener('click',e=>{if(e.target.id==='modalOverlay')closeModal()});
|
|
722
2941
|
document.getElementById('searchInput').addEventListener('keydown',e=>{if(e.key==='Escape'){e.target.value='';loadMemories()}});
|
|
2942
|
+
applyI18n();
|
|
723
2943
|
checkAuth();
|
|
724
2944
|
</script>
|
|
2945
|
+
|
|
2946
|
+
<!-- Memory Detail Modal -->
|
|
2947
|
+
<div class="memory-modal-overlay" id="memoryModal" onclick="if(event.target===this)closeMemoryModal()">
|
|
2948
|
+
<div class="memory-modal">
|
|
2949
|
+
<div class="memory-modal-title">
|
|
2950
|
+
<span>Memory Detail</span>
|
|
2951
|
+
<button class="btn btn-sm btn-ghost" onclick="closeMemoryModal()" style="font-size:16px;padding:2px 8px">×</button>
|
|
2952
|
+
</div>
|
|
2953
|
+
<div class="memory-modal-body" id="memoryModalBody"></div>
|
|
2954
|
+
</div>
|
|
2955
|
+
</div>
|
|
2956
|
+
|
|
725
2957
|
</body>
|
|
726
2958
|
</html>`;
|