@memtensor/memos-local-openclaw-plugin 1.0.2-beta.4 → 1.0.2-beta.6

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.
Files changed (87) hide show
  1. package/dist/capture/index.js +52 -8
  2. package/dist/capture/index.js.map +1 -1
  3. package/dist/ingest/chunker.d.ts +3 -4
  4. package/dist/ingest/chunker.d.ts.map +1 -1
  5. package/dist/ingest/chunker.js +19 -24
  6. package/dist/ingest/chunker.js.map +1 -1
  7. package/dist/ingest/providers/anthropic.d.ts +3 -1
  8. package/dist/ingest/providers/anthropic.d.ts.map +1 -1
  9. package/dist/ingest/providers/anthropic.js +90 -51
  10. package/dist/ingest/providers/anthropic.js.map +1 -1
  11. package/dist/ingest/providers/bedrock.d.ts +3 -1
  12. package/dist/ingest/providers/bedrock.d.ts.map +1 -1
  13. package/dist/ingest/providers/bedrock.js +90 -51
  14. package/dist/ingest/providers/bedrock.js.map +1 -1
  15. package/dist/ingest/providers/gemini.d.ts +3 -1
  16. package/dist/ingest/providers/gemini.d.ts.map +1 -1
  17. package/dist/ingest/providers/gemini.js +88 -51
  18. package/dist/ingest/providers/gemini.js.map +1 -1
  19. package/dist/ingest/providers/index.d.ts +3 -1
  20. package/dist/ingest/providers/index.d.ts.map +1 -1
  21. package/dist/ingest/providers/index.js +70 -30
  22. package/dist/ingest/providers/index.js.map +1 -1
  23. package/dist/ingest/providers/openai.d.ts +3 -1
  24. package/dist/ingest/providers/openai.d.ts.map +1 -1
  25. package/dist/ingest/providers/openai.js +91 -51
  26. package/dist/ingest/providers/openai.js.map +1 -1
  27. package/dist/ingest/task-processor.d.ts +1 -0
  28. package/dist/ingest/task-processor.d.ts.map +1 -1
  29. package/dist/ingest/task-processor.js +33 -9
  30. package/dist/ingest/task-processor.js.map +1 -1
  31. package/dist/ingest/worker.d.ts.map +1 -1
  32. package/dist/ingest/worker.js +29 -13
  33. package/dist/ingest/worker.js.map +1 -1
  34. package/dist/recall/engine.d.ts.map +1 -1
  35. package/dist/recall/engine.js +19 -14
  36. package/dist/recall/engine.js.map +1 -1
  37. package/dist/skill/bundled-memory-guide.d.ts +1 -5
  38. package/dist/skill/bundled-memory-guide.d.ts.map +1 -1
  39. package/dist/skill/bundled-memory-guide.js +38 -88
  40. package/dist/skill/bundled-memory-guide.js.map +1 -1
  41. package/dist/skill/evaluator.js +1 -1
  42. package/dist/storage/sqlite.d.ts +1 -2
  43. package/dist/storage/sqlite.d.ts.map +1 -1
  44. package/dist/storage/sqlite.js +90 -17
  45. package/dist/storage/sqlite.js.map +1 -1
  46. package/dist/tools/memory-get.d.ts.map +1 -1
  47. package/dist/tools/memory-get.js +1 -3
  48. package/dist/tools/memory-get.js.map +1 -1
  49. package/dist/types.d.ts +2 -2
  50. package/dist/types.d.ts.map +1 -1
  51. package/dist/types.js +1 -1
  52. package/dist/types.js.map +1 -1
  53. package/dist/update-check.d.ts +21 -0
  54. package/dist/update-check.d.ts.map +1 -0
  55. package/dist/update-check.js +111 -0
  56. package/dist/update-check.js.map +1 -0
  57. package/dist/viewer/html.d.ts +1 -1
  58. package/dist/viewer/html.d.ts.map +1 -1
  59. package/dist/viewer/html.js +608 -234
  60. package/dist/viewer/html.js.map +1 -1
  61. package/dist/viewer/server.d.ts +2 -1
  62. package/dist/viewer/server.d.ts.map +1 -1
  63. package/dist/viewer/server.js +201 -90
  64. package/dist/viewer/server.js.map +1 -1
  65. package/index.ts +206 -198
  66. package/openclaw.plugin.json +3 -0
  67. package/package.json +6 -1
  68. package/scripts/postinstall.cjs +69 -2
  69. package/skill/memos-memory-guide/SKILL.md +73 -36
  70. package/src/capture/index.ts +52 -8
  71. package/src/ingest/chunker.ts +22 -30
  72. package/src/ingest/providers/anthropic.ts +100 -53
  73. package/src/ingest/providers/bedrock.ts +101 -53
  74. package/src/ingest/providers/gemini.ts +100 -53
  75. package/src/ingest/providers/index.ts +81 -35
  76. package/src/ingest/providers/openai.ts +101 -53
  77. package/src/ingest/task-processor.ts +29 -8
  78. package/src/ingest/worker.ts +31 -13
  79. package/src/recall/engine.ts +20 -13
  80. package/src/skill/bundled-memory-guide.ts +5 -87
  81. package/src/skill/evaluator.ts +1 -1
  82. package/src/storage/sqlite.ts +93 -21
  83. package/src/tools/memory-get.ts +1 -4
  84. package/src/types.ts +2 -9
  85. package/src/update-check.ts +96 -0
  86. package/src/viewer/html.ts +607 -233
  87. package/src/viewer/server.ts +152 -82
@@ -1,4 +1,6 @@
1
- export const viewerHTML = `<!DOCTYPE html>
1
+ export function viewerHTML(pluginVersion?: string): string {
2
+ const vBadge = pluginVersion ? `<span class="version-badge">v${pluginVersion}</span>` : '';
3
+ return `<!DOCTYPE html>
2
4
  <html lang="zh-CN">
3
5
  <head>
4
6
  <meta charset="UTF-8">
@@ -39,7 +41,7 @@ export const viewerHTML = `<!DOCTYPE html>
39
41
  [data-theme="light"] .auth-screen{background:linear-gradient(135deg,#f0f4ff 0%,#f8f9fb 50%,#eef2ff 100%)}
40
42
  [data-theme="light"] .auth-card{box-shadow:0 25px 50px -12px rgba(0,0,0,.08)}
41
43
  [data-theme="light"] .topbar{background:rgba(255,255,255,.92);border-bottom-color:var(--border);backdrop-filter:blur(8px)}
42
- [data-theme="light"] .session-item .count,[data-theme="light"] .kind-tag,[data-theme="light"] .session-tag{background:rgba(0,0,0,.05)}
44
+ [data-theme="light"] .session-item .count,[data-theme="light"] .session-tag{background:rgba(0,0,0,.05)}
43
45
  [data-theme="light"] .card-content pre{background:#f3f4f6;border-color:var(--border)}
44
46
  [data-theme="light"] .vscore-badge{background:rgba(79,70,229,.06);color:#4f46e5}
45
47
  [data-theme="light"] ::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15)}
@@ -64,10 +66,6 @@ export const viewerHTML = `<!DOCTYPE html>
64
66
  [data-theme="light"] .tool-agg-table td{background:transparent}
65
67
  [data-theme="light"] .tool-agg-table tr:hover td{background:rgba(79,70,229,.03)}
