@memtensor/memos-local-openclaw-plugin 1.0.9-beta.2 → 1.0.9

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.
@@ -0,0 +1,1631 @@
1
+ export function viewerHTMLv2(pluginVersion?: string): string {
2
+ const vBadge = pluginVersion || '';
3
+ return `<!DOCTYPE html>
4
+ <html lang="zh-CN">
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
8
+ <title>MemOS Viewer</title>
9
+ <link rel="icon" href="https://statics.memtensor.com.cn/logo/color-m.svg" type="image/svg+xml">
10
+ <link rel="preconnect" href="https://fonts.googleapis.com">
11
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
13
+ <style>
14
+ *{margin:0;padding:0;box-sizing:border-box}
15
+ html{overflow-y:scroll;scroll-behavior:smooth}
16
+ :root{
17
+ --bg:#0f1117;--bg-card:#181b23;--bg-card-hover:#1e2230;--bg-sidebar:#14161e;
18
+ --border:rgba(255,255,255,.07);--border-hover:rgba(255,255,255,.14);
19
+ --text:#e4e6eb;--text-sec:#8b8fa4;--text-muted:#555a6e;
20
+ --pri:#7c8cf5;--pri-dim:rgba(124,140,245,.12);--pri-dark:#5b6be0;
21
+ --green:#34d399;--green-bg:rgba(52,211,153,.1);
22
+ --amber:#fbbf24;--amber-bg:rgba(251,191,36,.1);
23
+ --red:#f87171;--red-bg:rgba(248,113,113,.1);
24
+ --cyan:#22d3ee;--cyan-bg:rgba(34,211,238,.1);
25
+ --violet:#a78bfa;--violet-bg:rgba(167,139,250,.1);
26
+ --rose:#fb7185;--rose-bg:rgba(251,113,133,.1);
27
+ --shadow:0 4px 16px rgba(0,0,0,.35);--shadow-sm:0 1px 3px rgba(0,0,0,.25);
28
+ --radius:12px;--radius-sm:8px;--radius-xs:6px;
29
+ --sidebar-w:220px;--sidebar-collapsed:56px;--topbar-h:48px;
30
+ --font:'Inter',system-ui,-apple-system,sans-serif;
31
+ --mono:'SF Mono','JetBrains Mono',ui-monospace,monospace;
32
+ }
33
+ [data-theme="light"]{
34
+ --bg:#f5f6fa;--bg-card:#fff;--bg-card-hover:#f0f1f5;--bg-sidebar:#fff;
35
+ --border:rgba(0,0,0,.08);--border-hover:rgba(0,0,0,.15);
36
+ --text:#1a1d2e;--text-sec:#5a5f76;--text-muted:#9ca3af;
37
+ --pri:#5b6be0;--pri-dim:rgba(91,107,224,.08);--pri-dark:#4a58c9;
38
+ --green:#059669;--green-bg:rgba(5,150,105,.06);
39
+ --amber:#d97706;--amber-bg:rgba(217,119,6,.06);
40
+ --red:#dc2626;--red-bg:rgba(220,38,38,.06);
41
+ --cyan:#0891b2;--cyan-bg:rgba(8,145,178,.06);
42
+ --violet:#7c3aed;--violet-bg:rgba(124,58,237,.06);
43
+ --rose:#e11d48;--rose-bg:rgba(225,29,72,.06);
44
+ --shadow:0 4px 16px rgba(0,0,0,.06);--shadow-sm:0 1px 3px rgba(0,0,0,.04);
45
+ }
46
+ body{font-family:var(--font);background:var(--bg);color:var(--text);line-height:1.6;transition:background .25s,color .25s}
47
+ button{cursor:pointer;font-family:inherit}
48
+ ::selection{background:rgba(124,140,245,.3)}
49
+ ::-webkit-scrollbar{width:5px}
50
+ ::-webkit-scrollbar-track{background:transparent}
51
+ ::-webkit-scrollbar-thumb{background:rgba(124,140,245,.2);border-radius:4px}
52
+
53
+ .app{display:flex;min-height:100vh}
54
+ .sidebar{position:fixed;top:0;left:0;bottom:0;width:var(--sidebar-w);background:var(--bg-sidebar);border-right:1px solid var(--border);display:flex;flex-direction:column;z-index:200;transition:width .25s cubic-bezier(.4,0,.2,1);overflow:hidden}
55
+ .sidebar.collapsed{width:var(--sidebar-collapsed)}
56
+ .sidebar-logo{height:var(--topbar-h);display:flex;align-items:center;padding:0 16px;gap:10px;border-bottom:1px solid var(--border);flex-shrink:0;overflow:hidden;white-space:nowrap}
57
+ .sidebar-logo img{width:26px;height:26px;flex-shrink:0}
58
+ .sidebar-logo .logo-text{font-size:15px;font-weight:700;letter-spacing:-.02em;transition:opacity .2s}
59
+ .sidebar-logo .logo-sub{font-size:9px;color:var(--text-muted);font-weight:500;display:block;line-height:1}
60
+ .sidebar.collapsed .logo-text{opacity:0;pointer-events:none}
61
+ .sidebar-nav{flex:1;padding:12px 8px;overflow-y:auto;overflow-x:hidden}
62
+ .nav-group{margin-bottom:6px}
63
+ .nav-group-title{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);padding:8px 12px 4px;white-space:nowrap;overflow:hidden;transition:opacity .2s}
64
+ .sidebar.collapsed .nav-group-title{opacity:0;height:0;padding:0;margin:0}
65
+ .nav-item{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:var(--radius-sm);color:var(--text-sec);font-size:13px;font-weight:500;cursor:pointer;transition:all .15s;white-space:nowrap;overflow:hidden;border:none;background:none;width:100%;text-align:left}
66
+ .nav-item:hover{background:var(--pri-dim);color:var(--text)}
67
+ .nav-item.active{background:var(--pri-dim);color:var(--pri);font-weight:600}
68
+ .nav-item svg{width:18px;height:18px;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
69
+ .nav-item .nav-label{transition:opacity .2s}
70
+ .sidebar.collapsed .nav-label{opacity:0;pointer-events:none}
71
+ .nav-item .nav-badge{margin-left:auto;font-size:10px;font-weight:700;padding:1px 6px;border-radius:10px;background:var(--pri-dim);color:var(--pri);transition:opacity .2s}
72
+ .sidebar.collapsed .nav-badge{opacity:0}
73
+ .sidebar-footer{padding:8px;border-top:1px solid var(--border);flex-shrink:0}
74
+ .collapse-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:8px;border-radius:var(--radius-sm);background:none;border:none;color:var(--text-muted);font-size:12px;transition:all .15s;white-space:nowrap;overflow:hidden}
75
+ .collapse-btn:hover{background:var(--pri-dim);color:var(--text)}
76
+ .collapse-btn svg{width:16px;height:16px;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;transition:transform .25s}
77
+ .sidebar.collapsed .collapse-btn svg{transform:rotate(180deg)}
78
+ .collapse-btn .collapse-label{transition:opacity .2s}
79
+ .sidebar.collapsed .collapse-label{opacity:0;pointer-events:none}
80
+ .sidebar-version{text-align:center;padding:4px 0 2px;font-size:10px;color:var(--text-muted);transition:opacity .2s}
81
+ .sidebar.collapsed .sidebar-version{opacity:0}
82
+
83
+ .topbar{position:fixed;top:0;left:var(--sidebar-w);right:0;height:var(--topbar-h);background:var(--bg);border-bottom:1px solid var(--border);display:flex;align-items:center;padding:0 24px;gap:12px;z-index:100;transition:left .25s cubic-bezier(.4,0,.2,1),background .25s}
84
+ body.collapsed .topbar{left:var(--sidebar-collapsed)}
85
+ .search-box{flex:1;max-width:420px;position:relative}
86
+ .search-box input{width:100%;padding:7px 12px 7px 34px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-card);color:var(--text);font-size:13px;outline:none;transition:all .15s}
87
+ .search-box input:focus{border-color:var(--pri);box-shadow:0 0 0 3px var(--pri-dim)}
88
+ .search-box input::placeholder{color:var(--text-muted)}
89
+ .search-box svg{position:absolute;left:10px;top:50%;transform:translateY(-50%);width:16px;height:16px;stroke:var(--text-muted);fill:none;stroke-width:2}
90
+ .topbar-actions{margin-left:auto;display:flex;align-items:center;gap:6px}
91
+ .topbar-btn{width:34px;height:34px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--bg-card);color:var(--text-sec);display:flex;align-items:center;justify-content:center;transition:all .15s}
92
+ .topbar-btn:hover{border-color:var(--pri);color:var(--pri)}
93
+ .topbar-btn svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2}
94
+ .topbar-btn.active{background:var(--pri-dim);border-color:var(--pri);color:var(--pri)}
95
+
96
+ .search-results{position:absolute;top:100%;left:0;right:0;margin-top:4px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow);z-index:500;max-height:400px;overflow-y:auto;display:none}
97
+ .search-results.show{display:block}
98
+ .search-result-group{padding:6px 0}
99
+ .search-result-group+.search-result-group{border-top:1px solid var(--border)}
100
+ .search-result-group-title{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--text-muted);padding:4px 14px}
101
+ .search-result-item{display:flex;align-items:center;gap:8px;padding:8px 14px;cursor:pointer;font-size:13px;color:var(--text-sec);transition:background .1s}
102
+ .search-result-item:hover{background:var(--pri-dim);color:var(--text)}
103
+ .search-result-item .sri-icon{font-size:14px;flex-shrink:0;width:20px;text-align:center}
104
+ .search-result-item .sri-text{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
105
+ .search-result-item .sri-type{font-size:10px;color:var(--text-muted);flex-shrink:0}
106
+ .search-no-results{padding:16px 14px;font-size:13px;color:var(--text-muted);text-align:center}
107
+
108
+ .main{margin-left:var(--sidebar-w);margin-top:var(--topbar-h);padding:28px 32px 60px;transition:margin-left .25s cubic-bezier(.4,0,.2,1);min-height:calc(100vh - var(--topbar-h))}
109
+ body.collapsed .main{margin-left:var(--sidebar-collapsed)}
110
+ .page{display:none}
111
+ .page.active{display:block}
112
+ .page-title{font-size:22px;font-weight:700;letter-spacing:-.02em;margin-bottom:20px}
113
+
114
+ .stat-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:14px;margin-bottom:24px}
115
+ .stat-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:18px 20px;transition:all .2s}
116
+ .stat-card:hover{border-color:var(--border-hover);transform:translateY(-1px);box-shadow:var(--shadow-sm)}
117
+ .stat-card .sc-label{font-size:12px;color:var(--text-muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em;margin-bottom:6px}
118
+ .stat-card .sc-value{font-size:28px;font-weight:800;letter-spacing:-.02em}
119
+ .stat-card .sc-sub{font-size:11px;color:var(--text-sec);margin-top:4px}
120
+ .stat-card.green .sc-value{color:var(--green)}
121
+ .stat-card.pri .sc-value{color:var(--pri)}
122
+ .stat-card.amber .sc-value{color:var(--amber)}
123
+ .stat-card.cyan .sc-value{color:var(--cyan)}
124
+ .stat-card.red .sc-value{color:var(--red)}
125
+
126
+ .section-box{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:20px 22px;margin-bottom:20px}
127
+ .section-title{font-size:15px;font-weight:700;margin-bottom:14px;display:flex;align-items:center;gap:8px}
128
+ .section-title .st-icon{font-size:16px}
129
+
130
+ .timeline{position:relative;padding-left:22px}
131
+ .timeline::before{content:'';position:absolute;left:6px;top:4px;bottom:4px;width:2px;background:var(--border);border-radius:1px}
132
+ .tl-item{position:relative;padding-bottom:16px}
133
+ .tl-item:last-child{padding-bottom:0}
134
+ .tl-dot{position:absolute;left:-22px;top:4px;width:14px;height:14px;border-radius:50%;border:2px solid var(--pri);background:var(--bg-card)}
135
+ .tl-dot.green{border-color:var(--green)}
136
+ .tl-dot.amber{border-color:var(--amber)}
137
+ .tl-dot.red{border-color:var(--red)}
138
+ .tl-content{font-size:13px;color:var(--text-sec)}
139
+ .tl-content strong{color:var(--text);font-weight:600}
140
+ .tl-time{font-size:11px;color:var(--text-muted);margin-top:2px}
141
+ .tl-badge{display:inline-block;font-size:10px;font-weight:700;padding:1px 7px;border-radius:10px;margin-left:6px}
142
+ .tl-badge.created{background:var(--green-bg);color:var(--green)}
143
+ .tl-badge.upgraded{background:var(--pri-dim);color:var(--pri)}
144
+ .tl-badge.skipped{background:rgba(255,255,255,.06);color:var(--text-muted)}
145
+ .tl-badge.generated{background:var(--pri-dim);color:var(--pri)}
146
+ .tl-badge.not_generated{background:rgba(255,255,255,.06);color:var(--text-muted)}
147
+
148
+ .mem-list{display:flex;flex-direction:column;gap:10px}
149
+ .mem-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:14px 18px;transition:all .15s;cursor:default;display:flex;gap:10px;align-items:flex-start}
150
+ .mem-card:hover{border-color:var(--border-hover)}
151
+ .mem-card.highlight{border-color:var(--pri);box-shadow:0 0 0 2px var(--pri-dim)}
152
+ .mem-check{flex-shrink:0;width:18px;height:18px;border-radius:4px;border:2px solid var(--border);background:none;cursor:pointer;transition:all .15s;display:flex;align-items:center;justify-content:center;margin-top:2px;appearance:none;-webkit-appearance:none}
153
+ .mem-check:checked{background:var(--pri);border-color:var(--pri)}
154
+ .mem-check:checked::after{content:'\\2713';color:#fff;font-size:11px;font-weight:700}
155
+ .mem-card-body{flex:1;min-width:0}
156
+ .mem-meta{display:flex;align-items:center;gap:8px;margin-bottom:6px;flex-wrap:wrap}
157
+ .role-badge{font-size:10px;font-weight:700;padding:2px 8px;border-radius:10px;text-transform:uppercase}
158
+ .role-badge.user{background:var(--cyan-bg);color:var(--cyan)}
159
+ .role-badge.assistant{background:var(--violet-bg);color:var(--violet)}
160
+ .role-badge.tool{background:var(--amber-bg);color:var(--amber)}
161
+ .owner-tag{font-size:10px;color:var(--text-muted);padding:2px 7px;border-radius:8px;border:1px solid var(--border)}
162
+ .time-tag{font-size:11px;color:var(--text-muted);margin-left:auto}
163
+ .mem-summary{font-size:13px;color:var(--text-sec);line-height:1.6}
164
+ .mem-full{display:none;margin-top:10px;padding:12px 14px;background:rgba(124,140,245,.03);border:1px solid var(--border);border-radius:var(--radius-sm);font-size:12px;color:var(--text-sec);line-height:1.7;white-space:pre-wrap;font-family:var(--mono)}
165
+ .mem-full.show{display:block}
166
+ .mem-actions{display:flex;gap:6px;margin-top:8px}
167
+ .mem-act{font-size:11px;padding:3px 10px;border-radius:var(--radius-xs);border:1px solid var(--border);background:none;color:var(--text-sec);transition:all .15s}
168
+ .mem-act:hover{border-color:var(--pri);color:var(--pri)}
169
+ .mem-act.danger:hover{border-color:var(--red);color:var(--red)}
170
+ .mem-session{font-size:10px;color:var(--text-muted);padding:2px 7px;border-radius:8px;background:var(--pri-dim)}
171
+ .mem-id{font-size:10px;color:var(--text-muted);font-family:var(--mono)}
172
+ .pagination{display:flex;align-items:center;justify-content:center;gap:4px;margin-top:18px}
173
+ .page-btn{min-width:32px;height:32px;border-radius:var(--radius-xs);border:1px solid var(--border);background:var(--bg-card);color:var(--text-sec);font-size:12px;font-weight:600;display:flex;align-items:center;justify-content:center;transition:all .15s}
174
+ .page-btn:hover{border-color:var(--pri);color:var(--pri)}
175
+ .page-btn.active{background:var(--pri);border-color:var(--pri);color:#fff}
176
+ .page-btn:disabled{opacity:.3;cursor:default;pointer-events:none}
177
+ .page-info{font-size:12px;color:var(--text-muted);margin:0 8px}
178
+
179
+ .batch-bar{position:fixed;bottom:0;left:var(--sidebar-w);right:0;height:52px;background:var(--bg-card);border-top:1px solid var(--border);display:none;align-items:center;padding:0 24px;gap:12px;z-index:150;box-shadow:0 -2px 12px rgba(0,0,0,.2);transition:left .25s}
180
+ body.collapsed .batch-bar{left:var(--sidebar-collapsed)}
181
+ .batch-bar.show{display:flex}
182
+ .batch-bar .batch-count{font-size:13px;font-weight:600;color:var(--pri)}
183
+ .batch-bar .batch-btn{font-size:12px;padding:6px 14px;border-radius:var(--radius-xs);border:1px solid var(--border);background:var(--bg-card);color:var(--text-sec);font-weight:500;transition:all .15s}
184
+ .batch-bar .batch-btn:hover{border-color:var(--pri);color:var(--pri)}
185
+ .batch-bar .batch-btn.danger{color:var(--red)}
186
+ .batch-bar .batch-btn.danger:hover{border-color:var(--red)}
187
+ .batch-bar .batch-spacer{flex:1}
188
+
189
+ .task-list{display:flex;flex-direction:column;gap:10px}
190
+ .task-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:14px 18px;display:flex;gap:14px;cursor:pointer;transition:all .15s}
191
+ .task-card:hover{border-color:var(--border-hover);background:var(--bg-card-hover)}
192
+ .task-stripe{width:4px;border-radius:2px;flex-shrink:0}
193
+ .task-stripe.completed{background:var(--green)}
194
+ .task-stripe.active{background:var(--amber)}
195
+ .task-stripe.skipped{background:var(--text-muted)}
196
+ .task-body{flex:1;min-width:0}
197
+ .task-title{font-size:14px;font-weight:600;margin-bottom:4px}
198
+ .task-summary{font-size:12px;color:var(--text-sec);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
199
+ .task-footer{display:flex;align-items:center;gap:8px;margin-top:8px;flex-wrap:wrap}
200
+ .status-badge{font-size:10px;font-weight:700;padding:2px 8px;border-radius:10px}
201
+ .status-badge.completed{background:var(--green-bg);color:var(--green)}
202
+ .status-badge.active{background:var(--amber-bg);color:var(--amber)}
203
+ .status-badge.skipped{background:rgba(255,255,255,.06);color:var(--text-muted)}
204
+ .skill-status-badge{font-size:10px;font-weight:600;padding:2px 8px;border-radius:10px;background:var(--pri-dim);color:var(--pri)}
205
+
206
+ .skill-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:14px}
207
+ .skill-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:18px 20px;cursor:pointer;transition:all .15s}
208
+ .skill-card:hover{border-color:var(--border-hover);background:var(--bg-card-hover);transform:translateY(-1px);box-shadow:var(--shadow-sm)}
209
+ .skill-header{display:flex;align-items:flex-start;gap:10px;margin-bottom:8px}
210
+ .skill-name{font-size:15px;font-weight:700;flex:1}
211
+ .skill-ver{font-size:11px;font-weight:700;padding:2px 8px;border-radius:8px;background:var(--pri-dim);color:var(--pri);flex-shrink:0}
212
+ .skill-qs{font-size:12px;color:var(--amber);flex-shrink:0}
213
+ .skill-desc{font-size:12px;color:var(--text-sec);margin-bottom:10px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;line-height:1.6}
214
+ .skill-evo-line{font-size:11px;color:var(--text-muted);margin-bottom:8px;display:flex;align-items:center;gap:4px;flex-wrap:wrap}
215
+ .skill-evo-line .evo-num{color:var(--pri);font-weight:600}
216
+ .skill-tags{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px}
217
+ .skill-tag{font-size:10px;padding:2px 8px;border-radius:8px;background:var(--pri-dim);color:var(--pri);font-weight:500}
218
+ .skill-foot{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
219
+ .skill-foot .status-badge{font-size:10px}
220
+ .installed-badge{font-size:10px;font-weight:600;padding:2px 8px;border-radius:10px;background:var(--green-bg);color:var(--green)}
221
+
222
+ .heur-stats{display:flex;gap:16px;margin-bottom:18px;flex-wrap:wrap}
223
+ .heur-stat{font-size:13px;color:var(--text-sec)}
224
+ .heur-stat strong{font-weight:700}
225
+ .heur-filters{display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap}
226
+ .heur-filter{padding:6px 14px;border-radius:20px;border:1px solid var(--border);background:var(--bg-card);color:var(--text-sec);font-size:12px;font-weight:500;transition:all .15s}
227
+ .heur-filter:hover,.heur-filter.active{border-color:var(--pri);color:var(--pri);background:var(--pri-dim)}
228
+ .heur-list{display:flex;flex-direction:column;gap:12px}
229
+ .heur-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:16px 20px;transition:all .15s}
230
+ .heur-card:hover{border-color:var(--border-hover)}
231
+ .heur-top{display:flex;align-items:center;gap:8px;margin-bottom:10px;flex-wrap:wrap}
232
+ .kind-badge{font-size:10px;font-weight:800;padding:3px 10px;border-radius:6px;text-transform:uppercase;letter-spacing:.04em}
233
+ .kind-badge.avoid{background:var(--red-bg);color:var(--red)}
234
+ .kind-badge.prefer{background:var(--green-bg);color:var(--green)}
235
+ .kind-badge.verify{background:var(--amber-bg);color:var(--amber)}
236
+ .kind-badge.clarify{background:var(--cyan-bg);color:var(--cyan)}
237
+ .heur-status{font-size:10px;font-weight:600;padding:2px 8px;border-radius:10px;margin-left:auto}
238
+ .heur-status.active-s{background:var(--green-bg);color:var(--green)}
239
+ .heur-status.candidate-s{background:var(--amber-bg);color:var(--amber)}
240
+ .heur-status.refined-s{background:var(--pri-dim);color:var(--pri)}
241
+ .heur-status.stale-s{background:rgba(255,255,255,.05);color:var(--text-muted)}
242
+ .heur-field{margin-bottom:8px}
243
+ .heur-field-label{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--text-muted);margin-bottom:2px}
244
+ .heur-field-value{font-size:13px;color:var(--text-sec);line-height:1.6}
245
+ .heur-meta{display:flex;align-items:center;gap:12px;margin-top:10px;flex-wrap:wrap;font-size:11px;color:var(--text-muted)}
246
+ .heur-meta .hm-item{display:flex;align-items:center;gap:4px}
247
+ .heur-evidence-toggle{font-size:12px;color:var(--pri);cursor:pointer;border:none;background:none;font-weight:500;margin-top:6px;padding:0}
248
+ .heur-evidence-toggle:hover{text-decoration:underline}
249
+ .heur-evidence{display:none;margin-top:10px;padding:12px 14px;background:rgba(124,140,245,.04);border:1px solid var(--border);border-radius:var(--radius-sm);font-size:12px;color:var(--text-sec);line-height:1.7;white-space:pre-wrap;font-family:var(--mono)}
250
+ .heur-evidence.show{display:block}
251
+ .heur-actions{display:flex;gap:6px;margin-top:10px}
252
+ .heur-act-btn{font-size:11px;padding:4px 10px;border-radius:var(--radius-xs);border:1px solid var(--border);background:none;color:var(--text-sec);transition:all .15s}
253
+ .heur-act-btn:hover{border-color:var(--pri);color:var(--pri)}
254
+ .heur-skill-link{font-size:11px;color:var(--pri);cursor:pointer;text-decoration:none}
255
+ .heur-skill-link:hover{text-decoration:underline}
256
+ .quality-bar{width:60px;height:6px;background:var(--border);border-radius:3px;overflow:hidden;display:inline-block;vertical-align:middle;margin-left:4px}
257
+ .quality-fill{height:100%;border-radius:3px;background:var(--pri)}
258
+
259
+ .trend-indicator{font-size:12px;font-weight:700;margin-left:4px}
260
+ .trend-indicator.up{color:var(--green)}
261
+ .trend-indicator.down{color:var(--red)}
262
+ .trend-indicator.stable{color:var(--text-muted)}
263
+
264
+ .heur-mini-timeline{display:none;margin-top:12px;padding:12px 14px;background:rgba(124,140,245,.03);border:1px solid var(--border);border-radius:var(--radius-sm)}
265
+ .heur-mini-timeline.show{display:block}
266
+ .heur-mini-timeline .evo-timeline{padding-left:24px}
267
+ .heur-mini-timeline .evo-timeline::before{left:6px}
268
+ .heur-mini-timeline .evo-tl-item{padding-bottom:12px}
269
+ .heur-mini-timeline .evo-tl-item:last-child{padding-bottom:0}
270
+ .heur-mini-timeline .evo-tl-dot{left:-24px;width:14px;height:14px;border-width:2px;font-size:7px}
271
+ .heur-mini-timeline .evo-tl-title{font-size:12px}
272
+ .heur-mini-timeline .evo-tl-desc{font-size:11px}
273
+ .heur-mini-timeline .evo-tl-time{font-size:10px}
274
+ .heur-evo-dot-created{border-color:var(--green)!important;color:var(--green)!important}
275
+ .heur-evo-dot-validated{border-color:var(--pri)!important;color:var(--pri)!important}
276
+ .heur-evo-dot-refined{border-color:var(--amber)!important;color:var(--amber)!important}
277
+ .heur-evo-dot-promoted{border-color:var(--cyan)!important;color:var(--cyan)!important}
278
+
279
+ .sort-select{padding:6px 12px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--bg-card);color:var(--text-sec);font-size:12px;font-family:var(--font);outline:none;cursor:pointer;transition:border-color .15s}
280
+ .sort-select:focus{border-color:var(--pri)}
281
+
282
+ .chart-section{margin-bottom:20px}
283
+ .chart-container{display:flex;align-items:flex-end;gap:6px;height:140px;padding:10px 0}
284
+ .chart-col{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px}
285
+ .chart-bar{width:100%;border-radius:4px 4px 0 0;background:linear-gradient(180deg,var(--pri),var(--pri-dark));transition:height .3s;min-height:4px}
286
+ .chart-label{font-size:10px;color:var(--text-muted)}
287
+ .chart-val{font-size:10px;color:var(--text-sec);font-weight:600}
288
+ .evo-table{width:100%;border-collapse:collapse;font-size:12px}
289
+ .evo-table th{text-align:left;padding:8px 12px;font-size:11px;font-weight:700;color:var(--text-muted);text-transform:uppercase;letter-spacing:.04em;border-bottom:2px solid var(--border)}
290
+ .evo-table td{padding:8px 12px;border-bottom:1px solid var(--border);color:var(--text-sec)}
291
+ .evo-table tr:hover td{background:var(--bg-card-hover)}
292
+
293
+ .log-list{display:flex;flex-direction:column;gap:6px}
294
+ .log-entry{display:flex;align-items:flex-start;gap:10px;padding:8px 14px;border-radius:var(--radius-sm);background:var(--bg-card);border:1px solid var(--border);font-size:12px}
295
+ .log-time{color:var(--text-muted);font-family:var(--mono);flex-shrink:0;font-size:11px;padding-top:1px}
296
+ .log-level{font-size:10px;font-weight:700;padding:2px 7px;border-radius:8px;flex-shrink:0}
297
+ .log-level.info{background:var(--pri-dim);color:var(--pri)}
298
+ .log-level.warn{background:var(--amber-bg);color:var(--amber)}
299
+ .log-level.error{background:var(--red-bg);color:var(--red)}
300
+ .log-msg{color:var(--text-sec);flex:1;line-height:1.5}
301
+ .log-tool{font-size:10px;padding:2px 7px;border-radius:8px;background:var(--cyan-bg);color:var(--cyan);font-weight:600;flex-shrink:0}
302
+ .log-cat{font-size:10px;padding:2px 7px;border-radius:8px;font-weight:700;flex-shrink:0}
303
+ .log-cat.cat-memory-add,.log-cat.cat-memory_add{background:var(--green-bg);color:var(--green)}
304
+ .log-cat.cat-memory-search,.log-cat.cat-memory_search{background:var(--cyan-bg);color:var(--cyan)}
305
+ .log-cat.cat-skill{background:var(--violet-bg);color:var(--violet)}
306
+ .log-cat.cat-task{background:var(--amber-bg);color:var(--amber)}
307
+ .log-cat.cat-system{background:rgba(255,255,255,.06);color:var(--text-muted)}
308
+ .log-filters{display:flex;gap:8px;margin-bottom:14px;flex-wrap:wrap}
309
+ .log-entry.expandable{cursor:pointer}
310
+ .log-entry.expandable:hover{border-color:var(--border-hover)}
311
+ .log-expand-icon{font-size:10px;color:var(--text-muted);flex-shrink:0;transition:transform .2s}
312
+ .log-expand-icon.open{transform:rotate(90deg)}
313
+ .log-detail{display:none;margin-top:8px;padding:12px 14px;background:rgba(124,140,245,.03);border:1px solid var(--border);border-radius:var(--radius-sm);font-size:12px}
314
+ .log-detail.show{display:block}
315
+ .log-detail-query{font-size:12px;color:var(--text);font-weight:600;margin-bottom:8px}
316
+ .log-detail-query span{color:var(--pri);font-family:var(--mono)}
317
+ .log-detail-funnel{display:flex;gap:12px;margin-bottom:12px;flex-wrap:wrap}
318
+ .log-detail-funnel .funnel-step{font-size:11px;color:var(--text-sec);display:flex;align-items:center;gap:4px}
319
+ .log-detail-funnel .funnel-num{font-weight:700;color:var(--pri)}
320
+ .log-detail-funnel .funnel-arrow{color:var(--text-muted)}
321
+ .recall-reason{font-size:10px;color:var(--text-muted);margin-left:4px;font-style:italic}
322
+
323
+ .hub-status{display:flex;align-items:center;gap:6px;padding:4px 12px;font-size:10px;color:var(--text-muted);transition:opacity .2s}
324
+ .sidebar.collapsed .hub-status .hub-label{opacity:0;pointer-events:none}
325
+ .hub-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
326
+ .hub-dot.connected{background:var(--green);box-shadow:0 0 4px var(--green)}
327
+ .hub-dot.disconnected{background:var(--red)}
328
+
329
+ .share-preview{padding:12px 14px;background:rgba(124,140,245,.03);border:1px solid var(--border);border-radius:var(--radius-sm);font-size:13px;color:var(--text-sec);margin-bottom:16px;line-height:1.6}
330
+ .share-options{display:flex;flex-direction:column;gap:12px;margin-bottom:20px}
331
+ .share-option-row{display:flex;align-items:center;gap:12px}
332
+ .share-option-row label{font-size:13px;font-weight:500;color:var(--text)}
333
+ .share-radio-group{display:flex;gap:12px;flex-wrap:wrap}
334
+ .share-radio{display:flex;align-items:center;gap:4px;font-size:12px;color:var(--text-sec);cursor:pointer}
335
+ .share-radio input{accent-color:var(--pri)}
336
+ .share-btn-row{display:flex;gap:8px;margin-bottom:16px}
337
+ .share-action-btn{padding:8px 16px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--bg-card);color:var(--text-sec);font-size:13px;font-weight:500;transition:all .15s}
338
+ .share-action-btn:hover{border-color:var(--pri);color:var(--pri)}
339
+ .share-footer{display:flex;justify-content:flex-end;gap:8px;padding-top:16px;border-top:1px solid var(--border)}
340
+ .share-footer .btn-cancel{padding:8px 20px;border-radius:var(--radius-sm);border:1px solid var(--border);background:none;color:var(--text-sec);font-size:13px;transition:all .15s}
341
+ .share-footer .btn-cancel:hover{border-color:var(--text);color:var(--text)}
342
+ .share-footer .btn-confirm{padding:8px 20px;border-radius:var(--radius-sm);border:none;background:var(--pri);color:#fff;font-size:13px;font-weight:600;transition:all .15s}
343
+ .share-footer .btn-confirm:hover{background:var(--pri-dark)}
344
+ .share-success{display:none;padding:16px;text-align:center;font-size:13px;color:var(--green)}
345
+ .share-success.show{display:block}
346
+ .share-success a{color:var(--pri);word-break:break-all}
347
+
348
+ .settings-section{margin-bottom:24px}
349
+ .settings-section h3{font-size:15px;font-weight:700;margin-bottom:14px;padding-bottom:8px;border-bottom:1px solid var(--border)}
350
+ .setting-row{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--border)}
351
+ .setting-row:last-child{border-bottom:none}
352
+ .setting-label{font-size:13px;font-weight:500}
353
+ .setting-desc{font-size:11px;color:var(--text-muted);margin-top:2px}
354
+ .setting-input{padding:6px 12px;border:1px solid var(--border);border-radius:var(--radius-xs);background:var(--bg-card);color:var(--text);font-size:13px;width:220px;outline:none;transition:border-color .15s}
355
+ .setting-input:focus{border-color:var(--pri)}
356
+ .toggle-switch{width:40px;height:22px;border-radius:11px;background:var(--border);position:relative;cursor:pointer;transition:background .2s;border:none;flex-shrink:0}
357
+ .toggle-switch.on{background:var(--pri)}
358
+ .toggle-switch::after{content:'';position:absolute;width:18px;height:18px;border-radius:50%;background:#fff;top:2px;left:2px;transition:left .2s}
359
+ .toggle-switch.on::after{left:20px}
360
+
361
+ .overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);backdrop-filter:blur(4px);z-index:300;display:none;align-items:flex-start;justify-content:center;padding:40px 20px;overflow-y:auto}
362
+ .overlay.show{display:flex}
363
+ .overlay-panel{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);width:100%;max-width:720px;padding:28px 32px;position:relative;box-shadow:var(--shadow)}
364
+ .overlay-close{position:absolute;top:14px;right:14px;width:32px;height:32px;border-radius:var(--radius-xs);border:1px solid var(--border);background:none;color:var(--text-sec);display:flex;align-items:center;justify-content:center;font-size:18px;transition:all .15s}
365
+ .overlay-close:hover{border-color:var(--red);color:var(--red)}
366
+ .overlay-title{font-size:20px;font-weight:700;margin-bottom:4px;padding-right:40px}
367
+ .overlay-subtitle{font-size:13px;color:var(--text-sec);margin-bottom:20px}
368
+
369
+ .sd-section{margin-bottom:24px}
370
+ .sd-section-title{font-size:14px;font-weight:700;margin-bottom:12px;display:flex;align-items:center;gap:8px;color:var(--text)}
371
+ .sd-meta{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px}
372
+ .sd-meta-item{font-size:12px;padding:4px 12px;border-radius:var(--radius-sm);background:var(--bg-card-hover);border:1px solid var(--border);color:var(--text-sec)}
373
+ .sd-content{font-size:13px;color:var(--text-sec);line-height:1.8;white-space:pre-wrap}
374
+ .sd-content h4{color:var(--text);font-size:14px;font-weight:700;margin:16px 0 6px}
375
+ .sd-content ul{margin:6px 0 12px 18px}
376
+ .sd-content li{margin-bottom:4px}
377
+ .sd-content code{font-family:var(--mono);font-size:12px;padding:2px 6px;border-radius:4px;background:var(--pri-dim);color:var(--pri)}
378
+ .task-link{font-size:12px;color:var(--pri);cursor:pointer;text-decoration:none;padding:4px 0;display:inline-block}
379
+ .task-link:hover{text-decoration:underline}
380
+
381
+ .pipeline-steps{display:flex;flex-direction:column;gap:0;margin-bottom:20px}
382
+ .pipe-step{display:flex;align-items:center;gap:12px;padding:10px 14px;border:1px solid var(--border);border-radius:var(--radius-sm);margin-bottom:-1px;background:var(--bg-card);font-size:13px}
383
+ .pipe-step:first-child{border-radius:var(--radius-sm) var(--radius-sm) 0 0}
384
+ .pipe-step:last-child{border-radius:0 0 var(--radius-sm) var(--radius-sm)}
385
+ .pipe-icon{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;flex-shrink:0;font-weight:700}
386
+ .pipe-icon.done{background:var(--green-bg);color:var(--green)}
387
+ .pipe-icon.pending{background:rgba(255,255,255,.06);color:var(--text-muted)}
388
+ .pipe-icon.skip{background:rgba(255,255,255,.04);color:var(--text-muted)}
389
+ .pipe-label{font-weight:500;flex:1}
390
+ .pipe-detail{font-size:11px;color:var(--text-muted)}
391
+ .chat-bubbles{display:flex;flex-direction:column;gap:8px}
392
+ .chat-bubble{max-width:85%;padding:10px 14px;border-radius:12px;font-size:13px;line-height:1.6}
393
+ .chat-bubble.user{align-self:flex-end;background:var(--pri-dim);color:var(--text);border-bottom-right-radius:4px}
394
+ .chat-bubble.assistant{align-self:flex-start;background:var(--bg-card-hover);color:var(--text-sec);border:1px solid var(--border);border-bottom-left-radius:4px}
395
+
396
+ .recall-section{margin-top:4px}
397
+ .recall-group{margin-bottom:14px}
398
+ .recall-group-title{font-size:12px;font-weight:700;color:var(--text-muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.04em}
399
+ .recall-candidate{display:flex;align-items:center;gap:10px;padding:6px 10px;border-radius:var(--radius-xs);margin-bottom:4px;font-size:12px;background:var(--bg-card-hover);border:1px solid var(--border)}
400
+ .recall-candidate.selected{border-color:rgba(52,211,153,.3)}
401
+ .recall-candidate.dropped{opacity:.6}
402
+ .recall-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-sec)}
403
+ .recall-score{font-size:11px;font-weight:700;color:var(--text-muted);flex-shrink:0;width:36px;text-align:right}
404
+ .recall-bar{width:80px;height:6px;background:var(--border);border-radius:3px;overflow:hidden;flex-shrink:0}
405
+ .recall-bar-fill{height:100%;border-radius:3px}
406
+ .recall-bar-fill.selected{background:var(--green)}
407
+ .recall-bar-fill.dropped{background:var(--text-muted)}
408
+ .recall-tag{font-size:9px;font-weight:700;padding:1px 6px;border-radius:8px;flex-shrink:0}
409
+ .recall-tag.sel{background:var(--green-bg);color:var(--green)}
410
+ .recall-tag.drop{background:rgba(255,255,255,.05);color:var(--text-muted)}
411
+ .recall-summary-line{font-size:12px;color:var(--pri);font-weight:600;margin-top:8px;padding:8px 10px;background:var(--pri-dim);border-radius:var(--radius-xs)}
412
+
413
+ .evo-timeline{position:relative;padding-left:28px}
414
+ .evo-timeline::before{content:'';position:absolute;left:8px;top:6px;bottom:6px;width:2px;background:var(--border);border-radius:1px}
415
+ .evo-tl-item{position:relative;padding-bottom:18px}
416
+ .evo-tl-item:last-child{padding-bottom:0}
417
+ .evo-tl-dot{position:absolute;left:-28px;top:3px;width:18px;height:18px;border-radius:50%;border:3px solid var(--pri);background:var(--bg-card);display:flex;align-items:center;justify-content:center;font-size:8px;font-weight:800;color:var(--pri)}
418
+ .evo-tl-dot.v1{border-color:var(--green);color:var(--green)}
419
+ .evo-tl-title{font-size:13px;font-weight:600;margin-bottom:2px}
420
+ .evo-tl-desc{font-size:12px;color:var(--text-sec);line-height:1.6}
421
+ .evo-tl-time{font-size:11px;color:var(--text-muted);margin-top:2px}
422
+
423
+ .filter-bar{display:flex;align-items:center;gap:12px;margin-bottom:18px;flex-wrap:wrap}
424
+ .filter-bar input{padding:7px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-card);color:var(--text);font-size:13px;outline:none;width:240px;transition:border-color .15s}
425
+ .filter-bar input:focus{border-color:var(--pri)}
426
+
427
+ .heur-eff-table{width:100%;border-collapse:collapse;font-size:12px;margin-top:8px}
428
+ .heur-eff-table th{text-align:left;padding:8px 10px;font-size:10px;font-weight:700;color:var(--text-muted);text-transform:uppercase;letter-spacing:.04em;border-bottom:2px solid var(--border)}
429
+ .heur-eff-table td{padding:7px 10px;border-bottom:1px solid var(--border);color:var(--text-sec)}
430
+ .heur-eff-table tr:hover td{background:var(--bg-card-hover)}
431
+
432
+ .loading-box{display:flex;align-items:center;justify-content:center;padding:40px;color:var(--text-muted);font-size:13px;gap:10px}
433
+ .loading-box::before{content:'';width:18px;height:18px;border:2px solid var(--border);border-top-color:var(--pri);border-radius:50%;animation:spin .6s linear infinite;flex-shrink:0}
434
+ @keyframes spin{to{transform:rotate(360deg)}}
435
+ .error-box{padding:24px;text-align:center;color:var(--red);font-size:13px}
436
+ .error-box button{margin-top:10px;padding:6px 16px;border-radius:var(--radius-xs);border:1px solid var(--border);background:var(--bg-card);color:var(--text-sec);cursor:pointer;font-size:12px;transition:all .15s}
437
+ .error-box button:hover{border-color:var(--pri);color:var(--pri)}
438
+ .toast{position:fixed;bottom:24px;right:24px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:12px 20px;font-size:13px;box-shadow:var(--shadow);z-index:999;transform:translateY(80px);opacity:0;transition:all .3s;pointer-events:none}
439
+ .toast.show{transform:translateY(0);opacity:1;pointer-events:auto}
440
+ .toast.success{border-left:3px solid var(--green);color:var(--green)}
441
+ .toast.error{border-left:3px solid var(--red);color:var(--red)}
442
+ .settings-save-row{display:flex;justify-content:flex-end;padding-top:16px;border-top:1px solid var(--border);margin-top:8px}
443
+ .btn-save{padding:8px 24px;border-radius:var(--radius-sm);border:none;background:var(--pri);color:#fff;font-size:13px;font-weight:600;transition:all .15s}
444
+ .btn-save:hover{background:var(--pri-dark)}
445
+ .btn-save:disabled{opacity:.5;cursor:default}
446
+
447
+ @media(max-width:768px){
448
+ .sidebar{width:var(--sidebar-collapsed)}
449
+ .sidebar .nav-label,.sidebar .nav-badge,.sidebar .collapse-label,.sidebar .sidebar-version,.sidebar .nav-group-title,.sidebar .logo-text{opacity:0;pointer-events:none}
450
+ .topbar{left:var(--sidebar-collapsed)}
451
+ .main{margin-left:var(--sidebar-collapsed)}
452
+ .stat-grid{grid-template-columns:repeat(2,1fr)}
453
+ .skill-grid{grid-template-columns:1fr}
454
+ .batch-bar{left:var(--sidebar-collapsed)}
455
+ }
456
+ </style>
457
+ </head>
458
+ <body>
459
+
460
+ <div class="app">
461
+ <aside class="sidebar" id="sidebar">
462
+ <div class="sidebar-logo">
463
+ <img src="https://statics.memtensor.com.cn/logo/color-m.svg" alt="MemOS">
464
+ <span class="logo-text">MemOS<span class="logo-sub">Viewer</span></span>
465
+ </div>
466
+ <nav class="sidebar-nav">
467
+ <div class="nav-group">
468
+ <div class="nav-group-title">概览</div>
469
+ <button class="nav-item active" data-page="dashboard">
470
+ <svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
471
+ <span class="nav-label">Dashboard</span>
472
+ </button>
473
+ </div>
474
+ <div class="nav-group">
475
+ <div class="nav-group-title">数据</div>
476
+ <button class="nav-item" data-page="memories">
477
+ <svg viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
478
+ <span class="nav-label">记忆</span>
479
+ <span class="nav-badge" id="navBadgeMemories">—</span>
480
+ </button>
481
+ <button class="nav-item" data-page="tasks">
482
+ <svg viewBox="0 0 24 24"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
483
+ <span class="nav-label">任务</span>
484
+ <span class="nav-badge" id="navBadgeTasks">—</span>
485
+ </button>
486
+ </div>
487
+ <div class="nav-group">
488
+ <div class="nav-group-title">智能</div>
489
+ <button class="nav-item" data-page="skills">
490
+ <svg viewBox="0 0 24 24"><path d="M12 2a7 7 0 0 1 7 7c0 2.38-1.19 4.47-3 5.74V17a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2v-2.26C6.19 13.47 5 11.38 5 9a7 7 0 0 1 7-7z"/><path d="M10 21h4"/><path d="M10 17h4"/></svg>
491
+ <span class="nav-label">技能</span>
492
+ <span class="nav-badge" id="navBadgeSkills">—</span>
493
+ </button>
494
+ <button class="nav-item" data-page="heuristics">
495
+ <svg viewBox="0 0 24 24"><path d="M12 9v4"/><path d="M10.363 3.591l-8.106 13.534a1.914 1.914 0 0 0 1.636 2.871h16.214a1.914 1.914 0 0 0 1.636-2.87L13.637 3.59a1.914 1.914 0 0 0-3.274 0z"/><path d="M12 16h.01"/></svg>
496
+ <span class="nav-label">经验规则</span>
497
+ <span class="nav-badge" id="navBadgeHeuristics">—</span>
498
+ </button>
499
+ </div>
500
+ <div class="nav-group">
501
+ <div class="nav-group-title">系统</div>
502
+ <button class="nav-item" data-page="analytics">
503
+ <svg viewBox="0 0 24 24"><path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/></svg>
504
+ <span class="nav-label">分析</span>
505
+ </button>
506
+ <button class="nav-item" data-page="logs">
507
+ <svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M16 13H8"/><path d="M16 17H8"/><path d="M10 9H8"/></svg>
508
+ <span class="nav-label">日志</span>
509
+ </button>
510
+ </div>
511
+ <div class="nav-group">
512
+ <div class="nav-group-title">管理</div>
513
+ <button class="nav-item" data-page="settings">
514
+ <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
515
+ <span class="nav-label">设置</span>
516
+ </button>
517
+ </div>
518
+ </nav>
519
+ <div class="sidebar-footer">
520
+ <div class="hub-status" id="hubStatus">
521
+ <span class="hub-dot disconnected" id="hubDot"></span>
522
+ <span class="hub-label" id="hubLabel">检测中…</span>
523
+ </div>
524
+ <div class="sidebar-version">v${vBadge || '—'}</div>
525
+ <button class="collapse-btn" onclick="toggleSidebar()">
526
+ <svg viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6"/></svg>
527
+ <span class="collapse-label">收起侧栏</span>
528
+ </button>
529
+ </div>
530
+ </aside>
531
+
532
+ <header class="topbar">
533
+ <div class="search-box" id="searchBox">
534
+ <svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
535
+ <input type="text" placeholder="搜索记忆、任务、技能..." id="globalSearchInput">
536
+ <div class="search-results" id="searchResults"></div>
537
+ </div>
538
+ <div class="topbar-actions">
539
+ <button class="topbar-btn" onclick="toggleTheme()" title="切换主题" id="themeBtn">
540
+ <svg viewBox="0 0 24 24" id="themeIcon"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
541
+ </button>
542
+ </div>
543
+ </header>
544
+
545
+ <main class="main">
546
+
547
+ <div class="page active" id="page-dashboard">
548
+ <h1 class="page-title">Dashboard</h1>
549
+ <div class="stat-grid" id="dashStats">
550
+ <div class="stat-card pri"><div class="sc-label">总记忆</div><div class="sc-value" id="statMemories">—</div><div class="sc-sub" id="statMemoriesSub">加载中…</div></div>
551
+ <div class="stat-card amber"><div class="sc-label">活跃任务</div><div class="sc-value" id="statTasks">—</div><div class="sc-sub" id="statTasksSub">加载中…</div></div>
552
+ <div class="stat-card green"><div class="sc-label">技能</div><div class="sc-value" id="statSkills">—</div><div class="sc-sub" id="statSkillsSub">加载中…</div></div>
553
+ <div class="stat-card cyan"><div class="sc-label">经验规则</div><div class="sc-value" id="statHeuristics">—</div><div class="sc-sub" id="statHeuristicsSub">加载中…</div></div>
554
+ </div>
555
+ <div class="section-box">
556
+ <div class="section-title"><span class="st-icon">&#9889;</span> 最近技能进化</div>
557
+ <div class="timeline" id="dashTimeline"><div class="loading-box">加载中…</div></div>
558
+ </div>
559
+ <div class="section-box">
560
+ <div class="section-title"><span class="st-icon">&#128737;</span> 活跃经验规则 Top 3</div>
561
+ <div id="dashTopHeuristics"><div class="loading-box">加载中…</div></div>
562
+ </div>
563
+ </div>
564
+
565
+ <div class="page" id="page-memories">
566
+ <h1 class="page-title">记忆</h1>
567
+ <div class="filter-bar">
568
+ <input type="text" placeholder="搜索记忆内容..." id="memSearch" oninput="debounceMemSearch()">
569
+ <button class="heur-filter active" data-mf="all" onclick="setMemFilter('all',this)">全部</button>
570
+ <button class="heur-filter" data-mf="user" onclick="setMemFilter('user',this)">User</button>
571
+ <button class="heur-filter" data-mf="assistant" onclick="setMemFilter('assistant',this)">Assistant</button>
572
+ <button class="heur-filter" data-mf="tool" onclick="setMemFilter('tool',this)">Tool</button>
573
+ </div>
574
+ <div class="mem-list" id="memList"><div class="loading-box">加载中…</div></div>
575
+ <div class="pagination" id="memPagination"></div>
576
+ </div>
577
+
578
+ <div class="page" id="page-tasks">
579
+ <h1 class="page-title">任务</h1>
580
+ <div class="filter-bar">
581
+ <button class="heur-filter active" data-tf="all">全部</button>
582
+ <button class="heur-filter" data-tf="completed">已完成</button>
583
+ <button class="heur-filter" data-tf="active">进行中</button>
584
+ <button class="heur-filter" data-tf="skipped">已跳过</button>
585
+ </div>
586
+ <div class="task-list" id="taskList"><div class="loading-box">加载中…</div></div>
587
+ </div>
588
+
589
+ <div class="page" id="page-skills">
590
+ <h1 class="page-title">技能</h1>
591
+ <div class="filter-bar">
592
+ <input type="text" placeholder="搜索技能..." id="skillSearch" oninput="renderSkills()">
593
+ </div>
594
+ <div class="skill-grid" id="skillGrid"><div class="loading-box">加载中…</div></div>
595
+ </div>
596
+
597
+ <div class="page" id="page-heuristics">
598
+ <h1 class="page-title">经验规则</h1>
599
+ <div class="heur-stats" id="heurStats"></div>
600
+ <div class="heur-filters" id="heurStatusFilters">
601
+ <button class="heur-filter active" data-hf="all">全部</button>
602
+ <button class="heur-filter" data-hf="active">Active</button>
603
+ <button class="heur-filter" data-hf="candidate">Candidate</button>
604
+ <button class="heur-filter" data-hf="stale">Stale</button>
605
+ </div>
606
+ <div class="heur-filters" id="heurKindFilters" style="margin-top:-8px">
607
+ <button class="heur-filter active" data-kf="all">全部类型</button>
608
+ <button class="heur-filter" data-kf="avoid">Avoid</button>
609
+ <button class="heur-filter" data-kf="prefer">Prefer</button>
610
+ <button class="heur-filter" data-kf="verify">Verify</button>
611
+ <button class="heur-filter" data-kf="clarify">Clarify</button>
612
+ </div>
613
+ <div class="filter-bar" style="margin-bottom:16px">
614
+ <select class="sort-select" id="heurSortSelect">
615
+ <option value="quality">按质量分</option>
616
+ <option value="support">按验证次数</option>
617
+ <option value="validated">按最近验证时间</option>
618
+ <option value="recall">按召回次数</option>
619
+ </select>
620
+ </div>
621
+ <div class="heur-list" id="heurList"><div class="loading-box">加载中…</div></div>
622
+ <div class="pagination" id="heurPagination"></div>
623
+ </div>
624
+
625
+ <div class="page" id="page-analytics">
626
+ <h1 class="page-title">分析</h1>
627
+ <div class="stat-grid" id="analyticsStats">
628
+ <div class="stat-card green"><div class="sc-label">技能进化率</div><div class="sc-value" id="anaEvoRate">—</div><div class="sc-sub">任务 → 技能 转化率</div></div>
629
+ <div class="stat-card pri"><div class="sc-label">规则覆盖率</div><div class="sc-value" id="anaCovRate">—</div><div class="sc-sub">已知失败模式被规则覆盖</div></div>
630
+ <div class="stat-card cyan"><div class="sc-label">活跃规则数</div><div class="sc-value" id="anaActiveRules">—</div><div class="sc-sub">经验规则</div></div>
631
+ <div class="stat-card amber"><div class="sc-label">平均质量分</div><div class="sc-value" id="anaAvgQuality">—</div><div class="sc-sub">活跃规则</div></div>
632
+ </div>
633
+ <div class="section-box">
634
+ <div class="section-title"><span class="st-icon">&#128202;</span> 每周技能进化次数</div>
635
+ <div class="chart-container" id="evoChart"><div class="loading-box">加载中…</div></div>
636
+ </div>
637
+ <div class="section-box">
638
+ <div class="section-title"><span class="st-icon">&#128203;</span> 最近进化事件</div>
639
+ <table class="evo-table" id="evoTable"><thead><tr><th>时间</th><th>任务</th><th>操作</th><th>技能</th><th>原因</th></tr></thead><tbody></tbody></table>
640
+ </div>
641
+ <div class="section-box" id="heurEffSection">
642
+ <div class="section-title"><span class="st-icon">&#128737;</span> Heuristic 效果分析</div>
643
+ <div class="stat-grid" id="heurEffStats" style="margin-bottom:16px"></div>
644
+ <table class="heur-eff-table" id="heurEffTable"><thead><tr><th>ID</th><th>类型</th><th>触发条件</th><th>召回</th><th>Helpful</th><th>Misleading</th><th>质量分</th><th>趋势</th></tr></thead><tbody></tbody></table>
645
+ </div>
646
+ </div>
647
+
648
+ <div class="page" id="page-logs">
649
+ <h1 class="page-title">日志</h1>
650
+ <div class="log-filters" id="logFilters">
651
+ <button class="heur-filter active" data-lf="all">全部</button>
652
+ <button class="heur-filter" data-lf="memory_add">Memory Add</button>
653
+ <button class="heur-filter" data-lf="memory_search">Memory Search</button>
654
+ <button class="heur-filter" data-lf="skill">Skill</button>
655
+ <button class="heur-filter" data-lf="task">Task</button>
656
+ <button class="heur-filter" data-lf="system">System</button>
657
+ </div>
658
+ <div class="log-list" id="logList"><div class="loading-box">加载中…</div></div>
659
+ </div>
660
+
661
+ <div class="page" id="page-settings">
662
+ <h1 class="page-title">设置</h1>
663
+ <div class="section-box" id="settingsBox">
664
+ <div class="loading-box">加载中…</div>
665
+ </div>
666
+ </div>
667
+
668
+ </main>
669
+ </div>
670
+
671
+ <div class="batch-bar" id="batchBar">
672
+ <span class="batch-count" id="batchCount">已选 0 条</span>
673
+ <button class="batch-btn" onclick="batchSelectAll()">全选当前页</button>
674
+ <button class="batch-btn danger" onclick="batchDelete()">批量删除</button>
675
+ <button class="batch-btn" onclick="batchExport()">批量导出</button>
676
+ <span class="batch-spacer"></span>
677
+ <button class="batch-btn" onclick="batchClearAll()">取消选择</button>
678
+ </div>
679
+
680
+ <div class="overlay" id="skillOverlay" onclick="if(event.target===this)closeSkillDetail()">
681
+ <div class="overlay-panel">
682
+ <button class="overlay-close" onclick="closeSkillDetail()">&times;</button>
683
+ <div id="skillDetailContent"></div>
684
+ </div>
685
+ </div>
686
+
687
+ <div class="overlay" id="taskOverlay" onclick="if(event.target===this)closeTaskDetail()">
688
+ <div class="overlay-panel">
689
+ <button class="overlay-close" onclick="closeTaskDetail()">&times;</button>
690
+ <div id="taskDetailContent"></div>
691
+ </div>
692
+ </div>
693
+
694
+ <div class="overlay" id="shareOverlay" onclick="if(event.target===this)closeShareDialog()">
695
+ <div class="overlay-panel" style="max-width:520px">
696
+ <button class="overlay-close" onclick="closeShareDialog()">&times;</button>
697
+ <div class="overlay-title">共享记忆</div>
698
+ <div class="overlay-subtitle">将选中的记忆分享到 MemHub 或生成共享链接</div>
699
+ <div class="share-preview" id="sharePreview"></div>
700
+ <div class="share-btn-row">
701
+ <button class="share-action-btn" onclick="doShareToHub()">&#128228; 共享到 Hub</button>
702
+ <button class="share-action-btn" onclick="doShareLink()">&#128279; 生成共享链接</button>
703
+ </div>
704
+ <div class="share-options">
705
+ <div class="share-option-row">
706
+ <label>共享范围:</label>
707
+ <div class="share-radio-group">
708
+ <label class="share-radio"><input type="radio" name="shareScope" value="private" checked> 仅自己</label>
709
+ <label class="share-radio"><input type="radio" name="shareScope" value="team"> 团队</label>
710
+ <label class="share-radio"><input type="radio" name="shareScope" value="public"> 公开</label>
711
+ </div>
712
+ </div>
713
+ <div class="share-option-row">
714
+ <label class="share-radio"><input type="checkbox" id="shareAnonymize" checked> 自动脱敏敏感信息</label>
715
+ </div>
716
+ </div>
717
+ <div class="share-success" id="shareSuccess"></div>
718
+ <div class="share-footer">
719
+ <button class="btn-cancel" onclick="closeShareDialog()">取消</button>
720
+ <button class="btn-confirm" onclick="confirmShare()">确认共享</button>
721
+ </div>
722
+ </div>
723
+ </div>
724
+
725
+ <div class="toast" id="toast"></div>
726
+
727
+ <script>
728
+ var BASE = '';
729
+ var MEMORIES = [];
730
+ var TASKS = [];
731
+ var SKILLS = [];
732
+ var HEURISTICS = [];
733
+ var LOGS = [];
734
+ var memTotal = 0;
735
+ var memPage = 1;
736
+ var MEM_PER_PAGE = 20;
737
+ var memRoleFilter = 'all';
738
+ var selectedMemIds = new Set();
739
+ var heurStatusFilter = 'all';
740
+ var heurKindFilter = 'all';
741
+ var heurSort = 'quality';
742
+ var heurPage = 1;
743
+ var HEUR_PER_PAGE = 5;
744
+ var logCatFilter = 'all';
745
+ var currentShareId = null;
746
+ var _memSearchTimer = null;
747
+
748
+ function esc(s){if(s==null)return '';var d=document.createElement('div');d.textContent=String(s);return d.innerHTML;}
749
+
750
+ function trendHtml(trend){
751
+ if(trend==='up')return '<span class="trend-indicator up">&#9650;</span>';
752
+ if(trend==='down')return '<span class="trend-indicator down">&#9660;</span>';
753
+ return '<span class="trend-indicator stable">&#9679;</span>';
754
+ }
755
+
756
+ function showToast(msg,type){
757
+ var el=document.getElementById('toast');
758
+ el.textContent=msg;
759
+ el.className='toast '+(type||'success')+' show';
760
+ clearTimeout(el._t);
761
+ el._t=setTimeout(function(){el.classList.remove('show');},3000);
762
+ }
763
+
764
+ function showLoading(elId){
765
+ var el=document.getElementById(elId);
766
+ if(el)el.innerHTML='<div class="loading-box">加载中…</div>';
767
+ }
768
+
769
+ function showError(elId,msg,retryFn){
770
+ var el=document.getElementById(elId);
771
+ if(el)el.innerHTML='<div class="error-box">'+esc(msg)+'<br><button onclick="'+retryFn+'">重试</button></div>';
772
+ }
773
+
774
+ function api(url,opts){
775
+ return fetch(BASE+url,opts).then(function(r){
776
+ if(!r.ok)throw new Error('HTTP '+r.status);
777
+ return r.json();
778
+ });
779
+ }
780
+
781
+ function updateNavBadges(){
782
+ var el;
783
+ el=document.getElementById('navBadgeMemories');if(el)el.textContent=memTotal||MEMORIES.length||'—';
784
+ el=document.getElementById('navBadgeTasks');if(el)el.textContent=TASKS.length||'—';
785
+ el=document.getElementById('navBadgeSkills');if(el)el.textContent=SKILLS.length||'—';
786
+ el=document.getElementById('navBadgeHeuristics');if(el)el.textContent=HEURISTICS.length||'—';
787
+ }
788
+
789
+ function loadStats(){
790
+ api('/api/stats').then(function(d){
791
+ var el;
792
+ el=document.getElementById('statMemories');if(el)el.textContent=d.memories!=null?d.memories:'—';
793
+ el=document.getElementById('statMemoriesSub');if(el)el.textContent=d.memoriesSub||'';
794
+ el=document.getElementById('statTasks');if(el)el.textContent=d.activeTasks!=null?d.activeTasks:(d.tasks!=null?d.tasks:'—');
795
+ el=document.getElementById('statTasksSub');if(el)el.textContent=d.tasksSub||'已完成 '+(d.completedTasks||0)+' 个';
796
+ el=document.getElementById('statSkills');if(el)el.textContent=d.skills!=null?d.skills:'—';
797
+ el=document.getElementById('statSkillsSub');if(el)el.textContent=d.skillsSub||'';
798
+ el=document.getElementById('statHeuristics');if(el)el.textContent=d.heuristics!=null?d.heuristics:'—';
799
+ el=document.getElementById('statHeuristicsSub');if(el)el.textContent=d.heuristicsSub||'';
800
+ memTotal=d.memories||memTotal;
801
+ updateNavBadges();
802
+ }).catch(function(){});
803
+ }
804
+
805
+ function loadMemories(){
806
+ var params='?page='+memPage+'&pageSize='+MEM_PER_PAGE;
807
+ if(memRoleFilter!=='all')params+='&role='+encodeURIComponent(memRoleFilter);
808
+ var q=(document.getElementById('memSearch')||{}).value||'';
809
+ if(q.trim())params+='&q='+encodeURIComponent(q.trim());
810
+ showLoading('memList');
811
+ api('/api/memories'+params).then(function(d){
812
+ if(Array.isArray(d)){MEMORIES=d;memTotal=d.length;}
813
+ else{MEMORIES=d.items||[];memTotal=d.total||MEMORIES.length;}
814
+ renderMemories();
815
+ updateNavBadges();
816
+ }).catch(function(e){
817
+ showError('memList','加载记忆失败: '+e.message,'loadMemories()');
818
+ });
819
+ }
820
+
821
+ function loadTasks(){
822
+ showLoading('taskList');
823
+ api('/api/tasks').then(function(d){
824
+ TASKS=Array.isArray(d)?d:(d.items||[]);
825
+ renderTasks();
826
+ renderDashboardTimeline();
827
+ updateNavBadges();
828
+ }).catch(function(e){
829
+ showError('taskList','加载任务失败: '+e.message,'loadTasks()');
830
+ });
831
+ }
832
+
833
+ function loadSkills(){
834
+ showLoading('skillGrid');
835
+ api('/api/skills').then(function(d){
836
+ SKILLS=Array.isArray(d)?d:(d.items||[]);
837
+ renderSkills();
838
+ updateNavBadges();
839
+ }).catch(function(e){
840
+ showError('skillGrid','加载技能失败: '+e.message,'loadSkills()');
841
+ });
842
+ }
843
+
844
+ function loadHeuristics(){
845
+ showLoading('heurList');
846
+ api('/api/heuristics').then(function(d){
847
+ HEURISTICS=Array.isArray(d)?d:(d.items||[]);
848
+ renderHeuristics();
849
+ renderDashboardTopHeuristics();
850
+ renderAnalytics();
851
+ updateNavBadges();
852
+ }).catch(function(e){
853
+ showError('heurList','加载经验规则失败: '+e.message,'loadHeuristics()');
854
+ });
855
+ }
856
+
857
+ function loadLogs(){
858
+ showLoading('logList');
859
+ api('/api/logs').then(function(d){
860
+ LOGS=Array.isArray(d)?d:(d.items||[]);
861
+ renderLogs();
862
+ }).catch(function(e){
863
+ showError('logList','加载日志失败: '+e.message,'loadLogs()');
864
+ });
865
+ }
866
+
867
+ function loadConfig(){
868
+ api('/api/config').then(function(cfg){renderSettings(cfg);}).catch(function(e){
869
+ showError('settingsBox','加载配置失败: '+e.message,'loadConfig()');
870
+ });
871
+ }
872
+
873
+ function loadAll(){
874
+ loadStats();
875
+ loadMemories();
876
+ loadTasks();
877
+ loadSkills();
878
+ loadHeuristics();
879
+ loadLogs();
880
+ loadConfig();
881
+ }
882
+
883
+ function renderDashboardTimeline(){
884
+ var tl=document.getElementById('dashTimeline');
885
+ if(!tl)return;
886
+ var evts=TASKS.filter(function(t){return t.skillStatus&&t.skillStatus!=='queued';}).map(function(t){
887
+ var action=t.skillStatus==='generated'?'upgraded':(t.skillStatus==='not_generated'?'skipped':t.skillStatus);
888
+ return{time:(t.endedAt||t.startedAt||'').split(' ')[0],task:t.title,action:action,skill:t.skillName||'—',reason:t.summary?t.summary.slice(0,80):''};
889
+ }).sort(function(a,b){return(b.time||'').localeCompare(a.time||'');}).slice(0,8);
890
+ if(!evts.length){tl.innerHTML='<div style="color:var(--text-muted);font-size:13px;padding:8px 0">暂无进化事件</div>';return;}
891
+ tl.innerHTML=evts.map(function(e){
892
+ var dotClass=e.action==='created'||e.action==='generated'?'green':e.action==='upgraded'?'':'red';
893
+ return '<div class="tl-item"><div class="tl-dot '+dotClass+'"></div><div class="tl-content"><strong>'+esc(e.skill)+'</strong><span class="tl-badge '+e.action+'">'+esc(e.action)+'</span><br>'+esc(e.task)+'</div><div class="tl-time">'+esc(e.time)+' &middot; '+esc(e.reason)+'</div></div>';
894
+ }).join('');
895
+ }
896
+
897
+ function renderDashboardTopHeuristics(){
898
+ var th=document.getElementById('dashTopHeuristics');
899
+ if(!th)return;
900
+ var top3=HEURISTICS.filter(function(h){return h.status==='active';}).sort(function(a,b){return(b.supportTaskCount||0)-(a.supportTaskCount||0);}).slice(0,3);
901
+ if(!top3.length){th.innerHTML='<div style="color:var(--text-muted);font-size:13px;padding:8px 0">暂无活跃经验规则</div>';return;}
902
+ th.innerHTML=top3.map(function(h){
903
+ var links=(h.relatedSkills||[]).map(function(s){return '<a class="heur-skill-link" onclick="openSkillById(\\''+s.id+'\\')">\\u2192 '+esc(s.name)+'</a>';}).join(' ');
904
+ return '<div class="heur-card" style="margin-bottom:10px"><div class="heur-top"><span class="kind-badge '+(h.kind||'')+'">'+esc(h.kind)+'</span><span class="heur-status active-s">active</span></div><div class="heur-field"><div class="heur-field-label">触发条件</div><div class="heur-field-value">'+esc(h.trigger)+'</div></div><div class="heur-field"><div class="heur-field-label">规则</div><div class="heur-field-value">'+esc(h.rule)+'</div></div><div class="heur-meta"><span class="hm-item">\\u2713 被 '+(h.supportTaskCount||0)+' 个任务验证</span>'+links+'</div></div>';
905
+ }).join('');
906
+ }
907
+
908
+ function renderDashboard(){
909
+ renderDashboardTimeline();
910
+ renderDashboardTopHeuristics();
911
+ }
912
+
913
+ function debounceMemSearch(){
914
+ clearTimeout(_memSearchTimer);
915
+ _memSearchTimer=setTimeout(function(){memPage=1;loadMemories();},300);
916
+ }
917
+
918
+ function setMemFilter(f,btn){
919
+ memRoleFilter=f;memPage=1;
920
+ document.querySelectorAll('[data-mf]').forEach(function(b){b.classList.remove('active');});
921
+ btn.classList.add('active');
922
+ loadMemories();
923
+ }
924
+
925
+ function updateBatchBar(){
926
+ var bar=document.getElementById('batchBar');
927
+ if(selectedMemIds.size>0){
928
+ bar.classList.add('show');
929
+ document.getElementById('batchCount').textContent='已选 '+selectedMemIds.size+' 条';
930
+ }else{
931
+ bar.classList.remove('show');
932
+ }
933
+ }
934
+
935
+ function toggleMemCheck(id){
936
+ if(selectedMemIds.has(id))selectedMemIds.delete(id);
937
+ else selectedMemIds.add(id);
938
+ updateBatchBar();
939
+ }
940
+
941
+ function batchSelectAll(){
942
+ MEMORIES.forEach(function(m){selectedMemIds.add(m.id);});
943
+ renderMemoryList();
944
+ updateBatchBar();
945
+ }
946
+
947
+ function batchClearAll(){
948
+ selectedMemIds.clear();
949
+ renderMemoryList();
950
+ updateBatchBar();
951
+ }
952
+
953
+ function batchDelete(){
954
+ if(!confirm('确定删除选中的 '+selectedMemIds.size+' 条记忆吗?'))return;
955
+ var ids=Array.from(selectedMemIds);
956
+ var done=0;
957
+ ids.forEach(function(id){
958
+ api('/api/memories/'+encodeURIComponent(id),{method:'DELETE'}).then(function(){
959
+ done++;if(done===ids.length){selectedMemIds.clear();showToast('已删除 '+ids.length+' 条记忆');loadMemories();updateBatchBar();}
960
+ }).catch(function(){
961
+ done++;if(done===ids.length){showToast('部分删除失败','error');loadMemories();updateBatchBar();}
962
+ });
963
+ });
964
+ }
965
+
966
+ function batchExport(){
967
+ var summaries=MEMORIES.filter(function(m){return selectedMemIds.has(m.id);}).map(function(m){return '['+m.id+'] '+(m.summary||'');}).join('\\n');
968
+ navigator.clipboard.writeText(summaries).then(function(){
969
+ showToast('已复制 '+selectedMemIds.size+' 条记忆摘要到剪贴板');
970
+ }).catch(function(){
971
+ showToast('复制失败','error');
972
+ });
973
+ }
974
+
975
+ function renderMemoryList(){
976
+ var el=document.getElementById('memList');
977
+ if(!MEMORIES.length){el.innerHTML='<div style="color:var(--text-muted);font-size:13px;padding:20px;text-align:center">暂无记忆</div>';return;}
978
+ el.innerHTML=MEMORIES.map(function(m){
979
+ var checked=selectedMemIds.has(m.id)?'checked':'';
980
+ var timeStr=m.time||m.updatedAt||m.createdAt||'';
981
+ return '<div class="mem-card" id="mem-'+esc(m.id)+'">'+
982
+ '<input type="checkbox" class="mem-check" '+checked+' onchange="toggleMemCheck(\\''+esc(m.id)+'\\')">'+
983
+ '<div class="mem-card-body">'+
984
+ '<div class="mem-meta">'+
985
+ '<span class="role-badge '+(m.role||'')+'">'+esc(m.role||'—')+'</span>'+
986
+ '<span class="owner-tag">'+esc(m.owner||'')+'</span>'+
987
+ (m.session?'<span class="mem-session">'+esc(m.session)+'</span>':'')+
988
+ '<span class="mem-id">'+esc(m.id)+'</span>'+
989
+ '<span class="time-tag">'+esc(timeStr)+'</span>'+
990
+ '</div>'+
991
+ '<div class="mem-summary">'+esc(m.summary||m.content||'')+'</div>'+
992
+ '<div class="mem-full" id="memfull-'+esc(m.id)+'">'+esc(m.fullContent||m.content||'')+'</div>'+
993
+ '<div class="mem-actions">'+
994
+ '<button class="mem-act" onclick="toggleMemExpand(\\''+esc(m.id)+'\\')">&#128214; 展开</button>'+
995
+ '<button class="mem-act" onclick="editMemory(\\''+esc(m.id)+'\\')">&#9999;&#65039; 编辑</button>'+
996
+ '<button class="mem-act" onclick="shareMemory(\\''+esc(m.id)+'\\')">&#128279; 共享</button>'+
997
+ '<button class="mem-act danger" onclick="deleteMemory(\\''+esc(m.id)+'\\')">&#128465; 删除</button>'+
998
+ '</div>'+
999
+ '</div>'+
1000
+ '</div>';
1001
+ }).join('');
1002
+ }
1003
+
1004
+ function renderMemories(){
1005
+ renderMemoryList();
1006
+ var total=memTotal;
1007
+ var totalPages=Math.max(1,Math.ceil(total/MEM_PER_PAGE));
1008
+ if(memPage>totalPages)memPage=totalPages;
1009
+ var start=(memPage-1)*MEM_PER_PAGE;
1010
+ var pg=document.getElementById('memPagination');
1011
+ if(totalPages<=1){pg.innerHTML='';return;}
1012
+ var html='<button class="page-btn" onclick="memGoPage('+(memPage-1)+')" '+(memPage<=1?'disabled':'')+'>&lt;</button>';
1013
+ var maxShow=7;
1014
+ var rangeStart=Math.max(1,memPage-Math.floor(maxShow/2));
1015
+ var rangeEnd=Math.min(totalPages,rangeStart+maxShow-1);
1016
+ if(rangeEnd-rangeStart<maxShow-1)rangeStart=Math.max(1,rangeEnd-maxShow+1);
1017
+ if(rangeStart>1){html+='<button class="page-btn" onclick="memGoPage(1)">1</button>';if(rangeStart>2)html+='<span class="page-info">&hellip;</span>';}
1018
+ for(var p=rangeStart;p<=rangeEnd;p++){html+='<button class="page-btn '+(p===memPage?'active':'')+'" onclick="memGoPage('+p+')">'+p+'</button>';}
1019
+ if(rangeEnd<totalPages){if(rangeEnd<totalPages-1)html+='<span class="page-info">&hellip;</span>';html+='<button class="page-btn" onclick="memGoPage('+totalPages+')">'+totalPages+'</button>';}
1020
+ html+='<button class="page-btn" onclick="memGoPage('+(memPage+1)+')" '+(memPage>=totalPages?'disabled':'')+'>&gt;</button>';
1021
+ html+='<span class="page-info">第 '+(start+1)+'-'+Math.min(start+MEM_PER_PAGE,total)+' 条,共 '+total+' 条</span>';
1022
+ pg.innerHTML=html;
1023
+ }
1024
+
1025
+ function memGoPage(p){
1026
+ var totalPages=Math.max(1,Math.ceil(memTotal/MEM_PER_PAGE));
1027
+ memPage=Math.max(1,Math.min(p,totalPages));
1028
+ loadMemories();
1029
+ }
1030
+
1031
+ function toggleMemExpand(id){
1032
+ var el=document.getElementById('memfull-'+id);
1033
+ if(!el)return;
1034
+ var isShow=el.classList.toggle('show');
1035
+ var card=el.closest('.mem-card');
1036
+ if(card){var btn=card.querySelector('.mem-act');if(btn)btn.innerHTML=isShow?'&#128214; 收起':'&#128214; 展开';}
1037
+ }
1038
+
1039
+ function editMemory(id){
1040
+ var m=MEMORIES.find(function(x){return x.id===id;});
1041
+ if(!m)return;
1042
+ var newVal=prompt('编辑记忆摘要:',m.summary||m.content||'');
1043
+ if(newVal!==null&&newVal.trim()){
1044
+ api('/api/memories/'+encodeURIComponent(id),{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify({content:newVal.trim(),summary:newVal.trim()})}).then(function(){
1045
+ showToast('已更新记忆');loadMemories();
1046
+ }).catch(function(e){showToast('更新失败: '+e.message,'error');});
1047
+ }
1048
+ }
1049
+
1050
+ function deleteMemory(id){
1051
+ if(!confirm('确定删除这条记忆吗?'))return;
1052
+ api('/api/memories/'+encodeURIComponent(id),{method:'DELETE'}).then(function(){
1053
+ selectedMemIds.delete(id);showToast('已删除');loadMemories();updateBatchBar();
1054
+ }).catch(function(e){showToast('删除失败: '+e.message,'error');});
1055
+ }
1056
+
1057
+ function shareMemory(id){
1058
+ var m=MEMORIES.find(function(x){return x.id===id;});
1059
+ if(!m)return;
1060
+ currentShareId=id;
1061
+ document.getElementById('sharePreview').textContent=m.summary||m.content||'';
1062
+ document.getElementById('shareSuccess').classList.remove('show');
1063
+ document.getElementById('shareSuccess').innerHTML='';
1064
+ document.getElementById('shareOverlay').classList.add('show');
1065
+ }
1066
+ function closeShareDialog(){document.getElementById('shareOverlay').classList.remove('show');currentShareId=null;}
1067
+ function doShareToHub(){
1068
+ api('/api/memories/share',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:currentShareId,target:'hub'})}).then(function(d){
1069
+ var el=document.getElementById('shareSuccess');
1070
+ el.innerHTML='\\u2705 已成功共享到 Hub!'+(d.url?'<br><a href="'+esc(d.url)+'">'+esc(d.url)+'</a>':'');
1071
+ el.classList.add('show');
1072
+ }).catch(function(e){
1073
+ var el=document.getElementById('shareSuccess');
1074
+ el.innerHTML='\\u274c 共享失败: '+esc(e.message);el.style.color='var(--red)';el.classList.add('show');
1075
+ });
1076
+ }
1077
+ function doShareLink(){
1078
+ api('/api/memories/share',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:currentShareId,target:'link'})}).then(function(d){
1079
+ var link=d.url||d.link||'';
1080
+ var el=document.getElementById('shareSuccess');
1081
+ el.innerHTML='&#128279; 共享链接已生成:<br><a href="'+esc(link)+'">'+esc(link)+'</a>';
1082
+ el.classList.add('show');
1083
+ }).catch(function(e){
1084
+ var el=document.getElementById('shareSuccess');
1085
+ el.innerHTML='\\u274c 生成失败: '+esc(e.message);el.style.color='var(--red)';el.classList.add('show');
1086
+ });
1087
+ }
1088
+ function confirmShare(){
1089
+ var scope=(document.querySelector('input[name="shareScope"]:checked')||{}).value||'private';
1090
+ var anon=document.getElementById('shareAnonymize').checked;
1091
+ api('/api/memories/share',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:currentShareId,scope:scope,anonymize:anon})}).then(function(d){
1092
+ var scopeLabel={private:'仅自己',team:'团队','public':'公开'}[scope]||scope;
1093
+ var el=document.getElementById('shareSuccess');
1094
+ el.innerHTML='\\u2705 共享成功!<br>范围: '+esc(scopeLabel)+(anon?' &middot; 已脱敏':'')+(d.url?'<br><a href="'+esc(d.url)+'">'+esc(d.url)+'</a>':'');
1095
+ el.classList.add('show');
1096
+ }).catch(function(e){showToast('共享失败: '+e.message,'error');});
1097
+ }
1098
+
1099
+ function renderTasks(){
1100
+ var el=document.getElementById('taskList');
1101
+ if(!TASKS.length){el.innerHTML='<div style="color:var(--text-muted);font-size:13px;padding:20px;text-align:center">暂无任务</div>';return;}
1102
+ el.innerHTML=TASKS.map(function(t){
1103
+ var skillBadge=t.skillName?'<span class="skill-status-badge">\\u2192 '+esc(t.skillName)+'</span>':(t.skillStatus==='not_generated'?'<span class="skill-status-badge" style="background:rgba(255,255,255,.05);color:var(--text-muted)">门卫跳过</span>':'');
1104
+ return '<div class="task-card" onclick="openTaskDetail(\\''+esc(t.id)+'\\')"><div class="task-stripe '+(t.status||'')+'"></div><div class="task-body"><div class="task-title">'+esc(t.title)+'</div><div class="task-summary">'+esc(t.summary||'')+'</div><div class="task-footer"><span class="status-badge '+(t.status||'')+'">'+esc(t.status||'')+'</span>'+skillBadge+'<span class="time-tag">'+esc(t.startedAt||t.createdAt||'')+'</span></div></div></div>';
1105
+ }).join('');
1106
+ }
1107
+
1108
+ function renderSkills(){
1109
+ var el=document.getElementById('skillGrid');
1110
+ var q=((document.getElementById('skillSearch')||{}).value||'').toLowerCase();
1111
+ var list=q?SKILLS.filter(function(s){return(s.name||'').toLowerCase().indexOf(q)>=0||(s.description||'').toLowerCase().indexOf(q)>=0;}):SKILLS;
1112
+ if(!list.length){el.innerHTML='<div style="color:var(--text-muted);font-size:13px;padding:20px;text-align:center">'+(q?'未找到匹配技能':'暂无技能')+'</div>';return;}
1113
+ el.innerHTML=list.map(function(s){
1114
+ var qs=s.qualityScore?'<span class="skill-qs">\\u2605 '+s.qualityScore+'</span>':'';
1115
+ var tags=(s.tags||[]).map(function(t){return '<span class="skill-tag">'+esc(t)+'</span>';}).join('');
1116
+ var inst=s.installed?'<span class="installed-badge">已安装</span>':'';
1117
+ var statusClass=s.status==='archived'?'skipped':(s.status==='draft'?'active':'completed');
1118
+ return '<div class="skill-card" onclick="openSkillDetail(\\''+esc(s.id)+'\\')"><div class="skill-header"><span class="skill-name">'+esc(s.name)+'</span>'+qs+'<span class="skill-ver">v'+(s.version||1)+'</span></div><div class="skill-desc">'+esc(s.description||'')+'</div><div class="skill-evo-line"><span class="evo-num">'+(s.heuristicCount||0)+'</span> 条经验规则 &middot; <span class="evo-num">'+(s.failurePatterns||0)+'</span> 个失败模式 &middot; 来自 <span class="evo-num">'+(s.taskCount||0)+'</span> 个任务</div><div class="skill-tags">'+tags+'</div><div class="skill-foot"><span class="status-badge '+statusClass+'">'+esc(s.status||'')+'</span>'+inst+'<span class="time-tag">更新于 '+esc(s.updatedAt||'')+'</span></div></div>';
1119
+ }).join('');
1120
+ }
1121
+
1122
+ function getFilteredHeuristics(){
1123
+ var list=HEURISTICS;
1124
+ if(heurStatusFilter!=='all')list=list.filter(function(h){return h.status===heurStatusFilter;});
1125
+ if(heurKindFilter!=='all')list=list.filter(function(h){return h.kind===heurKindFilter;});
1126
+ list=list.slice();
1127
+ if(heurSort==='quality')list.sort(function(a,b){return(b.qualityScore||0)-(a.qualityScore||0);});
1128
+ else if(heurSort==='support')list.sort(function(a,b){return(b.supportTaskCount||0)-(a.supportTaskCount||0);});
1129
+ else if(heurSort==='validated')list.sort(function(a,b){return(b.lastValidatedAt||'').localeCompare(a.lastValidatedAt||'');});
1130
+ else if(heurSort==='recall')list.sort(function(a,b){return(b.recallCount||0)-(a.recallCount||0);});
1131
+ return list;
1132
+ }
1133
+
1134
+ function renderHeuristics(){
1135
+ var active=HEURISTICS.filter(function(h){return h.status==='active';}).length;
1136
+ var candidate=HEURISTICS.filter(function(h){return h.status==='candidate';}).length;
1137
+ var stale=HEURISTICS.filter(function(h){return h.status==='stale';}).length;
1138
+ document.getElementById('heurStats').innerHTML='<div class="heur-stat"><strong style="color:var(--green)">'+active+'</strong> active</div><div class="heur-stat"><strong style="color:var(--amber)">'+candidate+'</strong> candidate</div><div class="heur-stat"><strong style="color:var(--text-muted)">'+stale+'</strong> stale</div><div class="heur-stat">共 <strong>'+HEURISTICS.length+'</strong> 条</div>';
1139
+
1140
+ var filtered=getFilteredHeuristics();
1141
+ var total=filtered.length;
1142
+ var totalPages=Math.max(1,Math.ceil(total/HEUR_PER_PAGE));
1143
+ if(heurPage>totalPages)heurPage=totalPages;
1144
+ var start=(heurPage-1)*HEUR_PER_PAGE;
1145
+ var pageItems=filtered.slice(start,start+HEUR_PER_PAGE);
1146
+ if(!pageItems.length){document.getElementById('heurList').innerHTML='<div style="color:var(--text-muted);font-size:13px;padding:20px;text-align:center">暂无经验规则</div>';document.getElementById('heurPagination').innerHTML='';return;}
1147
+
1148
+ document.getElementById('heurList').innerHTML=pageItems.map(function(h){
1149
+ var statusClass=h.status==='active'?'active-s':h.status==='candidate'?'candidate-s':h.status==='refined'?'refined-s':'stale-s';
1150
+ var qw=Math.round((h.qualityScore||0)*10);
1151
+ var skillLinks=(h.relatedSkills||[]).map(function(s){return '<a class="heur-skill-link" onclick="openSkillById(\\''+esc(s.id)+'\\')">\\u2192 '+esc(s.name)+'</a>';}).join(' ');
1152
+ var historyHtml=(h.history||[]).map(function(ev){
1153
+ var dotClass='heur-evo-dot-'+(ev.event||'');
1154
+ var labelMap={created:'创建',validated:'验证',refined:'优化',promoted:'晋升'};
1155
+ var label=labelMap[ev.event]||ev.event||'';
1156
+ return '<div class="evo-tl-item"><div class="evo-tl-dot '+dotClass+'">'+(label[0]||'')+'</div><div><div class="evo-tl-title">'+esc(label)+'</div><div class="evo-tl-desc">'+esc(ev.detail||'')+'</div><div class="evo-tl-time">'+esc(ev.time||'')+'</div></div></div>';
1157
+ }).reverse().join('');
1158
+ return '<div class="heur-card"><div class="heur-top"><span class="kind-badge '+(h.kind||'')+'">'+esc(h.kind||'')+'</span>'+skillLinks+'<span class="heur-status '+statusClass+'">'+esc(h.status||'')+'</span></div>'+
1159
+ '<div class="heur-field"><div class="heur-field-label">触发条件</div><div class="heur-field-value">'+esc(h.trigger||'')+'</div></div>'+
1160
+ '<div class="heur-field"><div class="heur-field-label">规则</div><div class="heur-field-value">'+esc(h.rule||'')+'</div></div>'+
1161
+ '<div class="heur-field"><div class="heur-field-label">原因</div><div class="heur-field-value">'+esc(h.why||'')+'</div></div>'+
1162
+ '<div class="heur-meta"><span class="hm-item">质量 <span class="quality-bar"><span class="quality-fill" style="width:'+qw+'%"></span></span> '+(h.qualityScore||0)+trendHtml(h.qualityTrend)+'</span><span class="hm-item">\\u2713 '+(h.supportTaskCount||0)+' 个任务验证</span><span class="hm-item">&#128206; '+(h.evidenceCount||0)+' 条证据</span><span class="hm-item">最近验证 '+(h.lastValidatedAt||'—')+'</span></div>'+
1163
+ '<div class="heur-meta" style="margin-top:4px"><span class="hm-item">被召回 '+(h.recallCount||0)+' 次</span><span class="hm-item" style="color:var(--green)">&middot; helpful '+(h.helpfulCount||0)+'</span><span class="hm-item" style="color:var(--red)">&middot; misleading '+(h.misleadingCount||0)+'</span></div>'+
1164
+ (h.evidence?'<button class="heur-evidence-toggle" onclick="toggleEvidenceOrHistory(this)">展开证据</button><div class="heur-evidence">'+esc(h.evidence)+'</div>':'')+
1165
+ (historyHtml?'<button class="heur-evidence-toggle" onclick="toggleEvidenceOrHistory(this)">查看演化历史</button><div class="heur-mini-timeline"><div class="evo-timeline">'+historyHtml+'</div></div>':'')+
1166
+ '<div class="heur-actions"><button class="heur-act-btn" onclick="heurAction(\\''+esc(h.id)+'\\',\\'pin\\')">&#128204; 置顶</button><button class="heur-act-btn" onclick="heurAction(\\''+esc(h.id)+'\\',\\'archive\\')">&#128230; 归档</button><button class="heur-act-btn" onclick="heurAction(\\''+esc(h.id)+'\\',\\'stale\\')">&#128336; 标记过时</button></div></div>';
1167
+ }).join('');
1168
+
1169
+ var pg=document.getElementById('heurPagination');
1170
+ if(totalPages<=1){pg.innerHTML='';return;}
1171
+ var html='<button class="page-btn" onclick="heurGoPage('+(heurPage-1)+')" '+(heurPage<=1?'disabled':'')+'>&lt;</button>';
1172
+ for(var p=1;p<=totalPages;p++){html+='<button class="page-btn '+(p===heurPage?'active':'')+'" onclick="heurGoPage('+p+')">'+p+'</button>';}
1173
+ html+='<button class="page-btn" onclick="heurGoPage('+(heurPage+1)+')" '+(heurPage>=totalPages?'disabled':'')+'>&gt;</button>';
1174
+ html+='<span class="page-info">第 '+(start+1)+'-'+Math.min(start+HEUR_PER_PAGE,total)+' 条,共 '+total+' 条</span>';
1175
+ pg.innerHTML=html;
1176
+ }
1177
+
1178
+ function heurGoPage(p){var filtered=getFilteredHeuristics();var totalPages=Math.max(1,Math.ceil(filtered.length/HEUR_PER_PAGE));heurPage=Math.max(1,Math.min(p,totalPages));renderHeuristics();}
1179
+
1180
+ function toggleEvidenceOrHistory(btn){
1181
+ var el=btn.nextElementSibling;
1182
+ if(!el)return;
1183
+ var isEvidence=el.classList.contains('heur-evidence');
1184
+ el.classList.toggle('show');
1185
+ if(isEvidence){btn.textContent=el.classList.contains('show')?'收起证据':'展开证据';}
1186
+ else{btn.textContent=el.classList.contains('show')?'收起演化历史':'查看演化历史';}
1187
+ }
1188
+
1189
+ function heurAction(id,action){
1190
+ var statusMap={pin:'active',archive:'archived',stale:'stale'};
1191
+ var newStatus=statusMap[action]||action;
1192
+ api('/api/heuristics/'+encodeURIComponent(id),{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify({status:newStatus})}).then(function(){
1193
+ showToast('操作成功');loadHeuristics();
1194
+ }).catch(function(e){showToast('操作失败: '+e.message,'error');});
1195
+ }
1196
+
1197
+ function renderAnalytics(){
1198
+ if(!TASKS.length&&!HEURISTICS.length)return;
1199
+ var generated=TASKS.filter(function(t){return t.skillStatus==='generated';}).length;
1200
+ var totalTasks=TASKS.length;
1201
+ var evoRate=totalTasks?(generated/totalTasks*100).toFixed(0)+'%':'—';
1202
+ var activeH=HEURISTICS.filter(function(h){return h.status==='active';});
1203
+ var avgQ=activeH.length?(activeH.reduce(function(s,h){return s+(h.qualityScore||0);},0)/activeH.length).toFixed(1):'—';
1204
+ var el;
1205
+ el=document.getElementById('anaEvoRate');if(el)el.textContent=evoRate;
1206
+ el=document.getElementById('anaActiveRules');if(el)el.textContent=activeH.length;
1207
+ el=document.getElementById('anaAvgQuality');if(el)el.textContent=avgQ;
1208
+
1209
+ var evts=TASKS.filter(function(t){return t.skillStatus&&t.skillStatus!=='queued';}).map(function(t){
1210
+ var action=t.skillStatus==='generated'?'upgraded':(t.skillStatus==='not_generated'?'skipped':t.skillStatus);
1211
+ return{time:(t.endedAt||t.startedAt||'').split(' ')[0],task:t.title,action:action,skill:t.skillName||'—',reason:t.summary?t.summary.slice(0,60):''};
1212
+ }).sort(function(a,b){return(b.time||'').localeCompare(a.time||'');});
1213
+
1214
+ var weekMap={};
1215
+ evts.forEach(function(e){var w=e.time?e.time.slice(0,7):'?';weekMap[w]=(weekMap[w]||0)+1;});
1216
+ var weeks=Object.keys(weekMap).sort().slice(-8);
1217
+ var chartData=weeks.map(function(w){return{label:w.slice(5),value:weekMap[w]};});
1218
+ var maxV=Math.max.apply(null,chartData.map(function(d){return d.value;}).concat([1]));
1219
+ var chartEl=document.getElementById('evoChart');
1220
+ if(chartEl&&chartData.length){
1221
+ chartEl.innerHTML=chartData.map(function(d){
1222
+ var h=Math.max((d.value/maxV)*120,4);
1223
+ return '<div class="chart-col"><div class="chart-val">'+d.value+'</div><div class="chart-bar" style="height:'+h+'px"></div><div class="chart-label">'+esc(d.label)+'</div></div>';
1224
+ }).join('');
1225
+ }else if(chartEl){chartEl.innerHTML='<div style="color:var(--text-muted);font-size:13px;padding:20px;text-align:center">暂无数据</div>';}
1226
+
1227
+ var tb=document.querySelector('#evoTable tbody');
1228
+ if(tb){
1229
+ tb.innerHTML=evts.slice(0,20).map(function(e){
1230
+ return '<tr><td>'+esc(e.time)+'</td><td>'+esc(e.task)+'</td><td><span class="tl-badge '+e.action+'">'+esc(e.action)+'</span></td><td>'+esc(e.skill)+'</td><td style="font-size:11px;color:var(--text-muted)">'+esc(e.reason)+'</td></tr>';
1231
+ }).join('');
1232
+ }
1233
+ renderHeurEffAnalytics();
1234
+ }
1235
+
1236
+ function renderHeurEffAnalytics(){
1237
+ if(!HEURISTICS.length)return;
1238
+ var totalRecall=HEURISTICS.reduce(function(s,h){return s+(h.recallCount||0);},0);
1239
+ var totalHelpful=HEURISTICS.reduce(function(s,h){return s+(h.helpfulCount||0);},0);
1240
+ var totalMisleading=HEURISTICS.reduce(function(s,h){return s+(h.misleadingCount||0);},0);
1241
+ var activeH=HEURISTICS.filter(function(h){return h.status==='active';});
1242
+ var avgQuality=activeH.length?(activeH.reduce(function(s,h){return s+(h.qualityScore||0);},0)/activeH.length).toFixed(1):'—';
1243
+ var helpfulRate=totalRecall?(totalHelpful/totalRecall*100).toFixed(0):0;
1244
+ var misleadingRate=totalRecall?(totalMisleading/totalRecall*100).toFixed(0):0;
1245
+
1246
+ document.getElementById('heurEffStats').innerHTML=
1247
+ '<div class="stat-card pri"><div class="sc-label">总召回次数</div><div class="sc-value">'+totalRecall+'</div><div class="sc-sub">所有 heuristic 被召回的总次数</div></div>'+
1248
+ '<div class="stat-card green"><div class="sc-label">Helpful 率</div><div class="sc-value">'+helpfulRate+'%</div><div class="sc-sub">'+totalHelpful+' / '+totalRecall+' 次被标为有用</div></div>'+
1249
+ '<div class="stat-card red"><div class="sc-label">Misleading 率</div><div class="sc-value">'+misleadingRate+'%</div><div class="sc-sub">'+totalMisleading+' / '+totalRecall+' 次被标为误导</div></div>'+
1250
+ '<div class="stat-card amber"><div class="sc-label">活跃规则平均质量分</div><div class="sc-value">'+avgQuality+'</div><div class="sc-sub">'+activeH.length+' 条活跃规则</div></div>';
1251
+
1252
+ var effTb=document.querySelector('#heurEffTable tbody');
1253
+ if(effTb){
1254
+ effTb.innerHTML=HEURISTICS.map(function(h){
1255
+ var truncTrigger=(h.trigger||'').length>30?(h.trigger||'').slice(0,30)+'\\u2026':(h.trigger||'');
1256
+ return '<tr><td style="font-family:var(--mono);font-size:11px">'+esc(h.id)+'</td><td><span class="kind-badge '+(h.kind||'')+'" style="font-size:9px;padding:1px 6px">'+esc(h.kind||'')+'</span></td><td title="'+esc(h.trigger||'')+'">'+esc(truncTrigger)+'</td><td>'+(h.recallCount||0)+'</td><td style="color:var(--green)">'+(h.helpfulCount||0)+'</td><td style="color:var(--red)">'+(h.misleadingCount||0)+'</td><td>'+(h.qualityScore||0)+'</td><td>'+trendHtml(h.qualityTrend)+'</td></tr>';
1257
+ }).join('');
1258
+ }
1259
+ }
1260
+
1261
+ function renderLogs(filter){
1262
+ filter=filter||logCatFilter;
1263
+ logCatFilter=filter;
1264
+ var filtered=filter==='all'?LOGS:LOGS.filter(function(l){return l.cat===filter;});
1265
+ if(!filtered.length){document.getElementById('logList').innerHTML='<div style="color:var(--text-muted);font-size:13px;padding:20px;text-align:center">暂无日志</div>';return;}
1266
+ var catLabelMap={memory_add:'Memory Add',memory_search:'Memory Search',skill:'Skill',task:'Task',system:'System'};
1267
+ document.getElementById('logList').innerHTML=filtered.map(function(l,idx){
1268
+ var toolTag=l.tool?'<span class="log-tool">'+esc(l.tool)+'</span>':'';
1269
+ var catLabel=catLabelMap[l.cat]||l.cat||'';
1270
+ var timeStr=l.time||(l.timestamp?new Date(l.timestamp).toLocaleString():'');
1271
+ var shortTime=(timeStr.split(' ')[1]||timeStr);
1272
+
1273
+ if(l.recallDetail){
1274
+ var rd=l.recallDetail;
1275
+ var uid='logrd-'+idx;
1276
+ var detailHtml='<div class="log-detail-query">查询: <span>"'+esc(rd.query||'')+'"</span></div>';
1277
+ detailHtml+='<div class="log-detail-funnel"><span class="funnel-step">初筛 <span class="funnel-num">'+(rd.llmFilterInput||rd.initTotal||0)+'</span> 条</span><span class="funnel-step"><span class="funnel-arrow">\\u2192</span> LLM 过滤</span><span class="funnel-step"><span class="funnel-arrow">\\u2192</span> 保留 <span class="funnel-num">'+(rd.llmFilterSelected||0)+'</span> 条</span></div>';
1278
+ if(rd.memoryCandidates&&rd.memoryCandidates.length){
1279
+ detailHtml+='<div class="recall-group"><div class="recall-group-title">记忆候选 ('+rd.memoryCandidates.length+')</div>';
1280
+ detailHtml+=rd.memoryCandidates.map(function(c){
1281
+ var sel=c.selected;
1282
+ return '<div class="recall-candidate '+(sel?'selected':'dropped')+'"><span class="recall-label">\\ud83d\\udcc4 '+esc(c.summary||'')+'</span><div class="recall-bar"><div class="recall-bar-fill '+(sel?'selected':'dropped')+'" style="width:'+Math.round((c.score||0)*100)+'%"></div></div><span class="recall-score">'+(c.score||0).toFixed(2)+'</span><span class="recall-tag '+(sel?'sel':'drop')+'">'+(sel?'注入':'丢弃')+'</span>'+(c.reason?'<span class="recall-reason">'+esc(c.reason)+'</span>':'')+'</div>';
1283
+ }).join('');
1284
+ detailHtml+='</div>';
1285
+ }
1286
+ if(rd.heuristicCandidates&&rd.heuristicCandidates.length){
1287
+ detailHtml+='<div class="recall-group"><div class="recall-group-title">经验规则候选 ('+rd.heuristicCandidates.length+')</div>';
1288
+ detailHtml+=rd.heuristicCandidates.map(function(c){
1289
+ var sel=c.selected;
1290
+ return '<div class="recall-candidate '+(sel?'selected':'dropped')+'"><span class="recall-label">\\ud83d\\udee1 '+esc(c.trigger||'')+'</span><div class="recall-bar"><div class="recall-bar-fill '+(sel?'selected':'dropped')+'" style="width:'+Math.round((c.score||0)*100)+'%"></div></div><span class="recall-score">'+(c.score||0).toFixed(2)+'</span><span class="recall-tag '+(sel?'sel':'drop')+'">'+(sel?'注入':'丢弃')+'</span>'+(c.reason?'<span class="recall-reason">'+esc(c.reason)+'</span>':'')+'</div>';
1291
+ }).join('');
1292
+ detailHtml+='</div>';
1293
+ }
1294
+ if(rd.skillCandidates&&rd.skillCandidates.length){
1295
+ detailHtml+='<div class="recall-group"><div class="recall-group-title">技能候选 ('+rd.skillCandidates.length+')</div>';
1296
+ detailHtml+=rd.skillCandidates.map(function(c){
1297
+ return '<div class="recall-candidate selected"><span class="recall-label">\\ud83d\\udca1 '+esc(c.name||'')+'</span><div class="recall-bar"><div class="recall-bar-fill selected" style="width:'+Math.round((c.score||0)*100)+'%"></div></div><span class="recall-score">'+(c.score||0).toFixed(2)+'</span><span class="recall-tag sel">注入</span></div>';
1298
+ }).join('');
1299
+ detailHtml+='</div>';
1300
+ }
1301
+ detailHtml+='<div class="recall-summary-line">'+esc(rd.finalSummary||'')+'</div>';
1302
+ return '<div class="log-entry expandable" onclick="toggleLogDetail(\\''+uid+'\\',this)"><span class="log-expand-icon" id="icon-'+uid+'">\\u25b6</span><span class="log-time">'+esc(shortTime)+'</span><span class="log-level '+(l.level||'info')+'">'+(l.level||'info')+'</span><span class="log-cat cat-'+(l.cat||'')+'">'+esc(catLabel)+'</span><span class="log-msg">'+esc(l.msg||'')+'</span>'+toolTag+'</div><div class="log-detail" id="'+uid+'">'+detailHtml+'</div>';
1303
+ }
1304
+ return '<div class="log-entry"><span class="log-time">'+esc(shortTime)+'</span><span class="log-level '+(l.level||'info')+'">'+(l.level||'info')+'</span><span class="log-cat cat-'+(l.cat||'')+'">'+esc(catLabel)+'</span><span class="log-msg">'+esc(l.msg||'')+'</span>'+toolTag+'</div>';
1305
+ }).join('');
1306
+ }
1307
+
1308
+ function toggleLogDetail(uid){
1309
+ var el=document.getElementById(uid);
1310
+ var icon=document.getElementById('icon-'+uid);
1311
+ if(el)el.classList.toggle('show');
1312
+ if(icon)icon.classList.toggle('open');
1313
+ }
1314
+
1315
+ function openSkillById(id){openSkillDetail(id);}
1316
+
1317
+ function openSkillDetail(id){
1318
+ var s=SKILLS.find(function(sk){return sk.id===id;});
1319
+ if(!s){
1320
+ api('/api/skills/'+encodeURIComponent(id)).then(function(d){
1321
+ if(d)renderSkillOverlay(d);
1322
+ }).catch(function(){showToast('加载技能详情失败','error');});
1323
+ return;
1324
+ }
1325
+ renderSkillOverlay(s);
1326
+ }
1327
+
1328
+ function renderSkillOverlay(s){
1329
+ var related=HEURISTICS.filter(function(h){return(h.relatedSkills||[]).some(function(rs){return rs.id===s.id;});});
1330
+ var relatedTasks=TASKS.filter(function(t){return t.skillName===s.name;});
1331
+ var el=document.getElementById('skillDetailContent');
1332
+
1333
+ var tagsHtml=(s.tags||[]).map(function(t){return '<span class="sd-meta-item">'+esc(t)+'</span>';}).join('');
1334
+ var versionsHtml=(s.versions||[]).map(function(v){
1335
+ var dotClass=v.type==='create'?'v1':'';
1336
+ return '<div class="evo-tl-item"><div class="evo-tl-dot '+dotClass+'">v'+(v.v||v.version||'')+'</div><div><div class="evo-tl-title">'+(v.type==='create'?'创建':'升级')+' \\u2014 '+esc(v.taskTitle||v.task||'')+'</div><div class="evo-tl-desc">'+esc(v.summary||'')+'</div><div class="evo-tl-time">'+esc(v.time||v.date||'')+'</div></div></div>';
1337
+ }).reverse().join('');
1338
+
1339
+ var heurHtml=related.length?related.map(function(h){
1340
+ var statusClass=h.status==='active'?'active-s':'candidate-s';
1341
+ return '<div class="heur-card" style="margin-bottom:10px"><div class="heur-top"><span class="kind-badge '+(h.kind||'')+'">'+esc(h.kind||'')+'</span><span class="heur-status '+statusClass+'">'+esc(h.status||'')+'</span></div><div class="heur-field"><div class="heur-field-label">触发条件</div><div class="heur-field-value">'+esc(h.trigger||'')+'</div></div><div class="heur-field"><div class="heur-field-label">规则</div><div class="heur-field-value">'+esc(h.rule||'')+'</div></div><div class="heur-field"><div class="heur-field-label">原因</div><div class="heur-field-value">'+esc(h.why||'')+'</div></div><div class="heur-meta"><span class="hm-item">\\u2713 '+(h.supportTaskCount||0)+' 个任务验证</span><span class="hm-item">最近验证 '+(h.lastValidatedAt||'—')+'</span></div></div>';
1342
+ }).join(''):'<div style="color:var(--text-muted);font-size:13px;padding:12px 0">该技能尚未沉淀经验规则</div>';
1343
+
1344
+ var taskLinksHtml=relatedTasks.length?relatedTasks.map(function(t){return '<a class="task-link" onclick="closeSkillDetail();setTimeout(function(){openTaskDetail(\\''+esc(t.id)+'\\');},200)">\\ud83d\\udccb '+esc(t.title)+' <span style="color:var(--text-muted);font-size:11px">('+(t.status||'')+')</span></a>';}).join('<br>'):'<span style="color:var(--text-muted);font-size:12px">暂无关联任务</span>';
1345
+
1346
+ el.innerHTML=
1347
+ '<div class="overlay-title">'+esc(s.name)+'</div>'+
1348
+ '<div class="overlay-subtitle">'+esc(s.description||'')+'</div>'+
1349
+ '<div class="sd-meta">'+
1350
+ '<span class="sd-meta-item">v'+(s.version||1)+'</span>'+
1351
+ '<span class="sd-meta-item">\\u2605 '+(s.qualityScore||'—')+'</span>'+
1352
+ '<span class="sd-meta-item">'+(s.status||'')+'</span>'+
1353
+ (s.installed?'<span class="sd-meta-item" style="background:var(--green-bg);border-color:rgba(52,211,153,.2);color:var(--green)">已安装</span>':'')+
1354
+ tagsHtml+
1355
+ '</div>'+
1356
+ '<div class="sd-section"><div class="sd-section-title">\\ud83d\\udcd6 操作指南</div><div class="sd-content">'+esc(s.content||s.guide||'暂无内容')+'</div></div>'+
1357
+ '<div class="sd-section"><div class="sd-section-title">\\u26a0\\ufe0f 经验教训 (Lessons Learned)</div>'+heurHtml+'</div>'+
1358
+ (versionsHtml?'<div class="sd-section"><div class="sd-section-title">\\ud83d\\udcc8 进化时间线</div><div class="evo-timeline">'+versionsHtml+'</div></div>':'')+
1359
+ '<div class="sd-section"><div class="sd-section-title">\\ud83d\\udd17 关联任务</div><div style="font-size:13px;color:var(--text-sec);margin-bottom:8px">来自 '+(s.taskCount||0)+' 个任务的经验沉淀<br><span style="font-size:11px;color:var(--text-muted)">创建于 '+(s.createdAt||'—')+' &middot; 最近更新 '+(s.updatedAt||'—')+'</span></div><div>'+taskLinksHtml+'</div></div>';
1360
+ document.getElementById('skillOverlay').classList.add('show');
1361
+ }
1362
+
1363
+ function closeSkillDetail(){document.getElementById('skillOverlay').classList.remove('show');}
1364
+
1365
+ function openTaskDetail(id){
1366
+ var t=TASKS.find(function(tk){return tk.id===id;});
1367
+ if(!t){
1368
+ api('/api/tasks/'+encodeURIComponent(id)).then(function(d){
1369
+ if(d)renderTaskOverlay(d);
1370
+ }).catch(function(){showToast('加载任务详情失败','error');});
1371
+ return;
1372
+ }
1373
+ renderTaskOverlay(t);
1374
+ }
1375
+
1376
+ function renderTaskOverlay(t){
1377
+ var el=document.getElementById('taskDetailContent');
1378
+ var p=t.pipeline||{};
1379
+
1380
+ function step(label,detail,isDone){
1381
+ var cls=isDone?'done':(isDone===false?'skip':'pending');
1382
+ var ic=isDone?'\\u2713':(isDone===false?'\\u2014':'\\u00b7');
1383
+ return '<div class="pipe-step"><div class="pipe-icon '+cls+'">'+ic+'</div><div class="pipe-label">'+label+'</div><div class="pipe-detail">'+(detail||'')+'</div></div>';
1384
+ }
1385
+
1386
+ var pipelineHtml=
1387
+ step('任务摘要',p.summary?'已生成 \\u2713':'待生成',p.summary)+
1388
+ step('门卫筛选(便宜模型)',p.screenResult||'—',p.screen)+
1389
+ step('经验提取(优质模型)',p.expResult||'—',p.experience)+
1390
+ step('技能评估',p.evalResult||'—',p.evaluate)+
1391
+ step('技能生成/升级',p.generate?'已完成':'—',p.generate);
1392
+
1393
+ var recallHtml='';
1394
+ if(t.recall){
1395
+ var r=t.recall;
1396
+ var queryLine=r.query?'<div class="log-detail-query">查询: <span>"'+esc(r.query)+'"</span></div>':'';
1397
+ var funnelLine=r.funnel?'<div class="log-detail-funnel"><span class="funnel-step">初筛 <span class="funnel-num">'+(r.funnel.initTotal||0)+'</span> 条</span><span class="funnel-step"><span class="funnel-arrow">\\u2192</span> LLM 过滤</span><span class="funnel-step"><span class="funnel-arrow">\\u2192</span> 最终保留 <span class="funnel-num">'+(r.funnel.llmSelected||0)+'</span> 条</span></div>':'';
1398
+ var memHtml2=(r.memoryCandidates||[]).map(function(c){
1399
+ var sel=c.selected;
1400
+ return '<div class="recall-candidate '+(sel?'selected':'dropped')+'"><span class="recall-label">\\ud83d\\udcc4 '+esc(c.summary||'')+'</span><div class="recall-bar"><div class="recall-bar-fill '+(sel?'selected':'dropped')+'" style="width:'+Math.round((c.score||0)*100)+'%"></div></div><span class="recall-score">'+(c.score||0).toFixed(2)+'</span><span class="recall-tag '+(sel?'sel':'drop')+'">'+(sel?'注入':'丢弃')+'</span>'+(c.reason?'<span class="recall-reason">'+esc(c.reason)+'</span>':'')+'</div>';
1401
+ }).join('');
1402
+ var heurHtml2=(r.heuristicCandidates||[]).map(function(c){
1403
+ var sel=c.selected;
1404
+ return '<div class="recall-candidate '+(sel?'selected':'dropped')+'"><span class="recall-label">\\ud83d\\udee1 '+esc(c.trigger||'')+'</span><div class="recall-bar"><div class="recall-bar-fill '+(sel?'selected':'dropped')+'" style="width:'+Math.round((c.score||0)*100)+'%"></div></div><span class="recall-score">'+(c.score||0).toFixed(2)+'</span><span class="recall-tag '+(sel?'sel':'drop')+'">'+(sel?'注入':'丢弃')+'</span>'+(c.reason?'<span class="recall-reason">'+esc(c.reason)+'</span>':'')+'</div>';
1405
+ }).join('');
1406
+ var skillHtml2=(r.skillCandidates||[]).map(function(c){
1407
+ return '<div class="recall-candidate selected"><span class="recall-label">\\ud83d\\udca1 '+esc(c.name||'')+'</span><div class="recall-bar"><div class="recall-bar-fill selected" style="width:'+Math.round((c.score||0)*100)+'%"></div></div><span class="recall-score">'+(c.score||0).toFixed(2)+'</span><span class="recall-tag sel">注入</span></div>';
1408
+ }).join('');
1409
+ recallHtml='<div class="sd-section"><div class="sd-section-title">\\ud83d\\udce1 召回注入详情</div><div class="recall-section">'+queryLine+funnelLine+
1410
+ (r.memoryCandidates&&r.memoryCandidates.length?'<div class="recall-group"><div class="recall-group-title">记忆候选 ('+r.memoryCandidates.length+')</div>'+memHtml2+'</div>':'')+
1411
+ (r.heuristicCandidates&&r.heuristicCandidates.length?'<div class="recall-group"><div class="recall-group-title">经验规则候选 ('+r.heuristicCandidates.length+')</div>'+heurHtml2+'</div>':'')+
1412
+ (r.skillCandidates&&r.skillCandidates.length?'<div class="recall-group"><div class="recall-group-title">技能候选 ('+r.skillCandidates.length+')</div>'+skillHtml2+'</div>':'')+
1413
+ '<div class="recall-summary-line">最终注入: '+esc(r.summary||'')+'</div></div></div>';
1414
+ }
1415
+
1416
+ var bubblesHtml=(t.messages||[]).map(function(m){return '<div class="chat-bubble '+(m.role||'')+'">'+esc(m.content||'')+'</div>';}).join('');
1417
+
1418
+ el.innerHTML=
1419
+ '<div class="overlay-title">'+esc(t.title||'')+'</div>'+
1420
+ '<div class="overlay-subtitle"><span class="status-badge '+(t.status||'')+'">'+esc(t.status||'')+'</span> '+esc(t.startedAt||t.createdAt||'')+(t.endedAt?' \\u2192 '+esc(t.endedAt):'')+'</div>'+
1421
+ '<div class="sd-section"><div class="sd-section-title">\\ud83d\\udd04 进化管线状态</div><div class="pipeline-steps">'+pipelineHtml+'</div></div>'+
1422
+ recallHtml+
1423
+ '<div class="sd-section"><div class="sd-section-title">\\ud83d\\udccb 任务摘要</div><div style="font-size:13px;color:var(--text-sec);line-height:1.7">'+esc(t.summary||'')+'</div></div>'+
1424
+ '<div class="sd-section"><div class="sd-section-title">\\ud83d\\udcac 对话记录</div><div class="chat-bubbles">'+bubblesHtml+'</div></div>';
1425
+ document.getElementById('taskOverlay').classList.add('show');
1426
+ }
1427
+
1428
+ function closeTaskDetail(){document.getElementById('taskOverlay').classList.remove('show');}
1429
+
1430
+ function renderSettings(cfg){
1431
+ var box=document.getElementById('settingsBox');
1432
+ if(!cfg){box.innerHTML='<div style="color:var(--text-muted);font-size:13px;padding:20px;text-align:center">无法加载配置</div>';return;}
1433
+ function inp(key,val,w){return '<input class="setting-input" data-cfg="'+key+'" value="'+esc(val!=null?val:'')+'"'+(w?' style="width:'+w+'"':'')+'>';}
1434
+ function tog(key,on){return '<button class="toggle-switch'+(on?' on':'')+'" data-cfg-toggle="'+key+'" onclick="this.classList.toggle(\\'on\\')">'+'</button>';}
1435
+ box.innerHTML=
1436
+ '<div class="settings-section"><h3>模型配置</h3>'+
1437
+ '<div class="setting-row"><div><div class="setting-label">Embedding 模型</div><div class="setting-desc">用于向量化的嵌入模型</div></div>'+inp('embeddingModel',cfg.embeddingModel)+'</div>'+
1438
+ '<div class="setting-row"><div><div class="setting-label">摘要模型(便宜)</div><div class="setting-desc">高频调用:摘要、话题分类、门卫筛选</div></div>'+inp('cheapModel',cfg.cheapModel||cfg.summaryModel)+'</div>'+
1439
+ '<div class="setting-row"><div><div class="setting-label">技能模型(优质)</div><div class="setting-desc">低频调用:经验提取、技能生成/升级</div></div>'+inp('qualityModel',cfg.qualityModel||cfg.skillModel)+'</div>'+
1440
+ '</div>'+
1441
+ '<div class="settings-section"><h3>技能自进化</h3>'+
1442
+ '<div class="setting-row"><div><div class="setting-label">启用技能进化</div><div class="setting-desc">任务完成后自动评估并生成/升级技能</div></div>'+tog('skillEvolutionEnabled',cfg.skillEvolutionEnabled!==false)+'</div>'+
1443
+ '<div class="setting-row"><div><div class="setting-label">启用经验提取</div><div class="setting-desc">从失败尝试和用户反馈中提取结构化经验</div></div>'+tog('experienceExtractionEnabled',cfg.experienceExtractionEnabled!==false)+'</div>'+
1444
+ '<div class="setting-row"><div><div class="setting-label">启用 Heuristic 召回</div><div class="setting-desc">在任务开始时优先注入避坑规则</div></div>'+tog('heuristicRecallEnabled',cfg.heuristicRecallEnabled!==false)+'</div>'+
1445
+ '<div class="setting-row"><div><div class="setting-label">便宜门卫筛选</div><div class="setting-desc">用便宜模型预判任务是否值得经验提取</div></div>'+tog('cheapGuardEnabled',cfg.cheapGuardEnabled!==false)+'</div>'+
1446
+ '<div class="setting-row"><div><div class="setting-label">自动安装技能</div><div class="setting-desc">新生成的技能自动安装到工作区</div></div>'+tog('autoInstallSkills',!!cfg.autoInstallSkills)+'</div>'+
1447
+ '<div class="setting-row"><div><div class="setting-label">最小 Chunk 数</div><div class="setting-desc">触发技能评估的最低对话片段数</div></div>'+inp('minChunkCount',cfg.minChunkCount!=null?cfg.minChunkCount:6,'80px')+'</div>'+
1448
+ '</div>'+
1449
+ '<div class="settings-section"><h3>召回设置</h3>'+
1450
+ '<div class="setting-row"><div><div class="setting-label">最大返回数</div><div class="setting-desc">memory_search 默认返回条数</div></div>'+inp('maxRecallResults',cfg.maxRecallResults!=null?cfg.maxRecallResults:6,'80px')+'</div>'+
1451
+ '<div class="setting-row"><div><div class="setting-label">MMR Lambda</div><div class="setting-desc">相关性 vs 多样性平衡 (0-1)</div></div>'+inp('mmrLambda',cfg.mmrLambda!=null?cfg.mmrLambda:0.7,'80px')+'</div>'+
1452
+ '<div class="setting-row"><div><div class="setting-label">时间衰减半衰期</div><div class="setting-desc">天数,越大则旧记忆权重越高</div></div>'+inp('recencyHalfLife',cfg.recencyHalfLife!=null?cfg.recencyHalfLife:14,'80px')+'</div>'+
1453
+ '</div>'+
1454
+ '<div class="settings-section"><h3>记忆共享</h3>'+
1455
+ '<div class="setting-row"><div><div class="setting-label">Hub 地址</div><div class="setting-desc">MemHub 服务器地址</div></div>'+inp('hubUrl',cfg.hubUrl||'')+'</div>'+
1456
+ '<div class="setting-row"><div><div class="setting-label">启用共享</div><div class="setting-desc">允许将记忆共享到 Hub</div></div>'+tog('sharingEnabled',!!cfg.sharingEnabled)+'</div>'+
1457
+ '<div class="setting-row"><div><div class="setting-label">自动同步</div><div class="setting-desc">自动将新记忆同步到 Hub</div></div>'+tog('autoSync',!!cfg.autoSync)+'</div>'+
1458
+ '<div class="setting-row"><div><div class="setting-label">共享时脱敏</div><div class="setting-desc">共享时自动移除路径、密钥等敏感信息</div></div>'+tog('anonymizeOnShare',cfg.anonymizeOnShare!==false)+'</div>'+
1459
+ '<div class="setting-row"><div><div class="setting-label">Heuristic 共享</div><div class="setting-desc">共享技能时包含关联的经验规则</div></div>'+tog('heuristicSharing',cfg.heuristicSharing!==false)+'</div>'+
1460
+ '<div class="setting-row"><div><div class="setting-label">同步间隔</div><div class="setting-desc">分钟,自动同步的间隔</div></div>'+inp('syncInterval',cfg.syncInterval!=null?cfg.syncInterval:30,'80px')+'</div>'+
1461
+ '</div>'+
1462
+ '<div class="settings-save-row"><button class="btn-save" onclick="saveSettings()">保存设置</button></div>';
1463
+ }
1464
+
1465
+ function saveSettings(){
1466
+ var cfg={};
1467
+ document.querySelectorAll('[data-cfg]').forEach(function(el){cfg[el.getAttribute('data-cfg')]=el.value;});
1468
+ document.querySelectorAll('[data-cfg-toggle]').forEach(function(el){cfg[el.getAttribute('data-cfg-toggle')]=el.classList.contains('on');});
1469
+ ['minChunkCount','maxRecallResults','syncInterval'].forEach(function(k){if(cfg[k]!=null)cfg[k]=Number(cfg[k]);});
1470
+ ['mmrLambda','recencyHalfLife'].forEach(function(k){if(cfg[k]!=null)cfg[k]=parseFloat(cfg[k]);});
1471
+ var btn=document.querySelector('.btn-save');
1472
+ if(btn){btn.disabled=true;btn.textContent='保存中…';}
1473
+ api('/api/config',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify(cfg)}).then(function(){
1474
+ showToast('设置已保存');
1475
+ if(btn){btn.disabled=false;btn.textContent='保存设置';}
1476
+ }).catch(function(e){
1477
+ showToast('保存失败: '+e.message,'error');
1478
+ if(btn){btn.disabled=false;btn.textContent='保存设置';}
1479
+ });
1480
+ }
1481
+
1482
+ var searchTimer=null;
1483
+ var searchInput=document.getElementById('globalSearchInput');
1484
+ var searchResultsEl=document.getElementById('searchResults');
1485
+
1486
+ searchInput.addEventListener('input',function(){
1487
+ clearTimeout(searchTimer);
1488
+ searchTimer=setTimeout(doGlobalSearch,200);
1489
+ });
1490
+
1491
+ searchInput.addEventListener('keydown',function(e){
1492
+ if(e.key==='Escape'){searchResultsEl.classList.remove('show');searchInput.blur();}
1493
+ });
1494
+
1495
+ document.addEventListener('click',function(e){
1496
+ if(!document.getElementById('searchBox').contains(e.target)){
1497
+ searchResultsEl.classList.remove('show');
1498
+ }
1499
+ });
1500
+
1501
+ function doGlobalSearch(){
1502
+ var q=searchInput.value.trim().toLowerCase();
1503
+ if(!q){searchResultsEl.classList.remove('show');return;}
1504
+ var MAX=3;
1505
+ var groups=[];
1506
+
1507
+ var memHits=MEMORIES.filter(function(m){return(m.summary||'').toLowerCase().indexOf(q)>=0||(m.content||'').toLowerCase().indexOf(q)>=0;}).slice(0,MAX);
1508
+ if(memHits.length)groups.push({type:'记忆',icon:'\\ud83d\\udcc4',items:memHits.map(function(m){return{label:m.summary||m.content||m.id,id:m.id,page:'memories'};})});
1509
+
1510
+ var taskHits=TASKS.filter(function(t){return(t.title||'').toLowerCase().indexOf(q)>=0||(t.summary||'').toLowerCase().indexOf(q)>=0;}).slice(0,MAX);
1511
+ if(taskHits.length)groups.push({type:'任务',icon:'\\u2705',items:taskHits.map(function(t){return{label:t.title,id:t.id,page:'tasks',openFn:'openTaskDetail'};})});
1512
+
1513
+ var skillHits=SKILLS.filter(function(s){return(s.name||'').toLowerCase().indexOf(q)>=0||(s.description||'').toLowerCase().indexOf(q)>=0;}).slice(0,MAX);
1514
+ if(skillHits.length)groups.push({type:'技能',icon:'\\ud83d\\udca1',items:skillHits.map(function(s){return{label:s.name+' \\u2014 '+(s.description||'').slice(0,40),id:s.id,page:'skills',openFn:'openSkillDetail'};})});
1515
+
1516
+ var heurHits=HEURISTICS.filter(function(h){return(h.trigger||'').toLowerCase().indexOf(q)>=0||(h.rule||'').toLowerCase().indexOf(q)>=0;}).slice(0,MAX);
1517
+ if(heurHits.length)groups.push({type:'经验规则',icon:'\\ud83d\\udee1',items:heurHits.map(function(h){return{label:h.trigger||'',id:h.id,page:'heuristics'};})});
1518
+
1519
+ if(!groups.length){
1520
+ searchResultsEl.innerHTML='<div class="search-no-results">未找到匹配结果</div>';
1521
+ searchResultsEl.classList.add('show');
1522
+ return;
1523
+ }
1524
+
1525
+ searchResultsEl.innerHTML=groups.map(function(g){
1526
+ var items=g.items.map(function(it,i){return '<div class="search-result-item" data-gtype="'+esc(g.type)+'" data-gidx="'+i+'"><span class="sri-icon">'+g.icon+'</span><span class="sri-text">'+esc(it.label)+'</span><span class="sri-type">'+esc(g.type)+'</span></div>';}).join('');
1527
+ return '<div class="search-result-group"><div class="search-result-group-title">'+g.icon+' '+esc(g.type)+'</div>'+items+'</div>';
1528
+ }).join('');
1529
+
1530
+ searchResultsEl.querySelectorAll('.search-result-item').forEach(function(el){
1531
+ el.addEventListener('click',function(){
1532
+ var gName=el.dataset.gtype;
1533
+ var idx=+el.dataset.gidx;
1534
+ var g=groups.find(function(x){return x.type===gName;});
1535
+ if(!g||!g.items[idx])return;
1536
+ var it=g.items[idx];
1537
+ switchPage(it.page);
1538
+ if(it.openFn){setTimeout(function(){window[it.openFn](it.id);},100);}
1539
+ else if(it.page==='memories'){setTimeout(function(){var mel=document.getElementById('mem-'+it.id);if(mel){mel.scrollIntoView({behavior:'smooth',block:'center'});mel.classList.add('highlight');setTimeout(function(){mel.classList.remove('highlight');},2000);}},100);}
1540
+ searchResultsEl.classList.remove('show');
1541
+ searchInput.value='';
1542
+ });
1543
+ });
1544
+ searchResultsEl.classList.add('show');
1545
+ }
1546
+
1547
+ function switchPage(name){
1548
+ document.querySelectorAll('.page').forEach(function(p){p.classList.remove('active');});
1549
+ document.querySelectorAll('.nav-item').forEach(function(n){n.classList.remove('active');});
1550
+ var page=document.getElementById('page-'+name);
1551
+ if(page)page.classList.add('active');
1552
+ var nav=document.querySelector('.nav-item[data-page="'+name+'"]');
1553
+ if(nav)nav.classList.add('active');
1554
+ }
1555
+
1556
+ document.querySelectorAll('.nav-item').forEach(function(btn){
1557
+ btn.addEventListener('click',function(){switchPage(btn.dataset.page);});
1558
+ });
1559
+
1560
+ function toggleSidebar(){
1561
+ var sb=document.getElementById('sidebar');
1562
+ sb.classList.toggle('collapsed');
1563
+ document.body.classList.toggle('collapsed');
1564
+ }
1565
+
1566
+ function toggleTheme(){
1567
+ var html=document.documentElement;
1568
+ var current=html.getAttribute('data-theme')||'dark';
1569
+ var next=current==='dark'?'light':'dark';
1570
+ html.setAttribute('data-theme',next);
1571
+ var icon=document.getElementById('themeIcon');
1572
+ icon.innerHTML=next==='dark'?'<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>':'<circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>';
1573
+ }
1574
+
1575
+ document.querySelectorAll('#heurStatusFilters .heur-filter').forEach(function(btn){
1576
+ btn.addEventListener('click',function(){
1577
+ document.querySelectorAll('#heurStatusFilters .heur-filter').forEach(function(b){b.classList.remove('active');});
1578
+ btn.classList.add('active');
1579
+ heurStatusFilter=btn.dataset.hf;
1580
+ heurPage=1;
1581
+ renderHeuristics();
1582
+ });
1583
+ });
1584
+
1585
+ document.querySelectorAll('#heurKindFilters .heur-filter').forEach(function(btn){
1586
+ btn.addEventListener('click',function(){
1587
+ document.querySelectorAll('#heurKindFilters .heur-filter').forEach(function(b){b.classList.remove('active');});
1588
+ btn.classList.add('active');
1589
+ heurKindFilter=btn.dataset.kf;
1590
+ heurPage=1;
1591
+ renderHeuristics();
1592
+ });
1593
+ });
1594
+
1595
+ document.getElementById('heurSortSelect').addEventListener('change',function(){
1596
+ heurSort=this.value;
1597
+ heurPage=1;
1598
+ renderHeuristics();
1599
+ });
1600
+
1601
+ document.querySelectorAll('[data-tf]').forEach(function(btn){
1602
+ btn.addEventListener('click',function(){
1603
+ document.querySelectorAll('[data-tf]').forEach(function(b){b.classList.remove('active');});
1604
+ btn.classList.add('active');
1605
+ var f=btn.dataset.tf;
1606
+ var cards=document.querySelectorAll('.task-card');
1607
+ cards.forEach(function(c){
1608
+ var badge=c.querySelector('.status-badge');
1609
+ var status=badge?badge.textContent:'';
1610
+ c.style.display=(f==='all'||status===f)?'':'none';
1611
+ });
1612
+ });
1613
+ });
1614
+
1615
+ document.querySelectorAll('#logFilters .heur-filter').forEach(function(btn){
1616
+ btn.addEventListener('click',function(){
1617
+ document.querySelectorAll('#logFilters .heur-filter').forEach(function(b){b.classList.remove('active');});
1618
+ btn.classList.add('active');
1619
+ renderLogs(btn.dataset.lf);
1620
+ });
1621
+ });
1622
+
1623
+ document.addEventListener('keydown',function(e){
1624
+ if(e.key==='Escape'){closeSkillDetail();closeTaskDetail();closeShareDialog();searchResultsEl.classList.remove('show');}
1625
+ });
1626
+
1627
+ loadAll();
1628
+ </script>
1629
+ </body>
1630
+ </html>`;
1631
+ }