66
68
  [data-theme="light"] .tool-agg-table th{color:#9ca3af}
67
- [data-theme="light"] .breakdown-item{background:#f9fafb;border-color:var(--border)}
68
- [data-theme="light"] .breakdown-item:hover{background:#f3f4f6;border-color:#cbd5e1}
69
- [data-theme="light"] .breakdown-bar-wrap{background:#e5e7eb}
70
- [data-theme="light"] .breakdown-bar{background:linear-gradient(90deg,#4f46e5,#6366f1);box-shadow:none}
71
69
  [data-theme="light"] .range-btn{background:transparent;border-color:var(--border);color:var(--text-sec)}
72
70
  [data-theme="light"] .range-btn.active{background:rgba(79,70,229,.06);color:#4f46e5;border-color:rgba(79,70,229,.2)}
73
71
  [data-theme="light"] .range-btn:hover{border-color:#4f46e5;color:#4f46e5}
@@ -110,13 +108,15 @@ input,textarea,select{font-family:inherit;font-size:inherit}
110
108
  .topbar .brand{display:flex;align-items:center;gap:10px;font-weight:700;font-size:15px;color:var(--text);letter-spacing:-.02em;flex-shrink:0}
111
109
  .topbar .brand .icon{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:22px;background:none;border-radius:0}
112
110
  .topbar .brand .sub{font-weight:400;color:var(--text-muted);font-size:11px}
111
+ .version-badge{font-size:10px;font-weight:600;color:var(--text-muted);background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.1);padding:1px 7px;border-radius:6px;margin-left:6px;letter-spacing:.02em;user-select:all}
112
+ [data-theme="light"] .version-badge{background:rgba(0,0,0,.05);border-color:rgba(0,0,0,.08);color:var(--text-sec)}
113
113
  .topbar-center{flex:1;display:flex;justify-content:center}
114
114
  .topbar .actions{display:flex;align-items:center;gap:6px;flex-shrink:0}
115
115
 
116
116
  .main-content{display:flex;flex:1;max-width:1400px;margin:0 auto;width:100%;padding:28px 32px;gap:28px}
117
117
 
118
118
  /* ─── Sidebar ─── */
119
- .sidebar{width:260px;flex-shrink:0}
119
+ .sidebar{width:260px;min-width:260px;flex-shrink:0}
120
120
  .sidebar .stats-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:24px}
121
121
  .stat-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:18px;transition:all .2s}
122
122
  .stat-card:hover{border-color:var(--border-glow);background:var(--bg-card-hover)}
@@ -160,10 +160,9 @@ input,textarea,select{font-family:inherit;font-size:inherit}
160
160
  .role-tag.user{background:var(--pri-glow);color:var(--pri);border:1px solid rgba(99,102,241,.12)}
161
161
  .role-tag.assistant{background:var(--accent-glow);color:var(--accent);border:1px solid rgba(230,57,70,.2)}
162
162
  .role-tag.system{background:var(--amber-bg);color:var(--amber);border:1px solid rgba(245,158,11,.2)}
163
- .kind-tag{padding:4px 10px;border-radius:8px;font-size:11px;color:var(--text-sec);background:rgba(0,0,0,.2);font-weight:500}
164
163
  .card-time{font-size:12px;color:var(--text-sec);display:flex;align-items:center;gap:8px}
165
164
  .session-tag{font-size:11px;font-family:ui-monospace,monospace;color:var(--text-muted);background:rgba(0,0,0,.2);padding:3px 8px;border-radius:6px;cursor:default}
166
- .card-summary{font-size:15px;font-weight:600;color:var(--text);margin-bottom:10px;line-height:1.5;letter-spacing:-.01em}
165
+ .card-summary{font-size:15px;font-weight:600;color:var(--text);margin-bottom:10px;line-height:1.5;letter-spacing:-.01em;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
167
166
  .card-content{font-size:13px;color:var(--text-sec);line-height:1.65;max-height:0;overflow:hidden;transition:max-height .3s ease}
168
167
  .card-content.show{max-height:600px;overflow-y:auto}
169
168
  .card-content pre{white-space:pre-wrap;word-break:break-all;background:rgba(0,0,0,.25);padding:14px;border-radius:10px;font-size:12px;font-family:ui-monospace,monospace;margin-top:10px;border:1px solid var(--border);color:var(--text-sec)}
@@ -208,6 +207,9 @@ input,textarea,select{font-family:inherit;font-size:inherit}
208
207
  .modal-meta-row{display:flex;flex-wrap:wrap;gap:12px;font-size:11px;color:var(--text-sec);padding:8px 0;border-top:1px dashed var(--border)}
209
208
  [data-theme="light"] .merge-history{background:rgba(0,0,0,.04)}
210
209
  [data-theme="light"] .merge-history-item{border-bottom-color:rgba(0,0,0,.06)}
210
+ .card-merged-info{margin-top:8px;padding:8px 12px;background:rgba(16,185,129,.06);border:1px dashed rgba(16,185,129,.2);border-radius:8px;font-size:12px;line-height:1.6;color:var(--text-sec)}
211
+ .card-merged-label{font-size:10px;font-weight:600;color:#10b981;margin-bottom:4px;display:flex;align-items:center;gap:4px}
212
+ [data-theme="light"] .card-merged-info{background:rgba(16,185,129,.04);border-color:rgba(16,185,129,.15)}
211
213
 
212
214
  /* ─── Buttons ─── */
213
215
  .btn{padding:7px 14px;border-radius:8px;border:1px solid var(--border);background:var(--bg-card);color:var(--text);font-size:13px;font-weight:500;transition:all .18s ease;display:inline-flex;align-items:center;gap:5px;white-space:nowrap}
@@ -241,6 +243,16 @@ input,textarea,select{font-family:inherit;font-size:inherit}
241
243
  .modal-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:28px}
242
244
 
243
245
  /* ─── Toast ─── */
246
+ .emb-banner{display:flex;align-items:center;gap:10px;padding:12px 20px;font-size:13px;font-weight:500;border-radius:10px;margin:0 32px 0;animation:slideIn .3s ease}
247
+ .emb-banner.warning{background:rgba(245,158,11,.1);color:#d97706;border:1px solid rgba(245,158,11,.25)}
248
+ .emb-banner.error{background:rgba(239,68,68,.1);color:#ef4444;border:1px solid rgba(239,68,68,.25)}
249
+ [data-theme="light"] .emb-banner.warning{background:rgba(245,158,11,.08);color:#b45309}
250
+ [data-theme="light"] .emb-banner.error{background:rgba(239,68,68,.08);color:#dc2626}
251
+ .emb-banner span{flex:1}
252
+ .emb-banner-btn{background:none;border:1px solid currentColor;border-radius:6px;padding:4px 12px;font-size:12px;font-weight:600;color:inherit;cursor:pointer;white-space:nowrap;opacity:.85;transition:opacity .15s}
253
+ .emb-banner-btn:hover{opacity:1}
254
+ .emb-banner-close{background:none;border:none;font-size:18px;color:inherit;cursor:pointer;opacity:.5;padding:0 4px;line-height:1}
255
+ .emb-banner-close:hover{opacity:1}
244
256
  .toast-container{position:fixed;top:80px;right:24px;z-index:1000;display:flex;flex-direction:column;gap:8px}
245
257
  .toast{padding:14px 20px;border-radius:10px;font-size:13px;font-weight:500;box-shadow:var(--shadow-lg);animation:slideIn .3s ease;display:flex;align-items:center;gap:10px;max-width:360px;border:1px solid}
246
258
  .toast.success{background:var(--green-bg);color:var(--green);border-color:rgba(16,185,129,.3)}
@@ -330,8 +342,12 @@ input,textarea,select{font-family:inherit;font-size:inherit}
330
342
  .task-chunk-role.user{color:var(--pri)}
331
343
  .task-chunk-role.assistant{color:var(--green)}
332
344
  .task-chunk-role.tool{color:var(--amber)}
333
- .task-chunk-bubble{padding:12px 16px;border-radius:16px;white-space:pre-wrap;word-break:break-word;max-height:200px;overflow:hidden;position:relative;transition:all .2s}
334
- .task-chunk-bubble.expanded{max-height:none}
345
+ .task-chunk-bubble{padding:12px 16px;border-radius:16px;white-space:pre-wrap;word-break:break-word;max-height:none;overflow:hidden;position:relative;transition:all .2s}
346
+ .task-chunk-bubble.collapsed{max-height:200px}
347
+ .task-chunk-expand{display:none;align-items:center;justify-content:center;gap:4px;margin-top:4px;padding:4px 12px;font-size:12px;font-weight:600;color:var(--text-sec);cursor:pointer;user-select:none;border-radius:8px;transition:all .15s}
348
+ .task-chunk-expand:hover{color:var(--pri);background:rgba(99,102,241,.08)}
349
+ .task-chunk-expand .expand-arrow{display:inline-block;font-size:10px;transition:transform .2s}
350
+ .task-chunk-expand.is-expanded .expand-arrow{transform:rotate(180deg)}
335
351
  .role-user .task-chunk-bubble{background:var(--pri);color:#000;border-bottom-right-radius:4px}
336
352
  .role-assistant .task-chunk-bubble{background:var(--bg-card);border:1px solid var(--border);color:var(--text-sec);border-bottom-left-radius:4px}
337
353
  .role-tool .task-chunk-bubble{background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.2);color:var(--text-sec);border-bottom-left-radius:4px;font-family:'SF Mono',Monaco,Consolas,monospace;font-size:12px}
@@ -421,6 +437,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
421
437
  [data-theme="light"] .nav-tabs .tab.active{background:#fff;border-color:rgba(0,0,0,.1);box-shadow:0 1px 3px rgba(0,0,0,.08);color:var(--text)}
422
438
  .analytics-view,.settings-view,.logs-view,.migrate-view{display:none;flex:1;min-width:0;flex-direction:column;gap:20px}
423
439
  .analytics-view.show,.settings-view.show,.logs-view.show,.migrate-view.show{display:flex}
440
+ .feed-wrap,.tasks-view,.skills-view,.analytics-view,.settings-view,.logs-view,.migrate-view{max-width:960px}
424
441
 
425
442
  /* ─── Logs ─── */
426
443
  .logs-toolbar{display:flex;align-items:center;justify-content:space-between;padding:8px 0}
@@ -460,7 +477,41 @@ input,textarea,select{font-family:inherit;font-size:inherit}
460
477
  .log-stat-chip.merged{background:rgba(168,85,247,.12);color:#c084fc}
461
478
  .log-stat-chip.errors{background:rgba(248,113,113,.12);color:#f87171}
462
479
  .log-msg-list{margin-top:8px;display:flex;flex-direction:column;gap:4px}
463
- .log-msg-item{display:flex;gap:8px;align-items:flex-start;font-size:11.5px;line-height:1.5;padding:4px 10px;border-radius:6px;background:rgba(255,255,255,.02)}
480
+ .log-msg-item{display:flex;gap:8px;align-items:flex-start;font-size:11.5px;line-height:1.5;padding:4px 10px;border-radius:6px;background:rgba(255,255,255,.02);overflow:hidden}
481
+ .log-msg-item.expanded{flex-wrap:wrap}
482
+ .recall-layers{margin-top:8px;display:flex;flex-direction:column;gap:10px}
483
+ .recall-layer-title{font-size:11px;font-weight:600;color:var(--text-sec);margin-bottom:4px;display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none}
484
+ .recall-layer-title .recall-expand-icon{transition:transform .15s;font-size:9px}
485
+ .recall-layer.expanded .recall-layer-title .recall-expand-icon{transform:rotate(90deg)}
486
+ .recall-count{font-size:10px;font-weight:700;padding:1px 6px;border-radius:10px;background:rgba(99,102,241,.1);color:var(--pri)}
487
+ .recall-items{display:none;flex-direction:column;gap:3px}
488
+ .recall-layer.expanded .recall-items{display:flex}
489
+ .recall-item{font-size:11px;line-height:1.4;padding:4px 8px;border-radius:5px;background:rgba(255,255,255,.02);cursor:pointer}
490
+ .recall-item:hover{background:rgba(99,102,241,.06)}
491
+ [data-theme="light"] .recall-item{background:rgba(0,0,0,.02)}
492
+ [data-theme="light"] .recall-item:hover{background:rgba(99,102,241,.06)}
493
+ .recall-item-head{display:flex;gap:6px;align-items:center}
494
+ .recall-idx{flex-shrink:0;font-size:10px;font-weight:600;color:var(--text-muted);min-width:14px;text-align:right}
495
+ .recall-score{flex-shrink:0;font-family:'SF Mono',Consolas,monospace;font-size:10px;font-weight:600;padding:1px 5px;border-radius:4px}
496
+ .recall-score.high{background:rgba(34,197,94,.12);color:#22c55e}
497
+ .recall-score.mid{background:rgba(251,191,36,.12);color:#f59e0b}
498
+ .recall-score.low{background:rgba(248,113,113,.1);color:var(--text-muted)}
499
+ .recall-summary-short{flex:1;color:var(--text-sec);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
500
+ .recall-expand-icon{flex-shrink:0;font-size:10px;color:var(--text-muted);transition:transform .15s}
501
+ .recall-item.expanded .recall-expand-icon{transform:rotate(90deg)}
502
+ .recall-summary-full{display:none;margin-top:4px;padding:6px 8px 4px 28px;font-size:11px;line-height:1.5;color:var(--text);word-break:break-word;border-top:1px dashed var(--border)}
503
+ .recall-item.expanded .recall-summary-full{display:block}
504
+ .recall-layer.filtered .recall-layer-title{color:var(--pri)}
505
+ .recall-layer.filtered.empty .recall-layer-title{color:var(--text-muted)}
506
+ .recall-more{font-size:10px;color:var(--text-muted);padding:2px 8px}
507
+ .recall-detail{padding:4px 0}
508
+ .recall-detail-section{margin-bottom:10px}
509
+ .recall-detail-title{font-size:11px;font-weight:600;color:var(--text-sec);margin-bottom:6px;padding-bottom:4px;border-bottom:1px dashed var(--border);cursor:pointer;user-select:none;display:flex;align-items:center;gap:6px}
510
+ .recall-detail-title .recall-expand-icon{transition:transform .15s;font-size:9px}
511
+ .recall-detail-section.expanded .recall-detail-title .recall-expand-icon{transform:rotate(90deg)}
512
+ .recall-detail-section .recall-detail-items{display:none;flex-direction:column;gap:3px}
513
+ .recall-detail-section.expanded .recall-detail-items{display:flex}
514
+ .recall-detail-section.filtered .recall-detail-title{color:var(--pri)}
464
515
  [data-theme="light"] .log-msg-item{background:rgba(0,0,0,.02)}
465
516
  .log-msg-role{flex-shrink:0;font-size:10px;font-weight:600;padding:1px 6px;border-radius:4px;text-transform:uppercase;letter-spacing:.3px}
466
517
  .log-msg-role.user{background:rgba(59,130,246,.12);color:#60a5fa}
@@ -473,6 +524,15 @@ input,textarea,select{font-family:inherit;font-size:inherit}
473
524
  .log-msg-action.merged{color:#c084fc}
474
525
  .log-msg-action.error{color:#f87171}
475
526
  .log-msg-text{color:var(--text);opacity:.85;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}
527
+ .log-msg-text-short{color:var(--text);opacity:.85;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
528
+ .log-msg-text-full{display:none;color:var(--text);opacity:.85;flex:1;min-width:0;word-break:break-word;white-space:pre-wrap}
529
+ .log-msg-item.expanded .log-msg-text-short{display:none}
530
+ .log-msg-item.expanded .log-msg-text-full{display:block}
531
+ .log-msg-item.expanded .recall-expand-icon{transform:rotate(90deg)}
532
+ .log-add-detail{display:flex;flex-direction:column;gap:8px}
533
+ .log-add-msg{display:flex;gap:8px;align-items:flex-start;font-size:12px;line-height:1.6}
534
+ .log-add-msg-role{flex-shrink:0;font-size:10px;font-weight:600;text-transform:uppercase;padding:2px 8px;border-radius:4px;background:rgba(99,102,241,.1);color:var(--pri)}
535
+ .log-add-msg-content{flex:1;min-width:0;word-break:break-word;white-space:pre-wrap;color:var(--text)}
476
536
  .log-detail{display:none;border-top:1px solid var(--border);padding:0}
477
537
  .log-detail.open{display:block}
478
538
  .log-expand-btn{font-size:10px;color:var(--text-sec);opacity:.5;margin-left:auto;transition:transform .2s,opacity .15s;display:inline-block}
@@ -568,18 +628,19 @@ input,textarea,select{font-family:inherit;font-size:inherit}
568
628
  @keyframes migrateFadeIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
569
629
  .feed-wrap{flex:1;min-width:0;display:flex;flex-direction:column}
570
630
  .feed-wrap.hide{display:none}
631
+ .analytics-view{flex-direction:column;gap:20px}
571
632
  .analytics-cards{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
572
- .analytics-card{position:relative;overflow:hidden;border-radius:var(--radius-lg);padding:22px 20px;transition:all .2s ease;border:1px solid var(--border);background:var(--bg-card)}
633
+ .analytics-card{position:relative;overflow:hidden;border-radius:var(--radius-lg);padding:18px 16px;transition:all .2s ease;border:1px solid var(--border);background:var(--bg-card)}
573
634
  .analytics-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:var(--pri);opacity:.5}
574
635
  .analytics-card::after{display:none}
575
636
  .analytics-card:hover{transform:translateY(-2px);box-shadow:var(--shadow);border-color:var(--border-glow)}
576
637
  .analytics-card.green::before{background:var(--green)}
577
638
  .analytics-card.amber::before{background:var(--amber)}
578
- .analytics-card .ac-value{font-size:28px;font-weight:700;letter-spacing:-.03em;color:var(--text);line-height:1;-webkit-text-fill-color:unset;background:none}
639
+ .analytics-card .ac-value{font-size:24px;font-weight:700;letter-spacing:-.03em;color:var(--text);line-height:1;-webkit-text-fill-color:unset;background:none}
579
640
  .analytics-card.green .ac-value{color:var(--green);background:none}
580
641
  .analytics-card.amber .ac-value{color:var(--amber);background:none}
581
642
  .analytics-card .ac-label{font-size:11px;color:var(--text-muted);margin-top:6px;font-weight:500;text-transform:uppercase;letter-spacing:.06em}
582
- .analytics-section{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:22px 24px;position:relative;overflow:hidden}
643
+ .analytics-section{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:18px 20px;position:relative;overflow:hidden}
583
644
  .analytics-section::before{display:none}
584
645
  .analytics-section h3{font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:16px;display:flex;align-items:center;gap:8px}
585
646
  .analytics-section h3 .icon{font-size:14px;opacity:.6}
@@ -625,14 +686,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
625
686
  .tool-agg-table .ms-val.slow{color:var(--accent)}
626
687
  .chart-legend .dot.violet{background:var(--violet)}
627
688
  .chart-legend .dot.green{background:var(--green)}
628
- .breakdown-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:20px}
629
- .breakdown-item{display:flex;flex-direction:column;gap:5px;padding:10px 12px;background:rgba(255,255,255,.02);border-radius:8px;border:1px solid var(--border);transition:all .15s}
630
- .breakdown-item:hover{border-color:var(--border-glow);background:rgba(255,255,255,.04)}
631
- .breakdown-item .bd-top{display:flex;align-items:center;justify-content:space-between}
632
- .breakdown-item .label{font-size:12px;color:var(--text-sec);font-weight:500;text-transform:capitalize}
633
- .breakdown-item .value{font-size:13px;font-weight:600;color:var(--text)}
634
- .breakdown-bar-wrap{height:3px;background:rgba(255,255,255,.06);border-radius:2px;overflow:hidden}
635
- .breakdown-bar{height:100%;border-radius:2px;background:var(--pri);transition:width .5s ease}
636
689
  .metrics-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:16px;flex-wrap:wrap}
637
690
  .range-btn{padding:5px 12px;border-radius:6px;border:1px solid var(--border);background:transparent;color:var(--text-sec);font-size:12px;font-weight:500;cursor:pointer;transition:all .15s}
638
691
  .range-btn:hover{border-color:var(--pri);color:var(--pri)}
@@ -739,7 +792,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
739
792
  <div class="topbar">
740
793
  <div class="brand">
741
794
  <div class="icon"><svg width="24" height="24" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg" style="filter:drop-shadow(0 0 8px rgba(255,77,77,.3))"><defs><linearGradient id="tLG" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#ff4d4d"/><stop offset="100%" stop-color="#991b1b"/></linearGradient></defs><path d="M60 10C30 10 15 35 15 55C15 75 30 95 45 100L45 110L55 110L55 100C55 100 60 102 65 100L65 110L75 110L75 100C90 95 105 75 105 55C105 35 90 10 60 10Z" fill="url(#tLG)"/><path d="M20 45C5 40 0 50 5 60C10 70 20 65 25 55C28 48 25 45 20 45Z" fill="url(#tLG)"/><path d="M100 45C115 40 120 50 115 60C110 70 100 65 95 55C92 48 95 45 100 45Z" fill="url(#tLG)"/><path d="M45 15Q35 5 30 8" stroke="#ff4d4d" stroke-width="2" stroke-linecap="round"/><path d="M75 15Q85 5 90 8" stroke="#ff4d4d" stroke-width="2" stroke-linecap="round"/><circle cx="45" cy="35" r="6" fill="#050810"/><circle cx="75" cy="35" r="6" fill="#050810"/><circle cx="46" cy="34" r="2" fill="#00e5cc"/><circle cx="76" cy="34" r="2" fill="#00e5cc"/></svg></div>
742
- <span data-i18n="title">OpenClaw Memory</span>
795
+ <span data-i18n="title">OpenClaw Memory</span>${vBadge}
743
796
  </div>
744
797
  <div class="topbar-center">
745
798
  <nav class="nav-tabs">
@@ -768,10 +821,12 @@ input,textarea,select{font-family:inherit;font-size:inherit}
768
821
  <div class="stat-card amber"><div class="stat-value" id="statEmbeddings">-</div><div class="stat-label" data-i18n="stat.embeddings">Embeddings</div></div>
769
822
  <div class="stat-card rose"><div class="stat-value" id="statTimeSpan">-</div><div class="stat-label" data-i18n="stat.days">Days</div></div>
770
823
  </div>
771
- <div id="embeddingStatus"></div>
772
- <div class="section-title" data-i18n="sidebar.sessions">Sessions</div>
773
- <div class="session-list" id="sessionList"></div>
774
- <button class="btn btn-sm btn-ghost" style="width:100%;margin-top:20px;justify-content:center;color:var(--text-muted);font-size:11px" onclick="clearAll()" data-i18n="sidebar.clear">\u{1F5D1} Clear All Data</button>
824
+ <div id="sidebarSessionSection">
825
+ <div id="embeddingStatus"></div>
826
+ <div class="section-title" data-i18n="sidebar.sessions">Sessions</div>
827
+ <div class="session-list" id="sessionList"></div>
828
+ <button class="btn btn-sm btn-ghost" style="width:100%;margin-top:20px;justify-content:center;color:var(--text-muted);font-size:11px" onclick="clearAll()" data-i18n="sidebar.clear">\u{1F5D1} Clear All Data</button>
829
+ </div>
775
830
  </div>
776
831
 
777
832
  <div class="feed-wrap" id="feedWrap">
@@ -787,15 +842,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
787
842
  <button class="filter-chip" data-role="assistant" onclick="setRoleFilter(this,'assistant')">Assistant</button>
788
843
  <button class="filter-chip" data-role="system" onclick="setRoleFilter(this,'system')">System</button>
789
844
  <span class="filter-sep"></span>
790
- <select id="filterKind" class="filter-select" onchange="applyFilters()">
791
- <option value="" data-i18n="filter.allkinds">All kinds</option>
792
- <option value="paragraph" data-i18n="filter.paragraph">Paragraph</option>
793
- <option value="code_block" data-i18n="filter.code">Code</option>
794
- <option value="dialog" data-i18n="filter.dialog">Dialog</option>
795
- <option value="list" data-i18n="filter.list">List</option>
796
- <option value="error_stack" data-i18n="filter.error">Error</option>
797
- <option value="command" data-i18n="filter.command">Command</option>
798
- </select>
799
845
  <select id="filterSort" class="filter-select" onchange="applyFilters()">
800
846
  <option value="newest" data-i18n="filter.newest">Newest first</option>
801
847
  <option value="oldest" data-i18n="filter.oldest">Oldest first</option>
@@ -897,7 +943,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
897
943
  </div>
898
944
  </div>
899
945
  <div class="analytics-view" id="analyticsView">
900
- <div class="metrics-toolbar">
946
+ <div class="metrics-toolbar" style="margin-bottom:0">
901
947
  <span style="font-size:12px;color:var(--text-sec);font-weight:600" data-i18n="range">Range</span>
902
948
  <button class="range-btn" data-days="7" onclick="setMetricsDays(7)">7 <span data-i18n="range.days">days</span></button>
903
949
  <button class="range-btn active" data-days="30" onclick="setMetricsDays(30)">30 <span data-i18n="range.days">days</span></button>
@@ -929,16 +975,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
929
975
  <div id="toolAggTable" style="margin-top:20px"></div>
930
976
  </div>
931
977
 
932
- <div class="breakdown-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:24px">
933
- <div class="analytics-section">
934
- <h3><span class="icon">\u{1F464}</span> <span data-i18n="breakdown.role">By Role</span></h3>
935
- <div id="breakdownRole"></div>
936
- </div>
937
- <div class="analytics-section">
938
- <h3><span class="icon">\u{1F4DD}</span> <span data-i18n="breakdown.kind">By Kind</span></h3>
939
- <div id="breakdownKind"></div>
940
- </div>
941
- </div>
942
978
  </div>
943
979
 
944
980
  <!-- ─── Logs View ─── -->
@@ -962,8 +998,11 @@ input,textarea,select{font-family:inherit;font-size:inherit}
962
998
  <div class="settings-view" id="settingsView">
963
999
  <div class="settings-group" id="settingsModelConfig">
964
1000
  <h2 class="settings-group-title"><span data-i18n="settings.modelconfig">Model Configuration</span></h2>
965
- <div class="model-health-bar" id="modelHealthBar">
966
- <div style="font-size:12px;color:var(--text-muted);width:100%">Loading model status...</div>
1001
+ <div class="settings-section">
1002
+ <h3><span class="icon">\u{1F4CA}</span> <span data-i18n="settings.modelhealth">Model Health</span></h3>
1003
+ <div class="model-health-bar" id="modelHealthBar">
1004
+ <div style="font-size:12px;color:var(--text-muted);width:100%">Loading model status...</div>
1005
+ </div>
967
1006
  </div>
968
1007
  <div class="settings-section">
969
1008
  <h3><span class="icon">\u{1F4E1}</span> <span data-i18n="settings.embedding">Embedding Model</span></h3>
@@ -1175,7 +1214,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1175
1214
  </div>
1176
1215
 
1177
1216
  <div id="migrateActions" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap">
1178
- <button class="btn btn-ghost" onclick="migrateScan()" id="migrateScanBtn" data-i18n="migrate.scan">Scan Data Sources</button>
1217
+ <button class="btn" onclick="migrateScan(true)" id="migrateScanBtn" style="background:var(--bg);border:1px solid var(--border);color:var(--text);font-weight:600;padding:7px 18px;cursor:pointer" data-i18n="migrate.scan">Scan Data Sources</button>
1179
1218
  <button class="btn btn-primary" onclick="migrateStart()" id="migrateStartBtn" style="display:none" data-i18n="migrate.start">Start Import</button>
1180
1219
  <span id="migrateConcurrencyRow" style="display:none;align-items:center;gap:6px">
1181
1220
  <span style="font-size:11px;color:var(--text-muted)" data-i18n="migrate.concurrency.label">Concurrent agents</span>
@@ -1316,14 +1355,13 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1316
1355
  <!-- ─── Memory Modal ─── -->
1317
1356
  <div class="modal-overlay" id="modalOverlay">
1318
1357
  <div class="modal">
1319
- <h2 id="modalTitle" data-i18n="modal.new">New Memory</h2>
1358
+ <h2 id="modalTitle" data-i18n="modal.edit">Edit Memory</h2>
1320
1359
  <div class="form-group"><label data-i18n="modal.role">Role</label><select id="mRole"><option value="user">User</option><option value="assistant">Assistant</option><option value="system">System</option></select></div>
1321
1360
  <div class="form-group"><label data-i18n="modal.content">Content</label><textarea id="mContent" rows="4" data-i18n-ph="modal.content.ph" placeholder="Memory content..."></textarea></div>
1322
1361
  <div class="form-group"><label data-i18n="modal.summary">Summary</label><input type="text" id="mSummary" data-i18n-ph="modal.summary.ph" placeholder="Brief summary (optional)"></div>
1323
- <div class="form-group"><label data-i18n="modal.kind">Kind</label><select id="mKind"><option value="paragraph" data-i18n="filter.paragraph">Paragraph</option><option value="code" data-i18n="filter.code">Code</option><option value="dialog" data-i18n="filter.dialog">Dialog</option></select></div>
1324
1362
  <div class="modal-actions">
1325
1363
  <button class="btn btn-ghost" onclick="closeModal()" data-i18n="modal.cancel">Cancel</button>
1326
- <button class="btn btn-primary" id="modalSubmit" onclick="submitModal()" data-i18n="modal.create">Create</button>
1364
+ <button class="btn btn-primary" id="modalSubmit" onclick="submitModal()" data-i18n="modal.save">Save</button>
1327
1365
  </div>
1328
1366
  </div>
1329
1367
  </div>
@@ -1333,6 +1371,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1333
1371
 
1334
1372
  <script>
1335
1373
  let activeSession=null,activeRole='',editingId=null,searchTimer=null,memoryCache={},currentPage=1,totalPages=1,totalCount=0,PAGE_SIZE=40,metricsDays=30;
1374
+ let _embeddingWarningShown=false;
1336
1375
 
1337
1376
  /* ─── i18n ─── */
1338
1377
  const I18N={
@@ -1392,6 +1431,8 @@ const I18N={
1392
1431
  'tasks.untitled':'Untitled Task',
1393
1432
  'tasks.chunks':'Related Memories',
1394
1433
  'tasks.nochunks':'No memories in this task yet.',
1434
+ 'tasks.expand':'Show more',
1435
+ 'tasks.collapse':'Show less',
1395
1436
  'tasks.skipped.default':'This conversation was too brief to generate a summary. It will not appear in search results.',
1396
1437
  'refresh':'\\u21BB Refresh',
1397
1438
  'logout':'Logout',
@@ -1410,13 +1451,6 @@ const I18N={
1410
1451
  'search.meta.text':' text',
1411
1452
  'search.meta.results':' results',
1412
1453
  'filter.all':'All',
1413
- 'filter.allkinds':'All kinds',
1414
- 'filter.paragraph':'Paragraph',
1415
- 'filter.code':'Code',
1416
- 'filter.dialog':'Dialog',
1417
- 'filter.list':'List',
1418
- 'filter.error':'Error',
1419
- 'filter.command':'Command',
1420
1454
  'filter.newest':'Newest first',
1421
1455
  'filter.oldest':'Oldest first',
1422
1456
  'filter.allowners':'All owners',
@@ -1432,6 +1466,8 @@ const I18N={
1432
1466
  'card.delete':'Delete',
1433
1467
  'card.evolved':'Evolved',
1434
1468
  'card.times':'times',
1469
+ 'card.newMessage':'New message',
1470
+ 'card.mergedInfo':'Merged memory',
1435
1471
  'card.updated':'updated',
1436
1472
  'card.evolveHistory':'Evolution History',
1437
1473
  'card.oldSummary':'Old',
@@ -1455,21 +1491,15 @@ const I18N={
1455
1491
  'chart.toolperf':'Tool Response Time',
1456
1492
  'chart.list':'List',
1457
1493
  'chart.search':'Search',
1458
- 'breakdown.role':'By Role',
1459
- 'breakdown.kind':'By Kind',
1460
- 'modal.new':'New Memory',
1461
1494
  'modal.edit':'Edit Memory',
1462
1495
  'modal.role':'Role',
1463
1496
  'modal.content':'Content',
1464
1497
  'modal.content.ph':'Memory content...',
1465
1498
  'modal.summary':'Summary',
1466
1499
  'modal.summary.ph':'Brief summary (optional)',
1467
- 'modal.kind':'Kind',
1468
1500
  'modal.cancel':'Cancel',
1469
- 'modal.create':'Create',
1470
1501
  'modal.save':'Save',
1471
1502
  'modal.err.empty':'Please enter content',
1472
- 'toast.created':'Memory created',
1473
1503
  'toast.updated':'Memory updated',
1474
1504
  'toast.deleted':'Memory deleted',
1475
1505
  'toast.opfail':'Operation failed',
@@ -1484,6 +1514,9 @@ const I18N={
1484
1514
  'confirm.clearall2':'Are you absolutely sure?',
1485
1515
  'embed.on':'Embedding: ',
1486
1516
  'embed.off':'No embedding model',
1517
+ 'embed.warn.local':'Using built-in mini model (384d). Search quality is limited — configure an embedding model in Settings for best results.',
1518
+ 'embed.err.fail':'Embedding model error detected. Check Settings → Model Health.',
1519
+ 'embed.banner.goto':'Go to Settings',
1487
1520
  'lang.switch':'中',
1488
1521
  'tab.logs':'\u{1F4DD} Logs',
1489
1522
  'logs.allTools':'All Tools',
@@ -1493,9 +1526,15 @@ const I18N={
1493
1526
  'logs.output':'OUTPUT',
1494
1527
  'logs.empty':'No logs yet. Logs will appear here when tools are called.',
1495
1528
  'logs.ago':'ago',
1529
+ 'logs.recall.initial':'Initial Retrieval',
1530
+ 'logs.recall.filtered':'LLM Filtered',
1531
+ 'logs.recall.noHits':'No matching memories',
1532
+ 'logs.recall.noneRelevant':'LLM filter: none relevant',
1533
+ 'logs.recall.more':'{n} more...',
1496
1534
  'tab.import':'\u{1F4E5} Import',
1497
1535
  'tab.settings':'\u2699 Settings',
1498
1536
  'settings.modelconfig':'Model Configuration',
1537
+ 'settings.modelhealth':'Model Health',
1499
1538
  'settings.embedding':'Embedding Model',
1500
1539
  'settings.summarizer':'Summarizer Model',
1501
1540
  'settings.skill':'Skill Evolution',
@@ -1520,6 +1559,7 @@ const I18N={
1520
1559
  'settings.test.loading':'Testing...',
1521
1560
  'settings.test.ok':'Connected',
1522
1561
  'settings.test.fail':'Failed',
1562
+ 'settings.session.expired':'Session expired, please refresh the page to log in again',
1523
1563
  'settings.save':'Save Settings',
1524
1564
  'settings.reset':'Reset',
1525
1565
  'settings.saved':'Saved',
@@ -1552,12 +1592,18 @@ const I18N={
1552
1592
  'migrate.scan':'Scan Data Sources',
1553
1593
  'migrate.start':'Start Import',
1554
1594
  'migrate.scanning':'Scanning...',
1595
+ 'migrate.scan.required':'Please scan data sources first',
1596
+ 'migrate.scan.done':'Scan complete \u2014 {n} new items found',
1597
+ 'migrate.imported.hint':'{n} items already imported',
1598
+ 'migrate.reconnect.hint':'--- {n} items processed before page reload ---',
1555
1599
  'migrate.stat.stored':'Stored',
1556
1600
  'migrate.stat.skipped':'Skipped',
1557
1601
  'migrate.stat.merged':'Merged',
1558
1602
  'migrate.stat.errors':'Errors',
1559
1603
  'migrate.phase.sqlite':'Importing memory index...',
1560
1604
  'migrate.phase.sessions':'Importing conversation history...',
1605
+ 'migrate.phase.stopped':'Import stopped',
1606
+ 'migrate.phase.done':'Import completed',
1561
1607
  'migrate.chunks':'chunks',
1562
1608
  'migrate.sessions.count':'sessions, {n} messages',
1563
1609
  'migrate.nodata':'No OpenClaw data found to import.',
@@ -1583,7 +1629,8 @@ const I18N={
1583
1629
  'pp.select.warn':'Please select at least one option.',
1584
1630
  'pp.skill.created':'Skill created',
1585
1631
  'pp.stat.tasks':'Tasks',
1586
- 'pp.stat.skills':'Skills',
1632
+ 'pp.stat.skills':'Evolutions',
1633
+ 'pp.stat.skills.total':'Skills',
1587
1634
  'pp.stat.errors':'Errors',
1588
1635
  'pp.stat.skipped':'Skipped',
1589
1636
  'pp.info.skipped':'{n} sessions already processed, skipping.',
@@ -1643,6 +1690,10 @@ const I18N={
1643
1690
  'skill.save.error':'Failed to save skill: ',
1644
1691
  'update.available':'New version available',
1645
1692
  'update.run':'Run',
1693
+ 'update.btn':'Update now',
1694
+ 'update.installing':'Installing update...',
1695
+ 'update.success':'Update installed! Restarting...',
1696
+ 'update.failed':'Update failed',
1646
1697
  'update.dismiss':'Dismiss'
1647
1698
  },
1648
1699
  zh:{
@@ -1701,6 +1752,8 @@ const I18N={
1701
1752
  'tasks.untitled':'未命名任务',
1702
1753
  'tasks.chunks':'关联记忆',
1703
1754
  'tasks.nochunks':'此任务暂无关联记忆。',
1755
+ 'tasks.expand':'展开全文',
1756
+ 'tasks.collapse':'收起',
1704
1757
  'tasks.skipped.default':'对话内容过少,未生成摘要。该任务不会出现在检索结果中。',
1705
1758
  'refresh':'\\u21BB 刷新',
1706
1759
  'logout':'退出',
@@ -1719,13 +1772,6 @@ const I18N={
1719
1772
  'search.meta.text':' 文本',
1720
1773
  'search.meta.results':' 条结果',
1721
1774
  'filter.all':'全部',
1722
- 'filter.allkinds':'所有类型',
1723
- 'filter.paragraph':'段落',
1724
- 'filter.code':'代码',
1725
- 'filter.dialog':'对话',
1726
- 'filter.list':'列表',
1727
- 'filter.error':'错误',
1728
- 'filter.command':'命令',
1729
1775
  'filter.newest':'最新优先',
1730
1776
  'filter.oldest':'最早优先',
1731
1777
  'filter.allowners':'所有归属',
@@ -1741,6 +1787,8 @@ const I18N={
1741
1787
  'card.delete':'删除',
1742
1788
  'card.evolved':'已演化',
1743
1789
  'card.times':'次',
1790
+ 'card.newMessage':'新消息',
1791
+ 'card.mergedInfo':'合并记忆',
1744
1792
  'card.updated':'更新于',
1745
1793
  'card.evolveHistory':'演化记录',
1746
1794
  'card.oldSummary':'旧摘要',
@@ -1764,21 +1812,15 @@ const I18N={
1764
1812
  'chart.toolperf':'工具响应耗时',
1765
1813
  'chart.list':'列表',
1766
1814
  'chart.search':'搜索',
1767
- 'breakdown.role':'按角色',
1768
- 'breakdown.kind':'按类型',
1769
- 'modal.new':'新建记忆',
1770
1815
  'modal.edit':'编辑记忆',
1771
1816
  'modal.role':'角色',
1772
1817
  'modal.content':'内容',
1773
1818
  'modal.content.ph':'记忆内容...',
1774
1819
  'modal.summary':'摘要',
1775
1820
  'modal.summary.ph':'简要摘要(可选)',
1776
- 'modal.kind':'类型',
1777
1821
  'modal.cancel':'取消',
1778
- 'modal.create':'创建',
1779
1822
  'modal.save':'保存',
1780
1823
  'modal.err.empty':'请输入内容',
1781
- 'toast.created':'记忆已创建',
1782
1824
  'toast.updated':'记忆已更新',
1783
1825
  'toast.deleted':'记忆已删除',
1784
1826
  'toast.opfail':'操作失败',
@@ -1793,6 +1835,9 @@ const I18N={
1793
1835
  'confirm.clearall2':'你真的确定吗?',
1794
1836
  'embed.on':'嵌入模型:',
1795
1837
  'embed.off':'无嵌入模型',
1838
+ 'embed.warn.local':'当前使用内置迷你模型(384维),搜索效果有限。强烈建议在「设置」中配置专用 Embedding 模型以获得最佳效果。',
1839
+ 'embed.err.fail':'Embedding 模型调用异常,请前往「设置 → 模型健康」检查。',
1840
+ 'embed.banner.goto':'前往设置',
1796
1841
  'lang.switch':'EN',
1797
1842
  'tab.logs':'\u{1F4DD} 日志',
1798
1843
  'logs.allTools':'全部工具',
@@ -1802,9 +1847,15 @@ const I18N={
1802
1847
  'logs.output':'输出',
1803
1848
  'logs.empty':'暂无日志。当工具被调用时日志会显示在这里。',
1804
1849
  'logs.ago':'前',
1850
+ 'logs.recall.initial':'初始检索',
1851
+ 'logs.recall.filtered':'LLM 过滤后',
1852
+ 'logs.recall.noHits':'未匹配到记忆',
1853
+ 'logs.recall.noneRelevant':'LLM 过滤:无相关记忆',
1854
+ 'logs.recall.more':'还有 {n} 条...',
1805
1855
  'tab.import':'\u{1F4E5} 导入',
1806
1856
  'tab.settings':'\u2699 设置',
1807
1857
  'settings.modelconfig':'模型配置',
1858
+ 'settings.modelhealth':'模型健康',
1808
1859
  'settings.embedding':'嵌入模型',
1809
1860
  'settings.summarizer':'摘要模型',
1810
1861
  'settings.skill':'技能进化',
@@ -1829,6 +1880,7 @@ const I18N={
1829
1880
  'settings.test.loading':'测试中...',
1830
1881
  'settings.test.ok':'连接成功',
1831
1882
  'settings.test.fail':'连接失败',
1883
+ 'settings.session.expired':'登录已过期,请刷新页面重新登录',
1832
1884
  'settings.save':'保存设置',
1833
1885
  'settings.reset':'重置',
1834
1886
  'settings.saved':'已保存',
@@ -1861,12 +1913,18 @@ const I18N={
1861
1913
  'migrate.scan':'扫描数据源',
1862
1914
  'migrate.start':'开始导入',
1863
1915
  'migrate.scanning':'扫描中...',
1916
+ 'migrate.scan.required':'请先扫描数据源',
1917
+ 'migrate.scan.done':'扫描完成 — 发现 {n} 条新数据可导入',
1918
+ 'migrate.imported.hint':'已导入 {n} 条记忆',
1919
+ 'migrate.reconnect.hint':'--- 页面刷新前已处理 {n} 条 ---',
1864
1920
  'migrate.stat.stored':'已存储',
1865
1921
  'migrate.stat.skipped':'已跳过',
1866
1922
  'migrate.stat.merged':'已合并',
1867
1923
  'migrate.stat.errors':'错误',
1868
1924
  'migrate.phase.sqlite':'正在导入记忆索引...',
1869
1925
  'migrate.phase.sessions':'正在导入对话历史...',
1926
+ 'migrate.phase.stopped':'导入已停止',
1927
+ 'migrate.phase.done':'导入完成',
1870
1928
  'migrate.chunks':'条记忆',
1871
1929
  'migrate.sessions.count':'个会话,{n} 条消息',
1872
1930
  'migrate.nodata':'未找到可导入的 OpenClaw 数据。',
@@ -1892,7 +1950,8 @@ const I18N={
1892
1950
  'pp.select.warn':'请至少选择一个选项。',
1893
1951
  'pp.skill.created':'技能已创建',
1894
1952
  'pp.stat.tasks':'任务',
1895
- 'pp.stat.skills':'技能',
1953
+ 'pp.stat.skills':'进化',
1954
+ 'pp.stat.skills.total':'技能',
1896
1955
  'pp.stat.errors':'错误',
1897
1956
  'pp.stat.skipped':'已跳过',
1898
1957
  'pp.info.skipped':'已有 {n} 个会话处理过,自动跳过。',
@@ -1952,6 +2011,10 @@ const I18N={
1952
2011
  'skill.save.error':'保存技能失败:',
1953
2012
  'update.available':'发现新版本',
1954
2013
  'update.run':'执行命令',
2014
+ 'update.btn':'立即更新',
2015
+ 'update.installing':'正在安装更新...',
2016
+ 'update.success':'更新成功!正在重启...',
2017
+ 'update.failed':'更新失败',
1955
2018
  'update.dismiss':'关闭'
1956
2019
  }
1957
2020
  };
@@ -2068,6 +2131,7 @@ function switchView(view){
2068
2131
  const logsView=document.getElementById('logsView');
2069
2132
  const settingsView=document.getElementById('settingsView');
2070
2133
  const migrateView=document.getElementById('migrateView');
2134
+ const sidebar=document.getElementById('sidebar');
2071
2135
  feedWrap.classList.add('hide');
2072
2136
  analyticsView.classList.remove('show');
2073
2137
  tasksView.classList.remove('show');
@@ -2075,27 +2139,33 @@ function switchView(view){
2075
2139
  logsView.classList.remove('show');
2076
2140
  settingsView.classList.remove('show');
2077
2141
  migrateView.classList.remove('show');
2078
- if(view==='analytics'){
2079
- analyticsView.classList.add('show');
2080
- loadMetrics();
2081
- } else if(view==='tasks'){
2082
- tasksView.classList.add('show');
2083
- loadTasks();
2084
- } else if(view==='skills'){
2085
- skillsView.classList.add('show');
2086
- loadSkills();
2087
- } else if(view==='logs'){
2088
- logsView.classList.add('show');
2089
- loadLogs();
2090
- } else if(view==='settings'){
2091
- settingsView.classList.add('show');
2092
- loadConfig();
2093
- loadModelHealth();
2094
- } else if(view==='import'){
2095
- migrateView.classList.add('show');
2096
- if(!window._migrateRunning) migrateScan();
2097
- } else {
2142
+ const sessionSection=document.getElementById('sidebarSessionSection');
2143
+ if(view==='memories'){
2098
2144
  feedWrap.classList.remove('hide');
2145
+ sessionSection.style.visibility='';
2146
+ sessionSection.style.pointerEvents='';
2147
+ } else if(view==='tasks'||view==='skills'){
2148
+ sessionSection.style.visibility='hidden';
2149
+ sessionSection.style.pointerEvents='none';
2150
+ if(view==='tasks'){tasksView.classList.add('show');loadTasks();}
2151
+ else{skillsView.classList.add('show');loadSkills();}
2152
+ } else {
2153
+ sessionSection.style.visibility='hidden';
2154
+ sessionSection.style.pointerEvents='none';
2155
+ if(view==='analytics'){
2156
+ analyticsView.classList.add('show');
2157
+ loadMetrics();
2158
+ } else if(view==='logs'){
2159
+ logsView.classList.add('show');
2160
+ loadLogs();
2161
+ } else if(view==='settings'){
2162
+ settingsView.classList.add('show');
2163
+ loadConfig();
2164
+ loadModelHealth();
2165
+ } else if(view==='import'){
2166
+ migrateView.classList.add('show');
2167
+ if(!window._migrateRunning) migrateScan(false);
2168
+ }
2099
2169
  }
2100
2170
  }
2101
2171
 
@@ -2162,6 +2232,39 @@ function formatLogTime(ts){
2162
2232
  return y+'-'+m+'-'+day+' '+time;
2163
2233
  }
2164
2234
 
2235
+ function parseMemoryAddEntries(out){
2236
+ var lines=out.split('\\n');
2237
+ var results=[];
2238
+ for(var i=0;i<lines.length;i++){
2239
+ var line=lines[i].trim();
2240
+ if(!line) continue;
2241
+ if(line.startsWith('{')){
2242
+ try{
2243
+ var obj=JSON.parse(line);
2244
+ if(obj.role&&obj.action){results.push({role:obj.role,action:obj.action,summary:obj.summary||'',content:obj.content||'',reason:obj.reason||''});continue;}
2245
+ }catch(e){}
2246
+ }
2247
+ var rm=line.match(/^\\[(\\w+)\\]\\s*([^\u2192]+)\u2192/);
2248
+ if(rm){
2249
+ var role=rm[1],actionRaw=rm[2].trim();
2250
+ var action='stored';
2251
+ if(actionRaw.indexOf('exact-dup')>=0||actionRaw.indexOf('\u23ED')>=0) action='exact-dup';
2252
+ else if(actionRaw.indexOf('dedup')>=0||actionRaw.indexOf('\uD83D\uDD01')>=0) action='dedup';
2253
+ else if(actionRaw.indexOf('merged')>=0||actionRaw.indexOf('\uD83D\uDD00')>=0) action='merged';
2254
+ else if(actionRaw.indexOf('error')>=0||actionRaw.indexOf('\u274C')>=0) action='error';
2255
+ var afterArrow=line.replace(/^\\[\\w+\\]\\s*[^\u2192]+\u2192\\s*/,'');
2256
+ var contentLines=[afterArrow];
2257
+ while(i+1<lines.length&&!lines[i+1].trim().startsWith('[')&&!lines[i+1].trim().startsWith('{')){
2258
+ i++;
2259
+ if(lines[i].trim()) contentLines.push(lines[i]);
2260
+ else contentLines.push('');
2261
+ }
2262
+ results.push({role:role,action:action,summary:'',content:contentLines.join('\\n'),reason:''});
2263
+ }
2264
+ }
2265
+ return results;
2266
+ }
2267
+
2165
2268
  function buildLogSummary(lg){
2166
2269
  let inputObj=null;
2167
2270
  try{inputObj=JSON.parse(lg.input);}catch(_){}
@@ -2169,11 +2272,54 @@ function buildLogSummary(lg){
2169
2272
  const tn=lg.toolName;
2170
2273
  if(tn==='memory_search'&&inputObj){
2171
2274
  const q=inputObj.query||'';
2172
- if(q) html+='<div class="log-summary-query">'+escapeHtml(q.length>200?q.slice(0,200)+'...':q)+'</div>';
2173
- const outLines=(lg.output||'').split('\\n');
2174
- const memCount=outLines.filter(l=>l.match(/^\\d+\\.\\s*\\[/)).length;
2175
- if(memCount>0) html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u{1F4CE} '+memCount+' memories retrieved</div>';
2176
- else if(lg.output&&lg.output.includes('no hits')) html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u2205 No matching memories</div>';
2275
+ if(q) html+='<div class="log-summary-query">'+escapeHtml(q)+'</div>';
2276
+ var recallData=null;
2277
+ try{recallData=JSON.parse(lg.output);}catch(_){}
2278
+ if(recallData&&recallData.candidates){
2279
+ var cands=recallData.candidates||[];
2280
+ var filtered=recallData.filtered||[];
2281
+ if(cands.length===0){
2282
+ html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u2205 '+t('logs.recall.noHits')+'</div>';
2283
+ }else{
2284
+ html+='<div class="recall-layers">';
2285
+ html+='<div class="recall-layer" onclick="this.classList.toggle(\\\'expanded\\\')">';
2286
+ html+='<div class="recall-layer-title"><span class="recall-expand-icon">\u25B6</span>\u{1F50D} '+t('logs.recall.initial')+' <span class="recall-count">'+cands.length+'</span></div>';
2287
+ html+='<div class="recall-items">';
2288
+ cands.forEach(function(c,i){
2289
+ var scoreClass=c.score>=0.7?'high':c.score>=0.5?'mid':'low';
2290
+ var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
2291
+ var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
2292
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2293
+ html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+c.score.toFixed(2)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
2294
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2295
+ html+='</div>';
2296
+ });
2297
+ html+='</div></div>';
2298
+ if(filtered.length>0){
2299
+ html+='<div class="recall-layer filtered" onclick="this.classList.toggle(\\\'expanded\\\')">';
2300
+ html+='<div class="recall-layer-title"><span class="recall-expand-icon">\u25B6</span>\u2705 '+t('logs.recall.filtered')+' <span class="recall-count">'+filtered.length+'</span></div>';
2301
+ html+='<div class="recall-items">';
2302
+ filtered.forEach(function(f){
2303
+ var scoreClass=f.score>=0.7?'high':f.score>=0.5?'mid':'low';
2304
+ var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
2305
+ var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
2306
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2307
+ html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+f.score.toFixed(2)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
2308
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2309
+ html+='</div>';
2310
+ });
2311
+ html+='</div></div>';
2312
+ }else{
2313
+ html+='<div style="font-size:10px;color:var(--text-muted);margin-top:2px">\u26A0 '+t('logs.recall.noneRelevant')+'</div>';
2314
+ }
2315
+ html+='</div>';
2316
+ }
2317
+ }else{
2318
+ var outLines=(lg.output||'').split('\\n');
2319
+ var memCount=outLines.filter(function(l){return l.match(/^\\d+\\.\\s*\\[/)}).length;
2320
+ if(memCount>0) html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u{1F4CE} '+memCount+' memories retrieved</div>';
2321
+ else if(lg.output&&lg.output.includes('no hits')) html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u2205 No matching memories</div>';
2322
+ }
2177
2323
  }else if(tn==='memory_add'&&inputObj){
2178
2324
  const out=lg.output||'';
2179
2325
  const statsMatch=out.match(/^([^\\n]+)/);
@@ -2186,39 +2332,18 @@ function buildLogSummary(lg){
2186
2332
  });
2187
2333
  html+='</div>';
2188
2334
  }
2189
- const outLines=out.split('\\n').filter(l=>l.startsWith('['));
2190
- if(outLines.length>0){
2335
+ var parsed=parseMemoryAddEntries(out);
2336
+ if(parsed.length>0){
2191
2337
  html+='<div class="log-msg-list">';
2192
- outLines.forEach(function(l){
2193
- var rm=l.match(/^\\[(\\w+)\\]\\s*([^\u2192]+)\u2192\\s*(.*)/);
2194
- if(rm){
2195
- var role=rm[1],actionRaw=rm[2].trim(),text=rm[3].trim();
2196
- var actionCls='stored';
2197
- if(actionRaw.indexOf('exact-dup')>=0||actionRaw.indexOf('\u23ED')>=0) actionCls='exact-dup';
2198
- else if(actionRaw.indexOf('dedup')>=0||actionRaw.indexOf('\uD83D\uDD01')>=0) actionCls='dedup';
2199
- else if(actionRaw.indexOf('merged')>=0||actionRaw.indexOf('\uD83D\uDD00')>=0) actionCls='merged';
2200
- else if(actionRaw.indexOf('error')>=0||actionRaw.indexOf('\u274C')>=0) actionCls='error';
2201
- var actionLabel={'stored':'\u2713 stored','exact-dup':'\u23ED skip','dedup':'\uD83D\uDD01 dedup','merged':'\uD83D\uDD00 merged','error':'\u2717 error'}[actionCls]||actionCls;
2202
- html+='<div class="log-msg-item">'+
2203
- '<span class="log-msg-role '+role+'">'+role+'</span>'+
2204
- '<span class="log-msg-action '+actionCls+'">'+actionLabel+'</span>'+
2205
- '<span class="log-msg-text">'+escapeHtml(text.length>150?text.slice(0,150)+'...':text)+'</span>'+
2206
- '</div>';
2207
- }else{
2208
- html+='<div class="log-msg-item"><span class="log-msg-text">'+escapeHtml(l.length>200?l.slice(0,200)+'...':l)+'</span></div>';
2209
- }
2210
- });
2211
- html+='</div>';
2212
- }else if(inputObj.details&&Array.isArray(inputObj.details)&&inputObj.details.length>0){
2213
- html+='<div class="log-msg-list">';
2214
- inputObj.details.forEach(function(d){
2215
- var s=typeof d==='string'?d:String(d);
2216
- var dm=s.match(/^\\[(\\w+)\\]\\s*(.*)/);
2217
- if(dm){
2218
- html+='<div class="log-msg-item"><span class="log-msg-role '+dm[1]+'">'+dm[1]+'</span><span class="log-msg-text">'+escapeHtml(dm[2].length>150?dm[2].slice(0,150)+'...':dm[2])+'</span></div>';
2219
- }else{
2220
- html+='<div class="log-msg-item"><span class="log-msg-text">'+escapeHtml(s.length>150?s.slice(0,150)+'...':s)+'</span></div>';
2221
- }
2338
+ parsed.forEach(function(e){
2339
+ var actionCls=e.action==='exact-dup'?'exact-dup':e.action==='dedup'?'dedup':e.action==='merged'?'merged':e.action==='error'?'error':'stored';
2340
+ var actionLabel={'stored':'\u2713 stored','exact-dup':'\u23ED skip','dedup':'\uD83D\uDD01 dedup','merged':'\uD83D\uDD00 merged','error':'\u2717 error'}[actionCls]||actionCls;
2341
+ var displayText=e.content.split('\\n')[0].trim();
2342
+ html+='<div class="log-msg-item">'+
2343
+ '<span class="log-msg-role '+e.role+'">'+e.role+'</span>'+
2344
+ '<span class="log-msg-action '+actionCls+'">'+actionLabel+'</span>'+
2345
+ '<span class="log-msg-text">'+escapeHtml(displayText)+'</span>'+
2346
+ '</div>';
2222
2347
  });
2223
2348
  html+='</div>';
2224
2349
  }
@@ -2226,11 +2351,53 @@ function buildLogSummary(lg){
2226
2351
  const keys=Object.keys(inputObj);
2227
2352
  keys.slice(0,4).forEach(k=>{
2228
2353
  const v=String(inputObj[k]);
2229
- html+='<span class="log-summary-kv"><span class="kv-label">'+escapeHtml(k)+':</span><span class="kv-val">'+escapeHtml(v.length>60?v.slice(0,60)+'...':v)+'</span></span>';
2354
+ html+='<span class="log-summary-kv"><span class="kv-label">'+escapeHtml(k)+':</span><span class="kv-val">'+escapeHtml(v)+'</span></span>';
2230
2355
  });
2231
2356
  }
2232
2357
  return html;
2233
2358
  }
2359
+ function buildRecallDetailHtml(rd){
2360
+ var html='<div class="recall-detail">';
2361
+ var cands=rd.candidates||[];
2362
+ var filtered=rd.filtered||[];
2363
+ if(cands.length>0){
2364
+ html+='<div class="recall-detail-section" onclick="this.classList.toggle(\\\'expanded\\\')">';
2365
+ html+='<div class="recall-detail-title"><span class="recall-expand-icon">\u25B6</span>\u{1F50D} '+t('logs.recall.initial')+' ('+cands.length+')</div>';
2366
+ html+='<div class="recall-detail-items">';
2367
+ cands.forEach(function(c,i){
2368
+ var scoreClass=c.score>=0.7?'high':c.score>=0.5?'mid':'low';
2369
+ var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
2370
+ var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
2371
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2372
+ html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+c.score.toFixed(3)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
2373
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2374
+ html+='</div>';
2375
+ });
2376
+ html+='</div></div>';
2377
+ }
2378
+ if(filtered.length>0){
2379
+ html+='<div class="recall-detail-section filtered" onclick="this.classList.toggle(\\\'expanded\\\')">';
2380
+ html+='<div class="recall-detail-title"><span class="recall-expand-icon">\u25B6</span>\u2705 '+t('logs.recall.filtered')+' ('+filtered.length+')</div>';
2381
+ html+='<div class="recall-detail-items">';
2382
+ filtered.forEach(function(f,i){
2383
+ var scoreClass=f.score>=0.7?'high':f.score>=0.5?'mid':'low';
2384
+ var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
2385
+ var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
2386
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2387
+ html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+f.score.toFixed(3)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
2388
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2389
+ html+='</div>';
2390
+ });
2391
+ html+='</div></div>';
2392
+ }else if(cands.length>0){
2393
+ html+='<div style="font-size:10px;color:var(--text-muted);margin-top:4px">\u26A0 '+t('logs.recall.noneRelevant')+'</div>';
2394
+ }
2395
+ if(rd.status==='error'&&rd.error){
2396
+ html+='<div style="margin-top:8px;color:var(--accent);font-size:12px">\u274C '+escapeHtml(rd.error)+'</div>';
2397
+ }
2398
+ html+='</div>';
2399
+ return html;
2400
+ }
2234
2401
  function renderLogs(logs){
2235
2402
  const el=document.getElementById('logsList');
2236
2403
  if(!logs.length){
@@ -2243,7 +2410,29 @@ function renderLogs(logs){
2243
2410
  const toolCls=lg.toolName.replace(/[^a-zA-Z0-9_]/g,'_');
2244
2411
  const dur=lg.durationMs<1000?Math.round(lg.durationMs)+'ms':(lg.durationMs/1000).toFixed(1)+'s';
2245
2412
  let inputDisplay='';
2246
- try{const parsed=JSON.parse(lg.input);inputDisplay=JSON.stringify(parsed,null,2);}catch(_){inputDisplay=lg.input;}
2413
+ let inputHtml='';
2414
+ let outputHtml='';
2415
+ try{
2416
+ const parsed=JSON.parse(lg.input);
2417
+ if(lg.toolName==='memory_add'){
2418
+ var addEntries=parseMemoryAddEntries(lg.output||'');
2419
+ if(addEntries.length>0){
2420
+ inputHtml='<div class="log-add-detail">';
2421
+ addEntries.forEach(function(e){
2422
+ inputHtml+='<div class="log-add-msg"><div class="log-add-msg-role">'+escapeHtml(e.role)+'</div><div class="log-add-msg-content">'+escapeHtml(e.content).replace(/\\n/g,'<br>')+'</div></div>';
2423
+ });
2424
+ inputHtml+='</div>';
2425
+ }
2426
+ }else if(parsed.type==='auto_recall'||parsed.type==='tool_call'){
2427
+ inputDisplay=JSON.stringify({query:parsed.query},null,2);
2428
+ }else{
2429
+ inputDisplay=JSON.stringify(parsed,null,2);
2430
+ }
2431
+ }catch(_){inputDisplay=lg.input;}
2432
+ try{
2433
+ var rd2=null;try{rd2=JSON.parse(lg.output);}catch(_e){}
2434
+ if(rd2&&rd2.candidates){outputHtml=buildRecallDetailHtml(rd2);}
2435
+ }catch(_){}
2247
2436
  const summary=buildLogSummary(lg);
2248
2437
  return '<div class="log-entry" id="log-'+i+'">'+
2249
2438
  '<div class="log-header" onclick="toggleLog('+i+')">'+
@@ -2257,11 +2446,11 @@ function renderLogs(logs){
2257
2446
  '<div class="log-detail" id="log-detail-'+i+'">'+
2258
2447
  '<div class="log-io-section">'+
2259
2448
  '<div class="log-io-label">\u25B6 '+t('logs.input')+'</div>'+
2260
- '<pre class="log-io-content">'+escapeHtml(inputDisplay)+'</pre>'+
2449
+ (inputHtml?inputHtml:'<pre class="log-io-content">'+escapeHtml(inputDisplay)+'</pre>')+
2261
2450
  '</div>'+
2262
2451
  '<div class="log-io-section">'+
2263
2452
  '<div class="log-io-label">\u25C0 '+t('logs.output')+'</div>'+
2264
- '<pre class="log-io-content">'+escapeHtml(lg.output)+'</pre>'+
2453
+ (outputHtml?outputHtml:'<pre class="log-io-content">'+escapeHtml(lg.output)+'</pre>')+
2265
2454
  '</div>'+
2266
2455
  '</div>'+
2267
2456
  '</div>';
@@ -2304,8 +2493,6 @@ async function loadMetrics(){
2304
2493
  document.getElementById('mSessions').textContent=formatNum(d.totals.sessions);
2305
2494
  document.getElementById('mEmbeddings').textContent=formatNum(d.totals.embeddings);
2306
2495
  renderChartWrites(d.writesPerDay);
2307
- renderBreakdown(d.roleBreakdown,'breakdownRole');
2308
- renderBreakdown(d.kindBreakdown,'breakdownKind');
2309
2496
  loadToolMetrics();
2310
2497
  }
2311
2498
 
@@ -2448,14 +2635,16 @@ async function openTaskDetail(taskId){
2448
2635
  if(task.chunks.length===0){
2449
2636
  document.getElementById('taskDetailChunks').innerHTML='<div style="color:var(--text-muted);padding:12px;font-size:13px">'+t('tasks.nochunks')+'</div>';
2450
2637
  }else{
2451
- document.getElementById('taskDetailChunks').innerHTML=task.chunks.map(c=>{
2638
+ document.getElementById('taskDetailChunks').innerHTML=task.chunks.map(function(c,i){
2452
2639
  var roleLabel=c.role==='user'?t('tasks.role.user'):c.role==='assistant'?t('tasks.role.assistant'):c.role.toUpperCase();
2453
2640
  return '<div class="task-chunk-item role-'+c.role+'">'+
2454
2641
  '<div class="task-chunk-role '+c.role+'">'+roleLabel+'</div>'+
2455
- '<div class="task-chunk-bubble" onclick="this.classList.toggle(\\\'expanded\\\')">'+esc(c.content)+'</div>'+
2642
+ '<div class="task-chunk-bubble collapsed" id="chunk_b_'+i+'">'+esc(c.content)+'</div>'+
2643
+ '<div class="task-chunk-expand" id="chunk_e_'+i+'" onclick="toggleChunkExpand('+i+')"><span class="expand-arrow">▼</span> <span class="expand-label">'+t('tasks.expand')+'</span></div>'+
2456
2644
  '<div class="task-chunk-time">'+formatTime(c.createdAt)+'</div>'+
2457
2645
  '</div>';
2458
2646
  }).join('');
2647
+ setTimeout(function(){initChunkExpanders(task.chunks.length)},50);
2459
2648
  }
2460
2649
  }catch(e){
2461
2650
  document.getElementById('taskDetailTitle').textContent=t('tasks.error');
@@ -2519,6 +2708,33 @@ function renderTaskSkillSection(task){
2519
2708
  }
2520
2709
  }
2521
2710
 
2711
+ function initChunkExpanders(count){
2712
+ for(var i=0;i<count;i++){
2713
+ var b=document.getElementById('chunk_b_'+i);
2714
+ var e=document.getElementById('chunk_e_'+i);
2715
+ if(b && b.scrollHeight > b.clientHeight + 4){
2716
+ e.style.display='flex';
2717
+ } else if(b) {
2718
+ b.classList.remove('collapsed');
2719
+ }
2720
+ }
2721
+ }
2722
+ function toggleChunkExpand(i){
2723
+ var b=document.getElementById('chunk_b_'+i);
2724
+ var e=document.getElementById('chunk_e_'+i);
2725
+ if(!b||!e)return;
2726
+ var expanding=b.classList.contains('collapsed');
2727
+ if(expanding){
2728
+ b.classList.remove('collapsed');
2729
+ e.classList.add('is-expanded');
2730
+ e.querySelector('.expand-label').textContent=t('tasks.collapse');
2731
+ }else{
2732
+ b.classList.add('collapsed');
2733
+ e.classList.remove('is-expanded');
2734
+ e.querySelector('.expand-label').textContent=t('tasks.expand');
2735
+ }
2736
+ }
2737
+
2522
2738
  function closeTaskDetail(event){
2523
2739
  if(event && event.target!==document.getElementById('taskDetailOverlay')) return;
2524
2740
  document.getElementById('taskDetailOverlay').classList.remove('show');
@@ -2792,7 +3008,7 @@ function classifyError(msg){
2792
3008
  if(msg.indexOf('ECONNREFUSED')>=0) return 'Connection refused';
2793
3009
  if(msg.indexOf('ENOTFOUND')>=0) return 'DNS resolution failed';
2794
3010
  if(msg.indexOf('403')>=0) return 'Forbidden (403)';
2795
- return msg.length>50?msg.slice(0,47)+'...':msg;
3011
+ return msg;
2796
3012
  }
2797
3013
 
2798
3014
  function shortenModel(s){return s?s.replace('openai_compatible/','').replace('openai/',''):'\u2014';}
@@ -2981,6 +3197,7 @@ async function saveConfig(){
2981
3197
  // 2) Test embedding
2982
3198
  try{
2983
3199
  var er=await fetch('/api/test-model',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'embedding',provider:cfg.embedding.provider,model:cfg.embedding.model||'',endpoint:cfg.embedding.endpoint||'',apiKey:cfg.embedding.apiKey||''})});
3200
+ if(er.status===401){done();toast(t('settings.session.expired'),'error');return;}
2984
3201
  var ed=await er.json();
2985
3202
  if(!ed.ok){done();toast(t('settings.save.emb.fail')+': '+ed.error,'error');document.getElementById('testEmbResult').className='test-result fail';document.getElementById('testEmbResult').innerHTML='\\u274C '+ed.error;return;}
2986
3203
  document.getElementById('testEmbResult').className='test-result ok';document.getElementById('testEmbResult').innerHTML='\\u2705 '+t('settings.test.ok');
@@ -2990,6 +3207,7 @@ async function saveConfig(){
2990
3207
  if(hasSumConfig&&cfg.summarizer){
2991
3208
  try{
2992
3209
  var sr=await fetch('/api/test-model',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'summarizer',provider:cfg.summarizer.provider,model:cfg.summarizer.model||'',endpoint:cfg.summarizer.endpoint||'',apiKey:cfg.summarizer.apiKey||''})});
3210
+ if(sr.status===401){done();toast(t('settings.session.expired'),'error');return;}
2993
3211
  var sd=await sr.json();
2994
3212
  if(!sd.ok){done();toast(t('settings.save.sum.fail')+': '+sd.error,'error');document.getElementById('testSumResult').className='test-result fail';document.getElementById('testSumResult').innerHTML='\\u274C '+sd.error;return;}
2995
3213
  document.getElementById('testSumResult').className='test-result ok';document.getElementById('testSumResult').innerHTML='\\u2705 '+t('settings.test.ok');
@@ -3000,6 +3218,7 @@ async function saveConfig(){
3000
3218
  if(hasSkillConfig&&cfg.skillEvolution.summarizer){
3001
3219
  try{
3002
3220
  var kr=await fetch('/api/test-model',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'summarizer',provider:cfg.skillEvolution.summarizer.provider,model:cfg.skillEvolution.summarizer.model||'',endpoint:cfg.skillEvolution.summarizer.endpoint||'',apiKey:cfg.skillEvolution.summarizer.apiKey||''})});
3221
+ if(kr.status===401){done();toast(t('settings.session.expired'),'error');return;}
3003
3222
  var kd=await kr.json();
3004
3223
  if(!kd.ok){done();toast(t('settings.save.skill.fail')+': '+kd.error,'error');document.getElementById('testSkillResult').className='test-result fail';document.getElementById('testSkillResult').innerHTML='\\u274C '+kd.error;return;}
3005
3224
  document.getElementById('testSkillResult').className='test-result ok';document.getElementById('testSkillResult').innerHTML='\\u2705 '+t('settings.test.ok');
@@ -3057,18 +3276,20 @@ async function testModel(type){
3057
3276
  try{
3058
3277
  var body={type:type,provider:provider,model:model,endpoint:endpoint,apiKey:apiKey};
3059
3278
  var r=await fetch('/api/test-model',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
3279
+ if(r.status===401){resultEl.className='test-result fail';resultEl.innerHTML='\\u274C '+t('settings.session.expired');btn.disabled=false;return;}
3060
3280
  var d=await r.json();
3061
3281
  if(d.ok){
3062
3282
  resultEl.className='test-result ok';
3063
- resultEl.innerHTML='\\u2705 '+t('settings.test.ok')+'<div style="margin-top:4px;font-size:11px;color:var(--text-muted)">'+esc(d.detail||'')+'</div>';
3283
+ resultEl.innerHTML='\\u2705 '+t('settings.test.ok')+(d.detail?'<div style="margin-top:4px;font-size:11px;color:var(--text-muted)">'+esc(d.detail)+'</div>':'');
3064
3284
  }else{
3065
- var errMsg=d.error||'Unknown error';
3285
+ var errMsg=(d.error||'Unknown error').replace(/:\s*$/,'').trim();
3066
3286
  resultEl.className='test-result fail';
3067
- resultEl.innerHTML='\\u274C '+t('settings.test.fail')+'<div style="margin-top:6px;font-size:11px;padding:8px 10px;background:rgba(239,68,68,.06);border:1px solid rgba(239,68,68,.15);border-radius:6px;white-space:pre-wrap;word-break:break-all;max-height:120px;overflow-y:auto;font-family:SF Mono,Monaco,Consolas,monospace">'+esc(errMsg)+'</div>';
3287
+ resultEl.innerHTML='\\u274C '+t('settings.test.fail')+(errMsg?'<div style="margin-top:6px;font-size:11px;padding:8px 10px;background:rgba(239,68,68,.06);border:1px solid rgba(239,68,68,.15);border-radius:6px;white-space:pre-wrap;word-break:break-all;max-height:120px;overflow-y:auto;font-family:SF Mono,Monaco,Consolas,monospace">'+esc(errMsg)+'</div>':'');
3068
3288
  }
3069
3289
  }catch(e){
3290
+ var catchMsg=(e.message||'Network error').replace(/:\s*$/,'').trim();
3070
3291
  resultEl.className='test-result fail';
3071
- resultEl.innerHTML='\\u274C '+t('settings.test.fail')+'<div style="margin-top:6px;font-size:11px;padding:8px 10px;background:rgba(239,68,68,.06);border:1px solid rgba(239,68,68,.15);border-radius:6px;white-space:pre-wrap;word-break:break-all">'+esc(e.message)+'</div>';
3292
+ resultEl.innerHTML='\\u274C '+t('settings.test.fail')+(catchMsg?'<div style="margin-top:6px;font-size:11px;padding:8px 10px;background:rgba(239,68,68,.06);border:1px solid rgba(239,68,68,.15);border-radius:6px;white-space:pre-wrap;word-break:break-all">'+esc(catchMsg)+'</div>':'');
3072
3293
  }finally{btn.disabled=false;}
3073
3294
  }
3074
3295
 
@@ -3380,17 +3601,6 @@ function renderToolAgg(data){
3380
3601
  '</tbody></table>';
3381
3602
  }
3382
3603
 
3383
- function renderBreakdown(obj,containerId){
3384
- const el=document.getElementById(containerId);
3385
- if(!el)return;
3386
- const entries=Object.entries(obj||{}).sort((a,b)=>b[1]-a[1]);
3387
- const total=entries.reduce((s,[,v])=>s+v,0)||1;
3388
- el.innerHTML=entries.map(([label,value])=>{
3389
- const pct=Math.round((value/total)*100);
3390
- return '<div class="breakdown-item"><div class="bd-top"><span class="label">'+esc(label)+'</span><span class="value">'+value+' <span style="font-size:11px;font-weight:500;color:var(--text-muted)">('+pct+'%)</span></span></div><div class="breakdown-bar-wrap"><div class="breakdown-bar" style="width:'+pct+'%"></div></div></div>';
3391
- }).join('');
3392
- }
3393
-
3394
3604
  /* ─── Data loading ─── */
3395
3605
  async function loadAll(){
3396
3606
  await Promise.all([loadStats(),loadMemories()]);
@@ -3436,6 +3646,20 @@ async function loadStats(){
3436
3646
  provEl.innerHTML='<div class="provider-badge offline"><span>\\u26A0</span> '+t('embed.off')+'</div>';
3437
3647
  }
3438
3648
 
3649
+ if(!_embeddingWarningShown){
3650
+ _embeddingWarningShown=true;
3651
+ if(!d.embeddingProvider||d.embeddingProvider==='local'||d.embeddingProvider==='none'){
3652
+ showEmbeddingBanner(t('embed.warn.local'),'warning');
3653
+ }
3654
+ fetch('/api/model-health').then(r=>r.json()).then(mh=>{
3655
+ var models=mh.models||[];
3656
+ var embModel=models.find(m=>m.role==='embedding');
3657
+ if(embModel&&embModel.status==='error'){
3658
+ showEmbeddingBanner(t('embed.err.fail'),'error');
3659
+ }
3660
+ }).catch(()=>{});
3661
+ }
3662
+
3439
3663
  const sl=document.getElementById('sessionList');
3440
3664
  sl.innerHTML='<div class="session-item'+(activeSession===null?' active':'')+'" onclick="filterSession(null)"><span>'+t('sidebar.allsessions')+'</span><span class="count">'+tm+'</span></div>';
3441
3665
  (d.sessions||[]).forEach(s=>{
@@ -3459,8 +3683,6 @@ function getFilterParams(){
3459
3683
  const p=new URLSearchParams();
3460
3684
  if(activeSession) p.set('session',activeSession);
3461
3685
  if(activeRole) p.set('role',activeRole);
3462
- const kind=document.getElementById('filterKind').value;
3463
- if(kind) p.set('kind',kind);
3464
3686
  const df=document.getElementById('dateFrom').value;
3465
3687
  if(df) p.set('dateFrom',df);
3466
3688
  const dt=document.getElementById('dateTo').value;
@@ -3499,17 +3721,24 @@ async function doSearch(q){
3499
3721
  if(!q.trim()){currentPage=1;loadMemories();return}
3500
3722
  const list=document.getElementById('memoryList');
3501
3723
  list.innerHTML='<div class="spinner"></div>';
3502
- const p=getFilterParams();
3503
- p.set('q',q);
3504
- const r=await fetch('/api/search?'+p.toString());
3505
- const d=await r.json();
3506
- const meta=[];
3507
- if(d.vectorCount>0) meta.push(d.vectorCount+t('search.meta.semantic'));
3508
- if(d.ftsCount>0) meta.push(d.ftsCount+t('search.meta.text'));
3509
- meta.push(d.total+t('search.meta.results'));
3510
- document.getElementById('searchMeta').textContent=meta.join(' \\u00B7 ');
3511
- renderMemories(d.results||[]);
3512
- document.getElementById('pagination').innerHTML='';
3724
+ try{
3725
+ const p=getFilterParams();
3726
+ p.set('q',q);
3727
+ const r=await fetch('/api/search?'+p.toString());
3728
+ const d=await r.json();
3729
+ const total=d.total||0;
3730
+ const meta=[];
3731
+ if(d.vectorCount>0) meta.push(d.vectorCount+t('search.meta.semantic'));
3732
+ if(d.ftsCount>0) meta.push(d.ftsCount+t('search.meta.text'));
3733
+ meta.push(total+t('search.meta.results'));
3734
+ document.getElementById('searchMeta').textContent=meta.join(' \\u00B7 ');
3735
+ renderMemories(d.results||[]);
3736
+ document.getElementById('pagination').innerHTML='';
3737
+ }catch(e){
3738
+ document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
3739
+ renderMemories([]);
3740
+ document.getElementById('pagination').innerHTML='';
3741
+ }
3513
3742
  }
3514
3743
 
3515
3744
  function debounceSearch(){
@@ -3557,18 +3786,19 @@ function renderMemories(items){
3557
3786
  list.innerHTML=items.map(m=>{
3558
3787
  const time=m.created_at?new Date(typeof m.created_at==='number'?m.created_at:m.created_at).toLocaleString('zh-CN'):'';
3559
3788
  const role=m.role||'user';
3560
- const kind=m.kind||'paragraph';
3561
- const summary=esc(m.summary||m.content?.slice(0,120)||'');
3562
- const content=esc(m.content||'');
3789
+ const rawSummary=m.summary||'';
3790
+ const rawContent=m.content||'';
3791
+ const content=esc(rawContent);
3563
3792
  const id=m.id;
3564
3793
  const vscore=m._vscore?'<span class="vscore-badge">'+Math.round(m._vscore*100)+'%</span>':'';
3565
3794
  const sid=m.session_key||'';
3566
3795
  const sidShort=sid.length>18?sid.slice(0,6)+'..'+sid.slice(-6):sid;
3567
3796
  const mc=m.merge_count||0;
3797
+ const cardTitle=esc(rawSummary||rawContent||'');
3568
3798
  const mergeBadge=mc>0?'<span class="merge-badge">\\u{1F504} '+t('card.evolved')+' '+mc+t('card.times')+'</span>':'';
3569
3799
  const updatedAt=(m.updated_at&&m.updated_at>m.created_at)?'<span class="card-updated">'+t('card.updated')+' '+new Date(m.updated_at).toLocaleString('zh-CN')+'</span>':'';
3570
3800
  const ds=m.dedup_status||'active';
3571
- const isInactive=ds==='duplicate'||ds==='merged';
3801
+ const isInactive=ds==='merged';
3572
3802
  const dedupBadge=ds==='duplicate'?'<span class="dedup-badge duplicate">'+t('card.dedupDuplicate')+'</span>':ds==='merged'?'<span class="dedup-badge merged">'+t('card.dedupMerged')+'</span>':'';
3573
3803
  const isImported=sid.startsWith('openclaw-import-')||sid.startsWith('openclaw-session-');
3574
3804
  const importBadge=isImported?'<span class="import-badge">\u{1F990} '+t('card.imported')+'</span>':'';
@@ -3576,7 +3806,7 @@ function renderMemories(items){
3576
3806
  const isPublicMem=ownerVal==='public';
3577
3807
  const ownerBadge=isPublicMem?'<span class="owner-badge public">\\u{1F310} '+t('filter.public')+'</span>':'<span class="owner-badge agent">\\u{1F512} '+t('filter.private')+'</span>';
3578
3808
  let dedupInfo='';
3579
- if(isInactive){
3809
+ if(ds==='duplicate'||ds==='merged'){
3580
3810
  const reason=m.dedup_reason?'<span style="font-size:11px;color:var(--text-muted)">'+t('card.dedupReason')+esc(m.dedup_reason)+'</span>':'';
3581
3811
  const target=m.dedup_target?'<span class="dedup-target-link" onclick="scrollToMemory(\\''+m.dedup_target+'\\')">'+t('card.dedupTarget')+m.dedup_target.slice(0,8)+'...</span>':'';
3582
3812
  dedupInfo='<div style="margin-top:6px;font-size:11px">'+target+' '+reason+'</div>';
@@ -3599,8 +3829,23 @@ function renderMemories(items){
3599
3829
  }catch(e){}
3600
3830
  }
3601
3831
  return '<div class="memory-card'+(isInactive?' dedup-inactive':'')+'">'+
3602
- '<div class="card-header"><div class="meta"><span class="role-tag '+role+'">'+role+'</span><span class="kind-tag">'+kind+'</span>'+ownerBadge+importBadge+dedupBadge+mergeBadge+'</div><span class="card-time"><span class="session-tag" title="'+esc(sid)+'">'+esc(sidShort)+'</span> '+time+updatedAt+'</span></div>'+
3603
- '<div class="card-summary">'+summary+'</div>'+
3832
+ '<div class="card-header"><div class="meta"><span class="role-tag '+role+'">'+role+'</span>'+ownerBadge+importBadge+dedupBadge+mergeBadge+'</div><span class="card-time"><span class="session-tag" title="'+esc(sid)+'">'+esc(sidShort)+'</span> '+time+updatedAt+'</span></div>'+
3833
+ '<div class="card-summary">'+cardTitle+'</div>'+
3834
+ (function(){
3835
+ if(mc<=0) return '';
3836
+ var mergeHtml='<div class="card-merged-info">';
3837
+ mergeHtml+='<div class="card-merged-label">\\u{1F504} '+t('card.mergedInfo')+' ('+mc+t('card.times')+')</div>';
3838
+ var sources=m.merge_sources||[];
3839
+ if(sources.length>0){
3840
+ mergeHtml+='<div style="display:flex;flex-wrap:wrap;gap:6px">';
3841
+ sources.forEach(function(s){
3842
+ mergeHtml+='<span class="dedup-target-link" onclick="scrollToMemory(\\''+s.id+'\\')">\\u{1F517} '+s.id.slice(0,8)+'...</span>';
3843
+ });
3844
+ mergeHtml+='</div>';
3845
+ }
3846
+ mergeHtml+='</div>';
3847
+ return mergeHtml;
3848
+ })()+
3604
3849
  dedupInfo+
3605
3850
  '<div class="card-content" id="content-'+id+'"><pre>'+content+'</pre></div>'+
3606
3851
  historyHtml+
@@ -3679,12 +3924,11 @@ async function showMemoryModal(chunkId){
3679
3924
  const m=data.memory;
3680
3925
  const role=(m.role||'unknown').toUpperCase();
3681
3926
  const roleCls=(m.role||'').toLowerCase();
3682
- const kind=m.kind||'paragraph';
3683
3927
  const ds=m.dedup_status||'active';
3684
3928
  const time=new Date(m.created_at).toLocaleString('zh-CN');
3685
3929
  const updated=m.updated_at?new Date(m.updated_at).toLocaleString('zh-CN'):'';
3686
3930
  let html='<div class="modal-memory-card">';
3687
- html+='<div class="modal-header-row"><span class="role-tag '+roleCls+'">'+role+'</span><span class="kind-tag">'+kind+'</span>';
3931
+ html+='<div class="modal-header-row"><span class="role-tag '+roleCls+'">'+role+'</span>';
3688
3932
  if(ds!=='active') html+='<span class="dedup-badge '+(ds==='duplicate'?'duplicate':'merged')+'">'+ds+'</span>';
3689
3933
  html+='</div>';
3690
3934
  html+='<div class="modal-field"><div class="modal-field-label">ID</div><div class="modal-field-val" style="font-family:monospace;font-size:11px">'+esc(m.id)+'</div></div>';
@@ -3742,17 +3986,6 @@ function renderSummaryHtml(raw){
3742
3986
  }
3743
3987
 
3744
3988
  /* ─── CRUD ─── */
3745
- function openCreateModal(){
3746
- editingId=null;
3747
- document.getElementById('modalTitle').textContent=t('modal.new');
3748
- document.getElementById('modalSubmit').textContent=t('modal.create');
3749
- document.getElementById('mRole').value='user';
3750
- document.getElementById('mContent').value='';
3751
- document.getElementById('mSummary').value='';
3752
- document.getElementById('mKind').value='paragraph';
3753
- document.getElementById('modalOverlay').classList.add('show');
3754
- }
3755
-
3756
3989
  function openEditModal(id){
3757
3990
  const m=memoryCache[id];
3758
3991
  if(!m){toast(t('toast.notfound'),'error');return}
@@ -3762,7 +3995,6 @@ function openEditModal(id){
3762
3995
  document.getElementById('mRole').value=m.role||'user';
3763
3996
  document.getElementById('mContent').value=m.content||'';
3764
3997
  document.getElementById('mSummary').value=m.summary||'';
3765
- document.getElementById('mKind').value=m.kind||'paragraph';
3766
3998
  document.getElementById('modalOverlay').classList.add('show');
3767
3999
  }
3768
4000
 
@@ -3771,21 +4003,16 @@ function closeModal(){
3771
4003
  }
3772
4004
 
3773
4005
  async function submitModal(){
4006
+ if(!editingId)return;
3774
4007
  const data={
3775
4008
  role:document.getElementById('mRole').value,
3776
4009
  content:document.getElementById('mContent').value,
3777
4010
  summary:document.getElementById('mSummary').value,
3778
- kind:document.getElementById('mKind').value,
3779
4011
  };
3780
4012
  if(!data.content.trim()){toast(t('modal.err.empty'),'error');return}
3781
- let r;
3782
- if(editingId){
3783
- r=await fetch('/api/memory/'+editingId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
3784
- } else {
3785
- r=await fetch('/api/memory',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
3786
- }
4013
+ const r=await fetch('/api/memory/'+editingId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
3787
4014
  const d=await r.json();
3788
- if(d.ok){toast(editingId?t('toast.updated'):t('toast.created'),'success');closeModal();loadAll();}
4015
+ if(d.ok){toast(t('toast.updated'),'success');closeModal();loadAll();}
3789
4016
  else{toast(d.error||t('toast.opfail'),'error')}
3790
4017
  }
3791
4018
 
@@ -3808,12 +4035,15 @@ async function toggleMemoryPublic(id,setPublic){
3808
4035
  }
3809
4036
 
3810
4037
  async function clearAll(){
3811
- if(!confirm(t('confirm.clearall')))return;
3812
- if(!confirm(t('confirm.clearall2')))return;
3813
- const r=await fetch('/api/memories',{method:'DELETE'});
3814
- const d=await r.json();
3815
- if(d.ok){toast(t('toast.cleared'),'success');loadAll();}
3816
- else{toast(t('toast.clearfail'),'error')}
4038
+ try{
4039
+ if(!confirm(t('confirm.clearall')))return;
4040
+ if(!confirm(t('confirm.clearall2')))return;
4041
+ const r=await fetch('/api/memories',{method:'DELETE'});
4042
+ if(r.status===401){toast(t('settings.session.expired'),'error');return;}
4043
+ const d=await r.json();
4044
+ if(d.ok){toast(t('toast.cleared'),'success');loadAll();}
4045
+ else{toast(t('toast.clearfail'),'error')}
4046
+ }catch(e){toast('Error: '+e.message,'error')}
3817
4047
  }
3818
4048
 
3819
4049
  /* ─── Migration ─── */
@@ -3833,7 +4063,7 @@ let migrateStats={stored:0,skipped:0,merged:0,errors:0};
3833
4063
  });
3834
4064
  })();
3835
4065
 
3836
- async function migrateScan(){
4066
+ async function migrateScan(showToast){
3837
4067
  const btn=document.getElementById('migrateScanBtn');
3838
4068
  btn.disabled=true;
3839
4069
  btn.textContent=t('migrate.scanning');
@@ -3866,9 +4096,31 @@ async function migrateScan(){
3866
4096
  t('migrate.config.warn.desc')+' ('+parts.join(', ')+')';
3867
4097
  }
3868
4098
 
4099
+ const imported=d.importedChunkCount||0;
4100
+ const remaining=Math.max(0,(d.totalItems||0)-imported);
4101
+
3869
4102
  if(d.totalItems>0 && d.configReady){
3870
4103
  document.getElementById('migrateStartBtn').style.display='inline-flex';
3871
4104
  document.getElementById('migrateConcurrencyRow').style.display='inline-flex';
4105
+ if(d.hasImportedData){
4106
+ document.getElementById('migrateStartBtn').textContent=t('migrate.resume');
4107
+ }else{
4108
+ document.getElementById('migrateStartBtn').textContent=t('migrate.start');
4109
+ }
4110
+ }
4111
+
4112
+ var hintEl=document.getElementById('migrateImportedHint');
4113
+ if(!hintEl){
4114
+ hintEl=document.createElement('div');
4115
+ hintEl.id='migrateImportedHint';
4116
+ hintEl.style.cssText='font-size:12px;color:var(--text-sec);padding:6px 0';
4117
+ document.getElementById('migrateActions').appendChild(hintEl);
4118
+ }
4119
+ if(imported>0){
4120
+ hintEl.textContent=t('migrate.imported.hint').replace('{n}',imported);
4121
+ hintEl.style.display='block';
4122
+ }else{
4123
+ hintEl.style.display='none';
3872
4124
  }
3873
4125
 
3874
4126
  if(d.totalItems===0){
@@ -3878,6 +4130,7 @@ async function migrateScan(){
3878
4130
  if(d.hasImportedData){
3879
4131
  document.getElementById('postprocessSection').style.display='block';
3880
4132
  }
4133
+ if(showToast) toast(t('migrate.scan.done').replace('{n}',remaining),'success');
3881
4134
  }catch(e){
3882
4135
  toast('Scan failed: '+e.message,'error');
3883
4136
  }finally{
@@ -3887,27 +4140,36 @@ async function migrateScan(){
3887
4140
  }
3888
4141
 
3889
4142
  function migrateStart(){
3890
- if(!migrateScanData||!migrateScanData.configReady)return;
3891
- if(!confirm(t('migrate.start')+'?'))return;
4143
+ const isResume=document.getElementById('migrateStartBtn').textContent===t('migrate.resume');
4144
+ if(!isResume){
4145
+ if(!migrateScanData||!migrateScanData.configReady){
4146
+ toast(t('migrate.scan.required'),'error');
4147
+ return;
4148
+ }
4149
+ if(!confirm(t('migrate.start')+'?'))return;
4150
+ }
3892
4151
 
3893
4152
  const concSel=document.getElementById('migrateConcurrency');
3894
4153
  const concurrency=concSel?parseInt(concSel.value,10)||1:1;
3895
4154
 
3896
4155
  window._migrateRunning=true;
3897
- _migrateStatusChecked=false;
4156
+ _migrateStatusChecked=true;
3898
4157
  document.getElementById('migrateStartBtn').style.display='none';
3899
4158
  document.getElementById('migrateScanBtn').disabled=true;
4159
+ var hintEl=document.getElementById('migrateImportedHint');
4160
+ if(hintEl) hintEl.style.display='none';
3900
4161
  document.getElementById('migrateConcurrencyRow').style.display='none';
3901
4162
  document.getElementById('migrateConcurrencyWarn').style.display='none';
3902
4163
  document.getElementById('migrateProgress').style.display='block';
3903
4164
  document.getElementById('migrateLiveLog').innerHTML='';
3904
4165
  migrateStats={stored:0,skipped:0,merged:0,errors:0};
3905
4166
  updateMigrateStats();
4167
+ document.getElementById('migrateBar').style.width='0%';
4168
+ document.getElementById('migrateCounter').textContent='';
3906
4169
 
3907
4170
  document.getElementById('migrateStopBtn').disabled=false;
3908
- document.getElementById('migrateBar').style.width='0%';
4171
+ document.getElementById('migrateStopBtn').style.display='inline-flex';
3909
4172
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#6366f1,#8b5cf6)';
3910
- document.getElementById('migrateCounter').textContent='';
3911
4173
  const body=JSON.stringify({sources:['sqlite','sessions'],concurrency});
3912
4174
  connectMigrateSSE('/api/migrate/start','POST',body);
3913
4175
  }
@@ -3944,7 +4206,7 @@ function readSSEStream(r){
3944
4206
  const NL=String.fromCharCode(10);
3945
4207
  function pump(){
3946
4208
  reader.read().then(({done,value})=>{
3947
- if(done){if(!migrateDoneCalled&&!window._migrateRunning)onMigrateDone(false);return;}
4209
+ if(done){if(!migrateDoneCalled)onMigrateDone(false);return;}
3948
4210
  buf+=decoder.decode(value,{stream:true});
3949
4211
  const lines=buf.split(NL);
3950
4212
  buf=lines.pop()||'';
@@ -3967,7 +4229,7 @@ function readSSEStream(r){
3967
4229
 
3968
4230
  var _migrateStatusChecked=false;
3969
4231
  async function checkMigrateStatus(){
3970
- if(_migrateStatusChecked) return;
4232
+ if(_migrateStatusChecked||window._migrateRunning) return;
3971
4233
  _migrateStatusChecked=true;
3972
4234
  try{
3973
4235
  const r=await fetch('/api/migrate/status');
@@ -3989,7 +4251,18 @@ async function checkMigrateStatus(){
3989
4251
  document.getElementById('migrateCounter').textContent=s.processed+' / '+s.total+' ('+pct+'%)';
3990
4252
  const label=s.phase==='sqlite'?t('migrate.phase.sqlite'):t('migrate.phase.sessions');
3991
4253
  document.getElementById('migratePhaseLabel').textContent=label;
4254
+ document.getElementById('migrateStopBtn').style.display='inline-flex';
4255
+ if(s.processed>0){
4256
+ const log=document.getElementById('migrateLiveLog');
4257
+ const hint=document.createElement('div');
4258
+ hint.style.cssText='text-align:center;padding:8px 12px;color:var(--text-muted);font-size:11px;border-bottom:1px solid var(--border)';
4259
+ hint.textContent=t('migrate.reconnect.hint').replace('{n}',s.processed);
4260
+ log.appendChild(hint);
4261
+ }
3992
4262
  connectMigrateSSE('/api/migrate/stream','GET',null);
4263
+ fetch('/api/migrate/scan').then(function(sr){return sr.json()}).then(function(sd){
4264
+ if(sd&&sd.hasImportedData) document.getElementById('postprocessSection').style.display='block';
4265
+ }).catch(function(){});
3993
4266
  }else if(s.done&&(s.stored>0||s.skipped>0||s.stopped)){
3994
4267
  migrateStats={stored:s.stored,skipped:s.skipped,merged:s.merged,errors:s.errors};
3995
4268
  updateMigrateStats();
@@ -4087,18 +4360,23 @@ function onMigrateDone(wasStopped,skipReload){
4087
4360
  document.getElementById('migrateScanBtn').disabled=false;
4088
4361
  document.getElementById('migrateStopBtn').disabled=true;
4089
4362
  document.getElementById('migrateStopBtn').textContent=t('migrate.stop');
4363
+ document.getElementById('migrateStopBtn').style.display='none';
4090
4364
  if(wasStopped){
4091
4365
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#f59e0b,#fbbf24)';
4092
4366
  document.getElementById('migrateStartBtn').style.display='inline-flex';
4093
4367
  document.getElementById('migrateStartBtn').textContent=t('migrate.resume');
4368
+ document.getElementById('migratePhaseLabel').textContent=t('migrate.phase.stopped');
4094
4369
  }else{
4095
4370
  document.getElementById('migrateBar').style.width='100%';
4096
4371
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#22c55e,#16a34a)';
4097
4372
  const total=migrateStats.stored+migrateStats.skipped+migrateStats.merged+migrateStats.errors;
4098
4373
  if(total>0) document.getElementById('migrateCounter').textContent=total+' / '+total+' (100%)';
4374
+ document.getElementById('migratePhaseLabel').textContent=t('migrate.phase.done');
4099
4375
  }
4100
4376
  fetch('/api/migrate/scan').then(r=>{if(!r.ok)throw new Error();return r.json()}).then(d=>{
4101
- if(d&&d.hasImportedData) document.getElementById('postprocessSection').style.display='block';
4377
+ if(d&&d.hasImportedData){
4378
+ document.getElementById('postprocessSection').style.display='block';
4379
+ }
4102
4380
  }).catch(()=>{});
4103
4381
  if(!skipReload) loadAll();
4104
4382
  }
@@ -4226,12 +4504,18 @@ function connectPPSSE(){
4226
4504
  }).catch(function(){});
4227
4505
  }else if(s.done){
4228
4506
  document.getElementById('postprocessSection').style.display='block';
4229
- ppStats={tasks:s.tasksCreated||0,skills:s.skillsCreated||0,errors:s.errors||0,skipped:0};
4507
+ ppStats={tasks:s.tasksCreated||0,skills:s.skillsCreated||0,errors:s.errors||0,skipped:s.skippedSessions||0};
4230
4508
  updatePPStats();
4231
4509
  document.getElementById('ppProgress').style.display='block';
4232
- var pct2=s.total>0?Math.round((s.processed/s.total)*100):0;
4233
- document.getElementById('ppBar').style.width=pct2+'%';
4234
- document.getElementById('ppCounter').textContent=s.processed+' / '+s.total+' ('+pct2+'%)';
4510
+ var totalAll=(s.total||0)+(s.skippedSessions||0);
4511
+ if(totalAll>0){
4512
+ document.getElementById('ppBar').style.width='100%';
4513
+ document.getElementById('ppCounter').textContent=totalAll+' / '+totalAll+' (100%)';
4514
+ }else{
4515
+ var pct2=s.total>0?Math.round((s.processed/s.total)*100):0;
4516
+ document.getElementById('ppBar').style.width=pct2+'%';
4517
+ document.getElementById('ppCounter').textContent=s.processed+' / '+s.total+' ('+pct2+'%)';
4518
+ }
4235
4519
  ppDone(!!s.stopped,false,true);
4236
4520
  }
4237
4521
  }).catch(function(){});
@@ -4239,9 +4523,11 @@ function connectPPSSE(){
4239
4523
 
4240
4524
  function handlePPEvent(evtType,data){
4241
4525
  if(evtType==='progress'){
4242
- var pct=data.total>0?Math.round((data.processed/data.total)*100):0;
4243
- document.getElementById('ppBar').style.width=pct+'%';
4244
- document.getElementById('ppCounter').textContent=data.processed+' / '+data.total+' ('+pct+'%)';
4526
+ if(data.total>0){
4527
+ var pct=Math.round((data.processed/data.total)*100);
4528
+ document.getElementById('ppBar').style.width=pct+'%';
4529
+ document.getElementById('ppCounter').textContent=data.processed+' / '+data.total+' ('+pct+'%)';
4530
+ }
4245
4531
  }else if(evtType==='info'){
4246
4532
  if(data.alreadyProcessed>0){
4247
4533
  ppStats.skipped=data.alreadyProcessed;
@@ -4250,6 +4536,10 @@ function handlePPEvent(evtType,data){
4250
4536
  }
4251
4537
  if(data.pending===0){
4252
4538
  appendPPLogItem({step:'done',session:t('pp.info.allDone'),index:'',total:''});
4539
+ document.getElementById('ppPhaseLabel').textContent=t('pp.info.allDone');
4540
+ document.getElementById('ppBar').style.width='100%';
4541
+ document.getElementById('ppBar').style.background='linear-gradient(90deg,#22c55e,#16a34a)';
4542
+ document.getElementById('ppCounter').textContent=data.alreadyProcessed+' / '+data.totalSessions;
4253
4543
  }else{
4254
4544
  document.getElementById('ppPhaseLabel').textContent=t('pp.info.pending').replace('{n}',data.pending);
4255
4545
  }
@@ -4261,12 +4551,10 @@ function handlePPEvent(evtType,data){
4261
4551
  document.getElementById('ppPhaseLabel').textContent=t('pp.running')+' — '+actionLabel+' — '+label;
4262
4552
  }
4263
4553
  if(data.step==='done'){
4264
- if(data.action==='skill-only'){
4265
- ppStats.skills++;
4266
- }else{
4554
+ if(data.action!=='skill-only'){
4267
4555
  ppStats.tasks++;
4556
+ updatePPStats();
4268
4557
  }
4269
- updatePPStats();
4270
4558
  }else if(data.step==='error'){
4271
4559
  ppStats.errors++;
4272
4560
  updatePPStats();
@@ -4294,6 +4582,7 @@ function ppDone(wasStopped,wasFailed,skipReload){
4294
4582
  document.getElementById('ppStopBtn').style.display='none';
4295
4583
  document.getElementById('ppStartBtn').style.display='inline-flex';
4296
4584
  document.getElementById('ppStartBtn').textContent=wasStopped?t('pp.resume'):t('pp.start');
4585
+ document.getElementById('ppStartBtn').disabled=false;
4297
4586
  var doneEl=document.getElementById('ppDone');
4298
4587
  doneEl.style.display='block';
4299
4588
  if(wasFailed){
@@ -4301,21 +4590,57 @@ function ppDone(wasStopped,wasFailed,skipReload){
4301
4590
  doneEl.style.color='#ef4444';
4302
4591
  doneEl.textContent=t('pp.failed')||'Processing failed — check error above';
4303
4592
  document.getElementById('ppBar').style.background='linear-gradient(90deg,#ef4444,#dc2626)';
4593
+ document.getElementById('ppPhaseLabel').textContent=t('pp.failed');
4304
4594
  }else if(wasStopped){
4305
4595
  doneEl.style.background='rgba(245,158,11,.06)';
4306
4596
  doneEl.style.color='#f59e0b';
4307
4597
  doneEl.textContent=t('pp.stopped');
4308
4598
  document.getElementById('ppBar').style.background='linear-gradient(90deg,#f59e0b,#fbbf24)';
4599
+ document.getElementById('ppPhaseLabel').textContent=t('pp.stopped');
4309
4600
  }else{
4310
4601
  doneEl.style.background='rgba(34,197,94,.06)';
4311
4602
  doneEl.style.color='#22c55e';
4312
- doneEl.textContent=t('pp.done')+' ('+t('pp.stat.tasks')+': '+ppStats.tasks+', '+t('pp.stat.skills')+': '+ppStats.skills+')';
4313
4603
  document.getElementById('ppBar').style.width='100%';
4314
4604
  document.getElementById('ppBar').style.background='linear-gradient(90deg,#22c55e,#16a34a)';
4605
+ document.getElementById('ppPhaseLabel').textContent=t('pp.done');
4606
+ var ppTotal=ppStats.tasks+ppStats.skipped+ppStats.errors;
4607
+ if(ppTotal>0) document.getElementById('ppCounter').textContent=ppTotal+' / '+ppTotal+' (100%)';
4608
+ fetch('/api/migrate/postprocess/status').then(function(r){return r.json()}).then(function(st){
4609
+ var totalTasks=st.existingTasks||0;
4610
+ var totalSkills=st.existingSkills||0;
4611
+ var lines=[];
4612
+ if(ppStats.tasks>0) lines.push(t('pp.stat.tasks')+' +'+ppStats.tasks);
4613
+ if(ppStats.skills>0) lines.push(t('pp.stat.skills')+' +'+ppStats.skills);
4614
+ if(ppStats.skipped>0) lines.push(t('pp.stat.skipped')+': '+ppStats.skipped);
4615
+ var runText=lines.length>0?' ('+lines.join(', ')+')':'';
4616
+ var totalText=' — '+t('pp.stat.tasks')+' '+totalTasks+', '+t('pp.stat.skills.total')+' '+totalSkills;
4617
+ doneEl.textContent=t('pp.done')+runText+totalText;
4618
+ }).catch(function(){
4619
+ var parts=[];
4620
+ if(ppStats.tasks>0) parts.push(t('pp.stat.tasks')+': '+ppStats.tasks);
4621
+ if(ppStats.skills>0) parts.push(t('pp.stat.skills')+': '+ppStats.skills);
4622
+ if(ppStats.skipped>0) parts.push(t('pp.stat.skipped')+': '+ppStats.skipped);
4623
+ doneEl.textContent=t('pp.done')+(parts.length>0?' ('+parts.join(', ')+')':'');
4624
+ });
4315
4625
  }
4316
4626
  if(!skipReload) loadAll();
4317
4627
  }
4318
4628
 
4629
+ /* ─── Embedding Banner ─── */
4630
+ function showEmbeddingBanner(msg,type){
4631
+ if(document.getElementById('embBanner')) return;
4632
+ var cls=type==='error'?'emb-banner error':'emb-banner warning';
4633
+ var icon=type==='error'?'\\u274C':'\\u26A0\\uFE0F';
4634
+ var btn='<button class="emb-banner-btn" onclick="switchView(\\'settings\\');this.parentElement.remove()">'+t('embed.banner.goto')+'</button>';
4635
+ var close='<button class="emb-banner-close" onclick="this.parentElement.remove()">&times;</button>';
4636
+ var el=document.createElement('div');
4637
+ el.id='embBanner';
4638
+ el.className=cls;
4639
+ el.innerHTML=icon+' <span>'+esc(msg)+'</span>'+btn+close;
4640
+ var mc=document.querySelector('.main-content');
4641
+ if(mc) mc.parentElement.insertBefore(el,mc);
4642
+ }
4643
+
4319
4644
  /* ─── Toast ─── */
4320
4645
  function toast(msg,type='info'){
4321
4646
  const c=document.getElementById('toasts');
@@ -4334,16 +4659,64 @@ function toggleViewerTheme(){const el=document.documentElement;const cur=el.getA
4334
4659
  initViewerTheme();
4335
4660
 
4336
4661
  /* ─── Update check ─── */
4662
+ function waitForGatewayAndReload(maxAttempts,attempt){
4663
+ attempt=attempt||0;
4664
+ if(attempt>=maxAttempts){window.location.reload();return;}
4665
+ setTimeout(function(){
4666
+ fetch('/api/update-check').then(function(r){
4667
+ if(r.ok) window.location.reload();
4668
+ else waitForGatewayAndReload(maxAttempts,attempt+1);
4669
+ }).catch(function(){waitForGatewayAndReload(maxAttempts,attempt+1);});
4670
+ },3000);
4671
+ }
4672
+ function doUpdateInstall(packageSpec,btnEl){
4673
+ btnEl.disabled=true;
4674
+ btnEl.textContent=t('update.installing');
4675
+ fetch('/api/update-install',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({packageSpec:packageSpec})})
4676
+ .then(function(r){return r.json()})
4677
+ .then(function(d){
4678
+ if(d.ok){
4679
+ btnEl.textContent=t('update.success');
4680
+ btnEl.style.background='#22c55e';
4681
+ btnEl.style.color='#fff';
4682
+ waitForGatewayAndReload(20);
4683
+ }else{
4684
+ btnEl.textContent=t('update.failed')+': '+(d.error||'').slice(0,80);
4685
+ btnEl.style.background='#ef4444';
4686
+ btnEl.style.color='#fff';
4687
+ btnEl.disabled=false;
4688
+ setTimeout(function(){btnEl.textContent=t('update.btn');btnEl.style.background='';btnEl.style.color='';},5000);
4689
+ }
4690
+ })
4691
+ .catch(function(e){
4692
+ btnEl.textContent=t('update.failed');
4693
+ btnEl.disabled=false;
4694
+ setTimeout(function(){btnEl.textContent=t('update.btn');btnEl.style.background='';btnEl.style.color='';},5000);
4695
+ });
4696
+ }
4337
4697
  async function checkForUpdate(){
4338
4698
  try{
4339
4699
  const r=await fetch('/api/update-check');
4340
4700
  if(!r.ok)return;
4341
4701
  const d=await r.json();
4342
4702
  if(!d.updateAvailable)return;
4703
+ const pkgSpec=d.installCommand?d.installCommand.replace(/^openclaw plugins install\s+/,''):(d.packageName+'@'+d.latest);
4343
4704
  const banner=document.createElement('div');
4344
4705
  banner.id='updateBanner';
4345
4706
  banner.style.cssText='position:fixed;top:0;left:0;right:0;z-index:9999;background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;padding:10px 20px;display:flex;align-items:center;justify-content:space-between;font-size:14px;box-shadow:0 2px 8px rgba(0,0,0,.25)';
4346
- banner.innerHTML='<span>🔔 '+t('update.available')+': <b>v'+esc(d.current)+'</b> → <b>v'+esc(d.latest)+'</b> — '+t('update.run')+': <code style="background:rgba(0,0,0,.2);padding:2px 8px;border-radius:4px;margin:0 4px">openclaw plugins install '+esc(d.packageName)+'</code></span><button onclick="this.parentElement.remove();document.body.style.paddingTop=\\'\\';" style="background:none;border:none;color:#fff;font-size:18px;cursor:pointer;padding:0 4px">&times;</button>';
4707
+ var leftSpan=document.createElement('span');
4708
+ leftSpan.innerHTML='🔔 '+t('update.available')+': <b>v'+esc(d.current)+'</b> → <b>v'+esc(d.latest)+'</b>';
4709
+ var btnUpdate=document.createElement('button');
4710
+ btnUpdate.textContent=t('update.btn');
4711
+ btnUpdate.style.cssText='background:#fff;color:#d97706;border:none;padding:5px 16px;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;margin-left:12px';
4712
+ btnUpdate.onclick=function(){doUpdateInstall(pkgSpec,btnUpdate)};
4713
+ var btnClose=document.createElement('button');
4714
+ btnClose.innerHTML='&times;';
4715
+ btnClose.style.cssText='background:none;border:none;color:#fff;font-size:18px;cursor:pointer;padding:0 4px';
4716
+ btnClose.onclick=function(){banner.remove();document.body.style.paddingTop='';};
4717
+ leftSpan.appendChild(btnUpdate);
4718
+ banner.appendChild(leftSpan);
4719
+ banner.appendChild(btnClose);
4347
4720
  document.body.prepend(banner);
4348
4721
  document.body.style.paddingTop='48px';
4349
4722
  }catch(e){}
@@ -4369,3 +4742,4 @@ checkAuth();
4369
4742
 
4370
4743
  </body>
4371
4744
  </html>`;
4745
+ }