@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,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.viewerHTML = void 0;
4
- exports.viewerHTML = `<!DOCTYPE html>
3
+ exports.viewerHTML = viewerHTML;
4
+ function viewerHTML(pluginVersion) {
5
+ const vBadge = pluginVersion ? `<span class="version-badge">v${pluginVersion}</span>` : '';
6
+ return `<!DOCTYPE html>
5
7
  <html lang="zh-CN">
6
8
  <head>
7
9
  <meta charset="UTF-8">
@@ -42,7 +44,7 @@ exports.viewerHTML = `<!DOCTYPE html>
42
44
  [data-theme="light"] .auth-screen{background:linear-gradient(135deg,#f0f4ff 0%,#f8f9fb 50%,#eef2ff 100%)}
43
45
  [data-theme="light"] .auth-card{box-shadow:0 25px 50px -12px rgba(0,0,0,.08)}
44
46
  [data-theme="light"] .topbar{background:rgba(255,255,255,.92);border-bottom-color:var(--border);backdrop-filter:blur(8px)}
45
- [data-theme="light"] .session-item .count,[data-theme="light"] .kind-tag,[data-theme="light"] .session-tag{background:rgba(0,0,0,.05)}
47
+ [data-theme="light"] .session-item .count,[data-theme="light"] .session-tag{background:rgba(0,0,0,.05)}
46
48
  [data-theme="light"] .card-content pre{background:#f3f4f6;border-color:var(--border)}
47
49
  [data-theme="light"] .vscore-badge{background:rgba(79,70,229,.06);color:#4f46e5}
48
50
  [data-theme="light"] ::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15)}
@@ -67,10 +69,6 @@ exports.viewerHTML = `<!DOCTYPE html>
67
69
  [data-theme="light"] .tool-agg-table td{background:transparent}
68
70
  [data-theme="light"] .tool-agg-table tr:hover td{background:rgba(79,70,229,.03)}
69
71
  [data-theme="light"] .tool-agg-table th{color:#9ca3af}
70
- [data-theme="light"] .breakdown-item{background:#f9fafb;border-color:var(--border)}
71
- [data-theme="light"] .breakdown-item:hover{background:#f3f4f6;border-color:#cbd5e1}
72
- [data-theme="light"] .breakdown-bar-wrap{background:#e5e7eb}
73
- [data-theme="light"] .breakdown-bar{background:linear-gradient(90deg,#4f46e5,#6366f1);box-shadow:none}
74
72
  [data-theme="light"] .range-btn{background:transparent;border-color:var(--border);color:var(--text-sec)}
75
73
  [data-theme="light"] .range-btn.active{background:rgba(79,70,229,.06);color:#4f46e5;border-color:rgba(79,70,229,.2)}
76
74
  [data-theme="light"] .range-btn:hover{border-color:#4f46e5;color:#4f46e5}
@@ -113,13 +111,15 @@ input,textarea,select{font-family:inherit;font-size:inherit}
113
111
  .topbar .brand{display:flex;align-items:center;gap:10px;font-weight:700;font-size:15px;color:var(--text);letter-spacing:-.02em;flex-shrink:0}
114
112
  .topbar .brand .icon{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:22px;background:none;border-radius:0}
115
113
  .topbar .brand .sub{font-weight:400;color:var(--text-muted);font-size:11px}
114
+ .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}
115
+ [data-theme="light"] .version-badge{background:rgba(0,0,0,.05);border-color:rgba(0,0,0,.08);color:var(--text-sec)}
116
116
  .topbar-center{flex:1;display:flex;justify-content:center}
117
117
  .topbar .actions{display:flex;align-items:center;gap:6px;flex-shrink:0}
118
118
 
119
119
  .main-content{display:flex;flex:1;max-width:1400px;margin:0 auto;width:100%;padding:28px 32px;gap:28px}
120
120
 
121
121
  /* ─── Sidebar ─── */
122
- .sidebar{width:260px;flex-shrink:0}
122
+ .sidebar{width:260px;min-width:260px;flex-shrink:0}
123
123
  .sidebar .stats-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:24px}
124
124
  .stat-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:18px;transition:all .2s}
125
125
  .stat-card:hover{border-color:var(--border-glow);background:var(--bg-card-hover)}
@@ -163,10 +163,9 @@ input,textarea,select{font-family:inherit;font-size:inherit}
163
163
  .role-tag.user{background:var(--pri-glow);color:var(--pri);border:1px solid rgba(99,102,241,.12)}
164
164
  .role-tag.assistant{background:var(--accent-glow);color:var(--accent);border:1px solid rgba(230,57,70,.2)}
165
165
  .role-tag.system{background:var(--amber-bg);color:var(--amber);border:1px solid rgba(245,158,11,.2)}
166
- .kind-tag{padding:4px 10px;border-radius:8px;font-size:11px;color:var(--text-sec);background:rgba(0,0,0,.2);font-weight:500}
167
166
  .card-time{font-size:12px;color:var(--text-sec);display:flex;align-items:center;gap:8px}
168
167
  .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}
169
- .card-summary{font-size:15px;font-weight:600;color:var(--text);margin-bottom:10px;line-height:1.5;letter-spacing:-.01em}
168
+ .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}
170
169
  .card-content{font-size:13px;color:var(--text-sec);line-height:1.65;max-height:0;overflow:hidden;transition:max-height .3s ease}
171
170
  .card-content.show{max-height:600px;overflow-y:auto}
172
171
  .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)}
@@ -211,6 +210,9 @@ input,textarea,select{font-family:inherit;font-size:inherit}
211
210
  .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)}
212
211
  [data-theme="light"] .merge-history{background:rgba(0,0,0,.04)}
213
212
  [data-theme="light"] .merge-history-item{border-bottom-color:rgba(0,0,0,.06)}
213
+ .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)}
214
+ .card-merged-label{font-size:10px;font-weight:600;color:#10b981;margin-bottom:4px;display:flex;align-items:center;gap:4px}
215
+ [data-theme="light"] .card-merged-info{background:rgba(16,185,129,.04);border-color:rgba(16,185,129,.15)}
214
216
 
215
217
  /* ─── Buttons ─── */
216
218
  .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}
@@ -244,6 +246,16 @@ input,textarea,select{font-family:inherit;font-size:inherit}
244
246
  .modal-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:28px}
245
247
 
246
248
  /* ─── Toast ─── */
249
+ .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}
250
+ .emb-banner.warning{background:rgba(245,158,11,.1);color:#d97706;border:1px solid rgba(245,158,11,.25)}
251
+ .emb-banner.error{background:rgba(239,68,68,.1);color:#ef4444;border:1px solid rgba(239,68,68,.25)}
252
+ [data-theme="light"] .emb-banner.warning{background:rgba(245,158,11,.08);color:#b45309}
253
+ [data-theme="light"] .emb-banner.error{background:rgba(239,68,68,.08);color:#dc2626}
254
+ .emb-banner span{flex:1}
255
+ .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}
256
+ .emb-banner-btn:hover{opacity:1}
257
+ .emb-banner-close{background:none;border:none;font-size:18px;color:inherit;cursor:pointer;opacity:.5;padding:0 4px;line-height:1}
258
+ .emb-banner-close:hover{opacity:1}
247
259
  .toast-container{position:fixed;top:80px;right:24px;z-index:1000;display:flex;flex-direction:column;gap:8px}
248
260
  .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}
249
261
  .toast.success{background:var(--green-bg);color:var(--green);border-color:rgba(16,185,129,.3)}
@@ -333,8 +345,12 @@ input,textarea,select{font-family:inherit;font-size:inherit}
333
345
  .task-chunk-role.user{color:var(--pri)}
334
346
  .task-chunk-role.assistant{color:var(--green)}
335
347
  .task-chunk-role.tool{color:var(--amber)}
336
- .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}
337
- .task-chunk-bubble.expanded{max-height:none}
348
+ .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}
349
+ .task-chunk-bubble.collapsed{max-height:200px}
350
+ .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}
351
+ .task-chunk-expand:hover{color:var(--pri);background:rgba(99,102,241,.08)}
352
+ .task-chunk-expand .expand-arrow{display:inline-block;font-size:10px;transition:transform .2s}
353
+ .task-chunk-expand.is-expanded .expand-arrow{transform:rotate(180deg)}
338
354
  .role-user .task-chunk-bubble{background:var(--pri);color:#000;border-bottom-right-radius:4px}
339
355
  .role-assistant .task-chunk-bubble{background:var(--bg-card);border:1px solid var(--border);color:var(--text-sec);border-bottom-left-radius:4px}
340
356
  .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}
@@ -424,6 +440,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
424
440
  [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)}
425
441
  .analytics-view,.settings-view,.logs-view,.migrate-view{display:none;flex:1;min-width:0;flex-direction:column;gap:20px}
426
442
  .analytics-view.show,.settings-view.show,.logs-view.show,.migrate-view.show{display:flex}
443
+ .feed-wrap,.tasks-view,.skills-view,.analytics-view,.settings-view,.logs-view,.migrate-view{max-width:960px}
427
444
 
428
445
  /* ─── Logs ─── */
429
446
  .logs-toolbar{display:flex;align-items:center;justify-content:space-between;padding:8px 0}
@@ -463,7 +480,41 @@ input,textarea,select{font-family:inherit;font-size:inherit}
463
480
  .log-stat-chip.merged{background:rgba(168,85,247,.12);color:#c084fc}
464
481
  .log-stat-chip.errors{background:rgba(248,113,113,.12);color:#f87171}
465
482
  .log-msg-list{margin-top:8px;display:flex;flex-direction:column;gap:4px}
466
- .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)}
483
+ .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}
484
+ .log-msg-item.expanded{flex-wrap:wrap}
485
+ .recall-layers{margin-top:8px;display:flex;flex-direction:column;gap:10px}
486
+ .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}
487
+ .recall-layer-title .recall-expand-icon{transition:transform .15s;font-size:9px}
488
+ .recall-layer.expanded .recall-layer-title .recall-expand-icon{transform:rotate(90deg)}
489
+ .recall-count{font-size:10px;font-weight:700;padding:1px 6px;border-radius:10px;background:rgba(99,102,241,.1);color:var(--pri)}
490
+ .recall-items{display:none;flex-direction:column;gap:3px}
491
+ .recall-layer.expanded .recall-items{display:flex}
492
+ .recall-item{font-size:11px;line-height:1.4;padding:4px 8px;border-radius:5px;background:rgba(255,255,255,.02);cursor:pointer}
493
+ .recall-item:hover{background:rgba(99,102,241,.06)}
494
+ [data-theme="light"] .recall-item{background:rgba(0,0,0,.02)}
495
+ [data-theme="light"] .recall-item:hover{background:rgba(99,102,241,.06)}
496
+ .recall-item-head{display:flex;gap:6px;align-items:center}
497
+ .recall-idx{flex-shrink:0;font-size:10px;font-weight:600;color:var(--text-muted);min-width:14px;text-align:right}
498
+ .recall-score{flex-shrink:0;font-family:'SF Mono',Consolas,monospace;font-size:10px;font-weight:600;padding:1px 5px;border-radius:4px}
499
+ .recall-score.high{background:rgba(34,197,94,.12);color:#22c55e}
500
+ .recall-score.mid{background:rgba(251,191,36,.12);color:#f59e0b}
501
+ .recall-score.low{background:rgba(248,113,113,.1);color:var(--text-muted)}
502
+ .recall-summary-short{flex:1;color:var(--text-sec);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
503
+ .recall-expand-icon{flex-shrink:0;font-size:10px;color:var(--text-muted);transition:transform .15s}
504
+ .recall-item.expanded .recall-expand-icon{transform:rotate(90deg)}
505
+ .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)}
506
+ .recall-item.expanded .recall-summary-full{display:block}
507
+ .recall-layer.filtered .recall-layer-title{color:var(--pri)}
508
+ .recall-layer.filtered.empty .recall-layer-title{color:var(--text-muted)}
509
+ .recall-more{font-size:10px;color:var(--text-muted);padding:2px 8px}
510
+ .recall-detail{padding:4px 0}
511
+ .recall-detail-section{margin-bottom:10px}
512
+ .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}
513
+ .recall-detail-title .recall-expand-icon{transition:transform .15s;font-size:9px}
514
+ .recall-detail-section.expanded .recall-detail-title .recall-expand-icon{transform:rotate(90deg)}
515
+ .recall-detail-section .recall-detail-items{display:none;flex-direction:column;gap:3px}
516
+ .recall-detail-section.expanded .recall-detail-items{display:flex}
517
+ .recall-detail-section.filtered .recall-detail-title{color:var(--pri)}
467
518
  [data-theme="light"] .log-msg-item{background:rgba(0,0,0,.02)}
468
519
  .log-msg-role{flex-shrink:0;font-size:10px;font-weight:600;padding:1px 6px;border-radius:4px;text-transform:uppercase;letter-spacing:.3px}
469
520
  .log-msg-role.user{background:rgba(59,130,246,.12);color:#60a5fa}
@@ -476,6 +527,15 @@ input,textarea,select{font-family:inherit;font-size:inherit}
476
527
  .log-msg-action.merged{color:#c084fc}
477
528
  .log-msg-action.error{color:#f87171}
478
529
  .log-msg-text{color:var(--text);opacity:.85;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}
530
+ .log-msg-text-short{color:var(--text);opacity:.85;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
531
+ .log-msg-text-full{display:none;color:var(--text);opacity:.85;flex:1;min-width:0;word-break:break-word;white-space:pre-wrap}
532
+ .log-msg-item.expanded .log-msg-text-short{display:none}
533
+ .log-msg-item.expanded .log-msg-text-full{display:block}
534
+ .log-msg-item.expanded .recall-expand-icon{transform:rotate(90deg)}
535
+ .log-add-detail{display:flex;flex-direction:column;gap:8px}
536
+ .log-add-msg{display:flex;gap:8px;align-items:flex-start;font-size:12px;line-height:1.6}
537
+ .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)}
538
+ .log-add-msg-content{flex:1;min-width:0;word-break:break-word;white-space:pre-wrap;color:var(--text)}
479
539
  .log-detail{display:none;border-top:1px solid var(--border);padding:0}
480
540
  .log-detail.open{display:block}
481
541
  .log-expand-btn{font-size:10px;color:var(--text-sec);opacity:.5;margin-left:auto;transition:transform .2s,opacity .15s;display:inline-block}
@@ -571,18 +631,19 @@ input,textarea,select{font-family:inherit;font-size:inherit}
571
631
  @keyframes migrateFadeIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
572
632
  .feed-wrap{flex:1;min-width:0;display:flex;flex-direction:column}
573
633
  .feed-wrap.hide{display:none}
634
+ .analytics-view{flex-direction:column;gap:20px}
574
635
  .analytics-cards{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
575
- .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)}
636
+ .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)}
576
637
  .analytics-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:var(--pri);opacity:.5}
577
638
  .analytics-card::after{display:none}
578
639
  .analytics-card:hover{transform:translateY(-2px);box-shadow:var(--shadow);border-color:var(--border-glow)}
579
640
  .analytics-card.green::before{background:var(--green)}
580
641
  .analytics-card.amber::before{background:var(--amber)}
581
- .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}
642
+ .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}
582
643
  .analytics-card.green .ac-value{color:var(--green);background:none}
583
644
  .analytics-card.amber .ac-value{color:var(--amber);background:none}
584
645
  .analytics-card .ac-label{font-size:11px;color:var(--text-muted);margin-top:6px;font-weight:500;text-transform:uppercase;letter-spacing:.06em}
585
- .analytics-section{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:22px 24px;position:relative;overflow:hidden}
646
+ .analytics-section{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:18px 20px;position:relative;overflow:hidden}
586
647
  .analytics-section::before{display:none}
587
648
  .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}
588
649
  .analytics-section h3 .icon{font-size:14px;opacity:.6}
@@ -628,14 +689,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
628
689
  .tool-agg-table .ms-val.slow{color:var(--accent)}
629
690
  .chart-legend .dot.violet{background:var(--violet)}
630
691
  .chart-legend .dot.green{background:var(--green)}
631
- .breakdown-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:20px}
632
- .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}
633
- .breakdown-item:hover{border-color:var(--border-glow);background:rgba(255,255,255,.04)}
634
- .breakdown-item .bd-top{display:flex;align-items:center;justify-content:space-between}
635
- .breakdown-item .label{font-size:12px;color:var(--text-sec);font-weight:500;text-transform:capitalize}
636
- .breakdown-item .value{font-size:13px;font-weight:600;color:var(--text)}
637
- .breakdown-bar-wrap{height:3px;background:rgba(255,255,255,.06);border-radius:2px;overflow:hidden}
638
- .breakdown-bar{height:100%;border-radius:2px;background:var(--pri);transition:width .5s ease}
639
692
  .metrics-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:16px;flex-wrap:wrap}
640
693
  .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}
641
694
  .range-btn:hover{border-color:var(--pri);color:var(--pri)}
@@ -742,7 +795,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
742
795
  <div class="topbar">
743
796
  <div class="brand">
744
797
  <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>
745
- <span data-i18n="title">OpenClaw Memory</span>
798
+ <span data-i18n="title">OpenClaw Memory</span>${vBadge}
746
799
  </div>
747
800
  <div class="topbar-center">
748
801
  <nav class="nav-tabs">
@@ -771,10 +824,12 @@ input,textarea,select{font-family:inherit;font-size:inherit}
771
824
  <div class="stat-card amber"><div class="stat-value" id="statEmbeddings">-</div><div class="stat-label" data-i18n="stat.embeddings">Embeddings</div></div>
772
825
  <div class="stat-card rose"><div class="stat-value" id="statTimeSpan">-</div><div class="stat-label" data-i18n="stat.days">Days</div></div>
773
826
  </div>
774
- <div id="embeddingStatus"></div>
775
- <div class="section-title" data-i18n="sidebar.sessions">Sessions</div>
776
- <div class="session-list" id="sessionList"></div>
777
- <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>
827
+ <div id="sidebarSessionSection">
828
+ <div id="embeddingStatus"></div>
829
+ <div class="section-title" data-i18n="sidebar.sessions">Sessions</div>
830
+ <div class="session-list" id="sessionList"></div>
831
+ <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>
832
+ </div>
778
833
  </div>
779
834
 
780
835
  <div class="feed-wrap" id="feedWrap">
@@ -790,15 +845,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
790
845
  <button class="filter-chip" data-role="assistant" onclick="setRoleFilter(this,'assistant')">Assistant</button>
791
846
  <button class="filter-chip" data-role="system" onclick="setRoleFilter(this,'system')">System</button>
792
847
  <span class="filter-sep"></span>
793
- <select id="filterKind" class="filter-select" onchange="applyFilters()">
794
- <option value="" data-i18n="filter.allkinds">All kinds</option>
795
- <option value="paragraph" data-i18n="filter.paragraph">Paragraph</option>
796
- <option value="code_block" data-i18n="filter.code">Code</option>
797
- <option value="dialog" data-i18n="filter.dialog">Dialog</option>
798
- <option value="list" data-i18n="filter.list">List</option>
799
- <option value="error_stack" data-i18n="filter.error">Error</option>
800
- <option value="command" data-i18n="filter.command">Command</option>
801
- </select>
802
848
  <select id="filterSort" class="filter-select" onchange="applyFilters()">
803
849
  <option value="newest" data-i18n="filter.newest">Newest first</option>
804
850
  <option value="oldest" data-i18n="filter.oldest">Oldest first</option>
@@ -900,7 +946,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
900
946
  </div>
901
947
  </div>
902
948
  <div class="analytics-view" id="analyticsView">
903
- <div class="metrics-toolbar">
949
+ <div class="metrics-toolbar" style="margin-bottom:0">
904
950
  <span style="font-size:12px;color:var(--text-sec);font-weight:600" data-i18n="range">Range</span>
905
951
  <button class="range-btn" data-days="7" onclick="setMetricsDays(7)">7 <span data-i18n="range.days">days</span></button>
906
952
  <button class="range-btn active" data-days="30" onclick="setMetricsDays(30)">30 <span data-i18n="range.days">days</span></button>
@@ -932,16 +978,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
932
978
  <div id="toolAggTable" style="margin-top:20px"></div>
933
979
  </div>
934
980
 
935
- <div class="breakdown-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:24px">
936
- <div class="analytics-section">
937
- <h3><span class="icon">\u{1F464}</span> <span data-i18n="breakdown.role">By Role</span></h3>
938
- <div id="breakdownRole"></div>
939
- </div>
940
- <div class="analytics-section">
941
- <h3><span class="icon">\u{1F4DD}</span> <span data-i18n="breakdown.kind">By Kind</span></h3>
942
- <div id="breakdownKind"></div>
943
- </div>
944
- </div>
945
981
  </div>
946
982
 
947
983
  <!-- ─── Logs View ─── -->
@@ -965,8 +1001,11 @@ input,textarea,select{font-family:inherit;font-size:inherit}
965
1001
  <div class="settings-view" id="settingsView">
966
1002
  <div class="settings-group" id="settingsModelConfig">
967
1003
  <h2 class="settings-group-title"><span data-i18n="settings.modelconfig">Model Configuration</span></h2>
968
- <div class="model-health-bar" id="modelHealthBar">
969
- <div style="font-size:12px;color:var(--text-muted);width:100%">Loading model status...</div>
1004
+ <div class="settings-section">
1005
+ <h3><span class="icon">\u{1F4CA}</span> <span data-i18n="settings.modelhealth">Model Health</span></h3>
1006
+ <div class="model-health-bar" id="modelHealthBar">
1007
+ <div style="font-size:12px;color:var(--text-muted);width:100%">Loading model status...</div>
1008
+ </div>
970
1009
  </div>
971
1010
  <div class="settings-section">
972
1011
  <h3><span class="icon">\u{1F4E1}</span> <span data-i18n="settings.embedding">Embedding Model</span></h3>
@@ -1178,7 +1217,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1178
1217
  </div>
1179
1218
 
1180
1219
  <div id="migrateActions" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap">
1181
- <button class="btn btn-ghost" onclick="migrateScan()" id="migrateScanBtn" data-i18n="migrate.scan">Scan Data Sources</button>
1220
+ <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>
1182
1221
  <button class="btn btn-primary" onclick="migrateStart()" id="migrateStartBtn" style="display:none" data-i18n="migrate.start">Start Import</button>
1183
1222
  <span id="migrateConcurrencyRow" style="display:none;align-items:center;gap:6px">
1184
1223
  <span style="font-size:11px;color:var(--text-muted)" data-i18n="migrate.concurrency.label">Concurrent agents</span>
@@ -1319,14 +1358,13 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1319
1358
  <!-- ─── Memory Modal ─── -->
1320
1359
  <div class="modal-overlay" id="modalOverlay">
1321
1360
  <div class="modal">
1322
- <h2 id="modalTitle" data-i18n="modal.new">New Memory</h2>
1361
+ <h2 id="modalTitle" data-i18n="modal.edit">Edit Memory</h2>
1323
1362
  <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>
1324
1363
  <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>
1325
1364
  <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>
1326
- <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>
1327
1365
  <div class="modal-actions">
1328
1366
  <button class="btn btn-ghost" onclick="closeModal()" data-i18n="modal.cancel">Cancel</button>
1329
- <button class="btn btn-primary" id="modalSubmit" onclick="submitModal()" data-i18n="modal.create">Create</button>
1367
+ <button class="btn btn-primary" id="modalSubmit" onclick="submitModal()" data-i18n="modal.save">Save</button>
1330
1368
  </div>
1331
1369
  </div>
1332
1370
  </div>
@@ -1336,6 +1374,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1336
1374
 
1337
1375
  <script>
1338
1376
  let activeSession=null,activeRole='',editingId=null,searchTimer=null,memoryCache={},currentPage=1,totalPages=1,totalCount=0,PAGE_SIZE=40,metricsDays=30;
1377
+ let _embeddingWarningShown=false;
1339
1378
 
1340
1379
  /* ─── i18n ─── */
1341
1380
  const I18N={
@@ -1395,6 +1434,8 @@ const I18N={
1395
1434
  'tasks.untitled':'Untitled Task',
1396
1435
  'tasks.chunks':'Related Memories',
1397
1436
  'tasks.nochunks':'No memories in this task yet.',
1437
+ 'tasks.expand':'Show more',
1438
+ 'tasks.collapse':'Show less',
1398
1439
  'tasks.skipped.default':'This conversation was too brief to generate a summary. It will not appear in search results.',
1399
1440
  'refresh':'\\u21BB Refresh',
1400
1441
  'logout':'Logout',
@@ -1413,13 +1454,6 @@ const I18N={
1413
1454
  'search.meta.text':' text',
1414
1455
  'search.meta.results':' results',
1415
1456
  'filter.all':'All',
1416
- 'filter.allkinds':'All kinds',
1417
- 'filter.paragraph':'Paragraph',
1418
- 'filter.code':'Code',
1419
- 'filter.dialog':'Dialog',
1420
- 'filter.list':'List',
1421
- 'filter.error':'Error',
1422
- 'filter.command':'Command',
1423
1457
  'filter.newest':'Newest first',
1424
1458
  'filter.oldest':'Oldest first',
1425
1459
  'filter.allowners':'All owners',
@@ -1435,6 +1469,8 @@ const I18N={
1435
1469
  'card.delete':'Delete',
1436
1470
  'card.evolved':'Evolved',
1437
1471
  'card.times':'times',
1472
+ 'card.newMessage':'New message',
1473
+ 'card.mergedInfo':'Merged memory',
1438
1474
  'card.updated':'updated',
1439
1475
  'card.evolveHistory':'Evolution History',
1440
1476
  'card.oldSummary':'Old',
@@ -1458,21 +1494,15 @@ const I18N={
1458
1494
  'chart.toolperf':'Tool Response Time',
1459
1495
  'chart.list':'List',
1460
1496
  'chart.search':'Search',
1461
- 'breakdown.role':'By Role',
1462
- 'breakdown.kind':'By Kind',
1463
- 'modal.new':'New Memory',
1464
1497
  'modal.edit':'Edit Memory',
1465
1498
  'modal.role':'Role',
1466
1499
  'modal.content':'Content',
1467
1500
  'modal.content.ph':'Memory content...',
1468
1501
  'modal.summary':'Summary',
1469
1502
  'modal.summary.ph':'Brief summary (optional)',
1470
- 'modal.kind':'Kind',
1471
1503
  'modal.cancel':'Cancel',
1472
- 'modal.create':'Create',
1473
1504
  'modal.save':'Save',
1474
1505
  'modal.err.empty':'Please enter content',
1475
- 'toast.created':'Memory created',
1476
1506
  'toast.updated':'Memory updated',
1477
1507
  'toast.deleted':'Memory deleted',
1478
1508
  'toast.opfail':'Operation failed',
@@ -1487,6 +1517,9 @@ const I18N={
1487
1517
  'confirm.clearall2':'Are you absolutely sure?',
1488
1518
  'embed.on':'Embedding: ',
1489
1519
  'embed.off':'No embedding model',
1520
+ 'embed.warn.local':'Using built-in mini model (384d). Search quality is limited — configure an embedding model in Settings for best results.',
1521
+ 'embed.err.fail':'Embedding model error detected. Check Settings → Model Health.',
1522
+ 'embed.banner.goto':'Go to Settings',
1490
1523
  'lang.switch':'中',
1491
1524
  'tab.logs':'\u{1F4DD} Logs',
1492
1525
  'logs.allTools':'All Tools',
@@ -1496,9 +1529,15 @@ const I18N={
1496
1529
  'logs.output':'OUTPUT',
1497
1530
  'logs.empty':'No logs yet. Logs will appear here when tools are called.',
1498
1531
  'logs.ago':'ago',
1532
+ 'logs.recall.initial':'Initial Retrieval',
1533
+ 'logs.recall.filtered':'LLM Filtered',
1534
+ 'logs.recall.noHits':'No matching memories',
1535
+ 'logs.recall.noneRelevant':'LLM filter: none relevant',
1536
+ 'logs.recall.more':'{n} more...',
1499
1537
  'tab.import':'\u{1F4E5} Import',
1500
1538
  'tab.settings':'\u2699 Settings',
1501
1539
  'settings.modelconfig':'Model Configuration',
1540
+ 'settings.modelhealth':'Model Health',
1502
1541
  'settings.embedding':'Embedding Model',
1503
1542
  'settings.summarizer':'Summarizer Model',
1504
1543
  'settings.skill':'Skill Evolution',
@@ -1523,6 +1562,7 @@ const I18N={
1523
1562
  'settings.test.loading':'Testing...',
1524
1563
  'settings.test.ok':'Connected',
1525
1564
  'settings.test.fail':'Failed',
1565
+ 'settings.session.expired':'Session expired, please refresh the page to log in again',
1526
1566
  'settings.save':'Save Settings',
1527
1567
  'settings.reset':'Reset',
1528
1568
  'settings.saved':'Saved',
@@ -1555,12 +1595,18 @@ const I18N={
1555
1595
  'migrate.scan':'Scan Data Sources',
1556
1596
  'migrate.start':'Start Import',
1557
1597
  'migrate.scanning':'Scanning...',
1598
+ 'migrate.scan.required':'Please scan data sources first',
1599
+ 'migrate.scan.done':'Scan complete \u2014 {n} new items found',
1600
+ 'migrate.imported.hint':'{n} items already imported',
1601
+ 'migrate.reconnect.hint':'--- {n} items processed before page reload ---',
1558
1602
  'migrate.stat.stored':'Stored',
1559
1603
  'migrate.stat.skipped':'Skipped',
1560
1604
  'migrate.stat.merged':'Merged',
1561
1605
  'migrate.stat.errors':'Errors',
1562
1606
  'migrate.phase.sqlite':'Importing memory index...',
1563
1607
  'migrate.phase.sessions':'Importing conversation history...',
1608
+ 'migrate.phase.stopped':'Import stopped',
1609
+ 'migrate.phase.done':'Import completed',
1564
1610
  'migrate.chunks':'chunks',
1565
1611
  'migrate.sessions.count':'sessions, {n} messages',
1566
1612
  'migrate.nodata':'No OpenClaw data found to import.',
@@ -1586,7 +1632,8 @@ const I18N={
1586
1632
  'pp.select.warn':'Please select at least one option.',
1587
1633
  'pp.skill.created':'Skill created',
1588
1634
  'pp.stat.tasks':'Tasks',
1589
- 'pp.stat.skills':'Skills',
1635
+ 'pp.stat.skills':'Evolutions',
1636
+ 'pp.stat.skills.total':'Skills',
1590
1637
  'pp.stat.errors':'Errors',
1591
1638
  'pp.stat.skipped':'Skipped',
1592
1639
  'pp.info.skipped':'{n} sessions already processed, skipping.',
@@ -1646,6 +1693,10 @@ const I18N={
1646
1693
  'skill.save.error':'Failed to save skill: ',
1647
1694
  'update.available':'New version available',
1648
1695
  'update.run':'Run',
1696
+ 'update.btn':'Update now',
1697
+ 'update.installing':'Installing update...',
1698
+ 'update.success':'Update installed! Restarting...',
1699
+ 'update.failed':'Update failed',
1649
1700
  'update.dismiss':'Dismiss'
1650
1701
  },
1651
1702
  zh:{
@@ -1704,6 +1755,8 @@ const I18N={
1704
1755
  'tasks.untitled':'未命名任务',
1705
1756
  'tasks.chunks':'关联记忆',
1706
1757
  'tasks.nochunks':'此任务暂无关联记忆。',
1758
+ 'tasks.expand':'展开全文',
1759
+ 'tasks.collapse':'收起',
1707
1760
  'tasks.skipped.default':'对话内容过少,未生成摘要。该任务不会出现在检索结果中。',
1708
1761
  'refresh':'\\u21BB 刷新',
1709
1762
  'logout':'退出',
@@ -1722,13 +1775,6 @@ const I18N={
1722
1775
  'search.meta.text':' 文本',
1723
1776
  'search.meta.results':' 条结果',
1724
1777
  'filter.all':'全部',
1725
- 'filter.allkinds':'所有类型',
1726
- 'filter.paragraph':'段落',
1727
- 'filter.code':'代码',
1728
- 'filter.dialog':'对话',
1729
- 'filter.list':'列表',
1730
- 'filter.error':'错误',
1731
- 'filter.command':'命令',
1732
1778
  'filter.newest':'最新优先',
1733
1779
  'filter.oldest':'最早优先',
1734
1780
  'filter.allowners':'所有归属',
@@ -1744,6 +1790,8 @@ const I18N={
1744
1790
  'card.delete':'删除',
1745
1791
  'card.evolved':'已演化',
1746
1792
  'card.times':'次',
1793
+ 'card.newMessage':'新消息',
1794
+ 'card.mergedInfo':'合并记忆',
1747
1795
  'card.updated':'更新于',
1748
1796
  'card.evolveHistory':'演化记录',
1749
1797
  'card.oldSummary':'旧摘要',
@@ -1767,21 +1815,15 @@ const I18N={
1767
1815
  'chart.toolperf':'工具响应耗时',
1768
1816
  'chart.list':'列表',
1769
1817
  'chart.search':'搜索',
1770
- 'breakdown.role':'按角色',
1771
- 'breakdown.kind':'按类型',
1772
- 'modal.new':'新建记忆',
1773
1818
  'modal.edit':'编辑记忆',
1774
1819
  'modal.role':'角色',
1775
1820
  'modal.content':'内容',
1776
1821
  'modal.content.ph':'记忆内容...',
1777
1822
  'modal.summary':'摘要',
1778
1823
  'modal.summary.ph':'简要摘要(可选)',
1779
- 'modal.kind':'类型',
1780
1824
  'modal.cancel':'取消',
1781
- 'modal.create':'创建',
1782
1825
  'modal.save':'保存',
1783
1826
  'modal.err.empty':'请输入内容',
1784
- 'toast.created':'记忆已创建',
1785
1827
  'toast.updated':'记忆已更新',
1786
1828
  'toast.deleted':'记忆已删除',
1787
1829
  'toast.opfail':'操作失败',
@@ -1796,6 +1838,9 @@ const I18N={
1796
1838
  'confirm.clearall2':'你真的确定吗?',
1797
1839
  'embed.on':'嵌入模型:',
1798
1840
  'embed.off':'无嵌入模型',
1841
+ 'embed.warn.local':'当前使用内置迷你模型(384维),搜索效果有限。强烈建议在「设置」中配置专用 Embedding 模型以获得最佳效果。',
1842
+ 'embed.err.fail':'Embedding 模型调用异常,请前往「设置 → 模型健康」检查。',
1843
+ 'embed.banner.goto':'前往设置',
1799
1844
  'lang.switch':'EN',
1800
1845
  'tab.logs':'\u{1F4DD} 日志',
1801
1846
  'logs.allTools':'全部工具',
@@ -1805,9 +1850,15 @@ const I18N={
1805
1850
  'logs.output':'输出',
1806
1851
  'logs.empty':'暂无日志。当工具被调用时日志会显示在这里。',
1807
1852
  'logs.ago':'前',
1853
+ 'logs.recall.initial':'初始检索',
1854
+ 'logs.recall.filtered':'LLM 过滤后',
1855
+ 'logs.recall.noHits':'未匹配到记忆',
1856
+ 'logs.recall.noneRelevant':'LLM 过滤:无相关记忆',
1857
+ 'logs.recall.more':'还有 {n} 条...',
1808
1858
  'tab.import':'\u{1F4E5} 导入',
1809
1859
  'tab.settings':'\u2699 设置',
1810
1860
  'settings.modelconfig':'模型配置',
1861
+ 'settings.modelhealth':'模型健康',
1811
1862
  'settings.embedding':'嵌入模型',
1812
1863
  'settings.summarizer':'摘要模型',
1813
1864
  'settings.skill':'技能进化',
@@ -1832,6 +1883,7 @@ const I18N={
1832
1883
  'settings.test.loading':'测试中...',
1833
1884
  'settings.test.ok':'连接成功',
1834
1885
  'settings.test.fail':'连接失败',
1886
+ 'settings.session.expired':'登录已过期,请刷新页面重新登录',
1835
1887
  'settings.save':'保存设置',
1836
1888
  'settings.reset':'重置',
1837
1889
  'settings.saved':'已保存',
@@ -1864,12 +1916,18 @@ const I18N={
1864
1916
  'migrate.scan':'扫描数据源',
1865
1917
  'migrate.start':'开始导入',
1866
1918
  'migrate.scanning':'扫描中...',
1919
+ 'migrate.scan.required':'请先扫描数据源',
1920
+ 'migrate.scan.done':'扫描完成 — 发现 {n} 条新数据可导入',
1921
+ 'migrate.imported.hint':'已导入 {n} 条记忆',
1922
+ 'migrate.reconnect.hint':'--- 页面刷新前已处理 {n} 条 ---',
1867
1923
  'migrate.stat.stored':'已存储',
1868
1924
  'migrate.stat.skipped':'已跳过',
1869
1925
  'migrate.stat.merged':'已合并',
1870
1926
  'migrate.stat.errors':'错误',
1871
1927
  'migrate.phase.sqlite':'正在导入记忆索引...',
1872
1928
  'migrate.phase.sessions':'正在导入对话历史...',
1929
+ 'migrate.phase.stopped':'导入已停止',
1930
+ 'migrate.phase.done':'导入完成',
1873
1931
  'migrate.chunks':'条记忆',
1874
1932
  'migrate.sessions.count':'个会话,{n} 条消息',
1875
1933
  'migrate.nodata':'未找到可导入的 OpenClaw 数据。',
@@ -1895,7 +1953,8 @@ const I18N={
1895
1953
  'pp.select.warn':'请至少选择一个选项。',
1896
1954
  'pp.skill.created':'技能已创建',
1897
1955
  'pp.stat.tasks':'任务',
1898
- 'pp.stat.skills':'技能',
1956
+ 'pp.stat.skills':'进化',
1957
+ 'pp.stat.skills.total':'技能',
1899
1958
  'pp.stat.errors':'错误',
1900
1959
  'pp.stat.skipped':'已跳过',
1901
1960
  'pp.info.skipped':'已有 {n} 个会话处理过,自动跳过。',
@@ -1955,6 +2014,10 @@ const I18N={
1955
2014
  'skill.save.error':'保存技能失败:',
1956
2015
  'update.available':'发现新版本',
1957
2016
  'update.run':'执行命令',
2017
+ 'update.btn':'立即更新',
2018
+ 'update.installing':'正在安装更新...',
2019
+ 'update.success':'更新成功!正在重启...',
2020
+ 'update.failed':'更新失败',
1958
2021
  'update.dismiss':'关闭'
1959
2022
  }
1960
2023
  };
@@ -2071,6 +2134,7 @@ function switchView(view){
2071
2134
  const logsView=document.getElementById('logsView');
2072
2135
  const settingsView=document.getElementById('settingsView');
2073
2136
  const migrateView=document.getElementById('migrateView');
2137
+ const sidebar=document.getElementById('sidebar');
2074
2138
  feedWrap.classList.add('hide');
2075
2139
  analyticsView.classList.remove('show');
2076
2140
  tasksView.classList.remove('show');
@@ -2078,27 +2142,33 @@ function switchView(view){
2078
2142
  logsView.classList.remove('show');
2079
2143
  settingsView.classList.remove('show');
2080
2144
  migrateView.classList.remove('show');
2081
- if(view==='analytics'){
2082
- analyticsView.classList.add('show');
2083
- loadMetrics();
2084
- } else if(view==='tasks'){
2085
- tasksView.classList.add('show');
2086
- loadTasks();
2087
- } else if(view==='skills'){
2088
- skillsView.classList.add('show');
2089
- loadSkills();
2090
- } else if(view==='logs'){
2091
- logsView.classList.add('show');
2092
- loadLogs();
2093
- } else if(view==='settings'){
2094
- settingsView.classList.add('show');
2095
- loadConfig();
2096
- loadModelHealth();
2097
- } else if(view==='import'){
2098
- migrateView.classList.add('show');
2099
- if(!window._migrateRunning) migrateScan();
2100
- } else {
2145
+ const sessionSection=document.getElementById('sidebarSessionSection');
2146
+ if(view==='memories'){
2101
2147
  feedWrap.classList.remove('hide');
2148
+ sessionSection.style.visibility='';
2149
+ sessionSection.style.pointerEvents='';
2150
+ } else if(view==='tasks'||view==='skills'){
2151
+ sessionSection.style.visibility='hidden';
2152
+ sessionSection.style.pointerEvents='none';
2153
+ if(view==='tasks'){tasksView.classList.add('show');loadTasks();}
2154
+ else{skillsView.classList.add('show');loadSkills();}
2155
+ } else {
2156
+ sessionSection.style.visibility='hidden';
2157
+ sessionSection.style.pointerEvents='none';
2158
+ if(view==='analytics'){
2159
+ analyticsView.classList.add('show');
2160
+ loadMetrics();
2161
+ } else if(view==='logs'){
2162
+ logsView.classList.add('show');
2163
+ loadLogs();
2164
+ } else if(view==='settings'){
2165
+ settingsView.classList.add('show');
2166
+ loadConfig();
2167
+ loadModelHealth();
2168
+ } else if(view==='import'){
2169
+ migrateView.classList.add('show');
2170
+ if(!window._migrateRunning) migrateScan(false);
2171
+ }
2102
2172
  }
2103
2173
  }
2104
2174
 
@@ -2165,6 +2235,39 @@ function formatLogTime(ts){
2165
2235
  return y+'-'+m+'-'+day+' '+time;
2166
2236
  }
2167
2237
 
2238
+ function parseMemoryAddEntries(out){
2239
+ var lines=out.split('\\n');
2240
+ var results=[];
2241
+ for(var i=0;i<lines.length;i++){
2242
+ var line=lines[i].trim();
2243
+ if(!line) continue;
2244
+ if(line.startsWith('{')){
2245
+ try{
2246
+ var obj=JSON.parse(line);
2247
+ if(obj.role&&obj.action){results.push({role:obj.role,action:obj.action,summary:obj.summary||'',content:obj.content||'',reason:obj.reason||''});continue;}
2248
+ }catch(e){}
2249
+ }
2250
+ var rm=line.match(/^\\[(\\w+)\\]\\s*([^\u2192]+)\u2192/);
2251
+ if(rm){
2252
+ var role=rm[1],actionRaw=rm[2].trim();
2253
+ var action='stored';
2254
+ if(actionRaw.indexOf('exact-dup')>=0||actionRaw.indexOf('\u23ED')>=0) action='exact-dup';
2255
+ else if(actionRaw.indexOf('dedup')>=0||actionRaw.indexOf('\uD83D\uDD01')>=0) action='dedup';
2256
+ else if(actionRaw.indexOf('merged')>=0||actionRaw.indexOf('\uD83D\uDD00')>=0) action='merged';
2257
+ else if(actionRaw.indexOf('error')>=0||actionRaw.indexOf('\u274C')>=0) action='error';
2258
+ var afterArrow=line.replace(/^\\[\\w+\\]\\s*[^\u2192]+\u2192\\s*/,'');
2259
+ var contentLines=[afterArrow];
2260
+ while(i+1<lines.length&&!lines[i+1].trim().startsWith('[')&&!lines[i+1].trim().startsWith('{')){
2261
+ i++;
2262
+ if(lines[i].trim()) contentLines.push(lines[i]);
2263
+ else contentLines.push('');
2264
+ }
2265
+ results.push({role:role,action:action,summary:'',content:contentLines.join('\\n'),reason:''});
2266
+ }
2267
+ }
2268
+ return results;
2269
+ }
2270
+
2168
2271
  function buildLogSummary(lg){
2169
2272
  let inputObj=null;
2170
2273
  try{inputObj=JSON.parse(lg.input);}catch(_){}
@@ -2172,11 +2275,54 @@ function buildLogSummary(lg){
2172
2275
  const tn=lg.toolName;
2173
2276
  if(tn==='memory_search'&&inputObj){
2174
2277
  const q=inputObj.query||'';
2175
- if(q) html+='<div class="log-summary-query">'+escapeHtml(q.length>200?q.slice(0,200)+'...':q)+'</div>';
2176
- const outLines=(lg.output||'').split('\\n');
2177
- const memCount=outLines.filter(l=>l.match(/^\\d+\\.\\s*\\[/)).length;
2178
- if(memCount>0) html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u{1F4CE} '+memCount+' memories retrieved</div>';
2179
- 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>';
2278
+ if(q) html+='<div class="log-summary-query">'+escapeHtml(q)+'</div>';
2279
+ var recallData=null;
2280
+ try{recallData=JSON.parse(lg.output);}catch(_){}
2281
+ if(recallData&&recallData.candidates){
2282
+ var cands=recallData.candidates||[];
2283
+ var filtered=recallData.filtered||[];
2284
+ if(cands.length===0){
2285
+ html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u2205 '+t('logs.recall.noHits')+'</div>';
2286
+ }else{
2287
+ html+='<div class="recall-layers">';
2288
+ html+='<div class="recall-layer" onclick="this.classList.toggle(\\\'expanded\\\')">';
2289
+ 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>';
2290
+ html+='<div class="recall-items">';
2291
+ cands.forEach(function(c,i){
2292
+ var scoreClass=c.score>=0.7?'high':c.score>=0.5?'mid':'low';
2293
+ var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
2294
+ var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
2295
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2296
+ 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>';
2297
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2298
+ html+='</div>';
2299
+ });
2300
+ html+='</div></div>';
2301
+ if(filtered.length>0){
2302
+ html+='<div class="recall-layer filtered" onclick="this.classList.toggle(\\\'expanded\\\')">';
2303
+ 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>';
2304
+ html+='<div class="recall-items">';
2305
+ filtered.forEach(function(f){
2306
+ var scoreClass=f.score>=0.7?'high':f.score>=0.5?'mid':'low';
2307
+ var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
2308
+ var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
2309
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2310
+ 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>';
2311
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2312
+ html+='</div>';
2313
+ });
2314
+ html+='</div></div>';
2315
+ }else{
2316
+ html+='<div style="font-size:10px;color:var(--text-muted);margin-top:2px">\u26A0 '+t('logs.recall.noneRelevant')+'</div>';
2317
+ }
2318
+ html+='</div>';
2319
+ }
2320
+ }else{
2321
+ var outLines=(lg.output||'').split('\\n');
2322
+ var memCount=outLines.filter(function(l){return l.match(/^\\d+\\.\\s*\\[/)}).length;
2323
+ if(memCount>0) html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u{1F4CE} '+memCount+' memories retrieved</div>';
2324
+ 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>';
2325
+ }
2180
2326
  }else if(tn==='memory_add'&&inputObj){
2181
2327
  const out=lg.output||'';
2182
2328
  const statsMatch=out.match(/^([^\\n]+)/);
@@ -2189,39 +2335,18 @@ function buildLogSummary(lg){
2189
2335
  });
2190
2336
  html+='</div>';
2191
2337
  }
2192
- const outLines=out.split('\\n').filter(l=>l.startsWith('['));
2193
- if(outLines.length>0){
2338
+ var parsed=parseMemoryAddEntries(out);
2339
+ if(parsed.length>0){
2194
2340
  html+='<div class="log-msg-list">';
2195
- outLines.forEach(function(l){
2196
- var rm=l.match(/^\\[(\\w+)\\]\\s*([^\u2192]+)\u2192\\s*(.*)/);
2197
- if(rm){
2198
- var role=rm[1],actionRaw=rm[2].trim(),text=rm[3].trim();
2199
- var actionCls='stored';
2200
- if(actionRaw.indexOf('exact-dup')>=0||actionRaw.indexOf('\u23ED')>=0) actionCls='exact-dup';
2201
- else if(actionRaw.indexOf('dedup')>=0||actionRaw.indexOf('\uD83D\uDD01')>=0) actionCls='dedup';
2202
- else if(actionRaw.indexOf('merged')>=0||actionRaw.indexOf('\uD83D\uDD00')>=0) actionCls='merged';
2203
- else if(actionRaw.indexOf('error')>=0||actionRaw.indexOf('\u274C')>=0) actionCls='error';
2204
- var actionLabel={'stored':'\u2713 stored','exact-dup':'\u23ED skip','dedup':'\uD83D\uDD01 dedup','merged':'\uD83D\uDD00 merged','error':'\u2717 error'}[actionCls]||actionCls;
2205
- html+='<div class="log-msg-item">'+
2206
- '<span class="log-msg-role '+role+'">'+role+'</span>'+
2207
- '<span class="log-msg-action '+actionCls+'">'+actionLabel+'</span>'+
2208
- '<span class="log-msg-text">'+escapeHtml(text.length>150?text.slice(0,150)+'...':text)+'</span>'+
2209
- '</div>';
2210
- }else{
2211
- html+='<div class="log-msg-item"><span class="log-msg-text">'+escapeHtml(l.length>200?l.slice(0,200)+'...':l)+'</span></div>';
2212
- }
2213
- });
2214
- html+='</div>';
2215
- }else if(inputObj.details&&Array.isArray(inputObj.details)&&inputObj.details.length>0){
2216
- html+='<div class="log-msg-list">';
2217
- inputObj.details.forEach(function(d){
2218
- var s=typeof d==='string'?d:String(d);
2219
- var dm=s.match(/^\\[(\\w+)\\]\\s*(.*)/);
2220
- if(dm){
2221
- 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>';
2222
- }else{
2223
- html+='<div class="log-msg-item"><span class="log-msg-text">'+escapeHtml(s.length>150?s.slice(0,150)+'...':s)+'</span></div>';
2224
- }
2341
+ parsed.forEach(function(e){
2342
+ var actionCls=e.action==='exact-dup'?'exact-dup':e.action==='dedup'?'dedup':e.action==='merged'?'merged':e.action==='error'?'error':'stored';
2343
+ var actionLabel={'stored':'\u2713 stored','exact-dup':'\u23ED skip','dedup':'\uD83D\uDD01 dedup','merged':'\uD83D\uDD00 merged','error':'\u2717 error'}[actionCls]||actionCls;
2344
+ var displayText=e.content.split('\\n')[0].trim();
2345
+ html+='<div class="log-msg-item">'+
2346
+ '<span class="log-msg-role '+e.role+'">'+e.role+'</span>'+
2347
+ '<span class="log-msg-action '+actionCls+'">'+actionLabel+'</span>'+
2348
+ '<span class="log-msg-text">'+escapeHtml(displayText)+'</span>'+
2349
+ '</div>';
2225
2350
  });
2226
2351
  html+='</div>';
2227
2352
  }
@@ -2229,11 +2354,53 @@ function buildLogSummary(lg){
2229
2354
  const keys=Object.keys(inputObj);
2230
2355
  keys.slice(0,4).forEach(k=>{
2231
2356
  const v=String(inputObj[k]);
2232
- 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>';
2357
+ html+='<span class="log-summary-kv"><span class="kv-label">'+escapeHtml(k)+':</span><span class="kv-val">'+escapeHtml(v)+'</span></span>';
2233
2358
  });
2234
2359
  }
2235
2360
  return html;
2236
2361
  }
2362
+ function buildRecallDetailHtml(rd){
2363
+ var html='<div class="recall-detail">';
2364
+ var cands=rd.candidates||[];
2365
+ var filtered=rd.filtered||[];
2366
+ if(cands.length>0){
2367
+ html+='<div class="recall-detail-section" onclick="this.classList.toggle(\\\'expanded\\\')">';
2368
+ html+='<div class="recall-detail-title"><span class="recall-expand-icon">\u25B6</span>\u{1F50D} '+t('logs.recall.initial')+' ('+cands.length+')</div>';
2369
+ html+='<div class="recall-detail-items">';
2370
+ cands.forEach(function(c,i){
2371
+ var scoreClass=c.score>=0.7?'high':c.score>=0.5?'mid':'low';
2372
+ var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
2373
+ var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
2374
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2375
+ 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>';
2376
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2377
+ html+='</div>';
2378
+ });
2379
+ html+='</div></div>';
2380
+ }
2381
+ if(filtered.length>0){
2382
+ html+='<div class="recall-detail-section filtered" onclick="this.classList.toggle(\\\'expanded\\\')">';
2383
+ html+='<div class="recall-detail-title"><span class="recall-expand-icon">\u25B6</span>\u2705 '+t('logs.recall.filtered')+' ('+filtered.length+')</div>';
2384
+ html+='<div class="recall-detail-items">';
2385
+ filtered.forEach(function(f,i){
2386
+ var scoreClass=f.score>=0.7?'high':f.score>=0.5?'mid':'low';
2387
+ var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
2388
+ var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
2389
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2390
+ 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>';
2391
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2392
+ html+='</div>';
2393
+ });
2394
+ html+='</div></div>';
2395
+ }else if(cands.length>0){
2396
+ html+='<div style="font-size:10px;color:var(--text-muted);margin-top:4px">\u26A0 '+t('logs.recall.noneRelevant')+'</div>';
2397
+ }
2398
+ if(rd.status==='error'&&rd.error){
2399
+ html+='<div style="margin-top:8px;color:var(--accent);font-size:12px">\u274C '+escapeHtml(rd.error)+'</div>';
2400
+ }
2401
+ html+='</div>';
2402
+ return html;
2403
+ }
2237
2404
  function renderLogs(logs){
2238
2405
  const el=document.getElementById('logsList');
2239
2406
  if(!logs.length){
@@ -2246,7 +2413,29 @@ function renderLogs(logs){
2246
2413
  const toolCls=lg.toolName.replace(/[^a-zA-Z0-9_]/g,'_');
2247
2414
  const dur=lg.durationMs<1000?Math.round(lg.durationMs)+'ms':(lg.durationMs/1000).toFixed(1)+'s';
2248
2415
  let inputDisplay='';
2249
- try{const parsed=JSON.parse(lg.input);inputDisplay=JSON.stringify(parsed,null,2);}catch(_){inputDisplay=lg.input;}
2416
+ let inputHtml='';
2417
+ let outputHtml='';
2418
+ try{
2419
+ const parsed=JSON.parse(lg.input);
2420
+ if(lg.toolName==='memory_add'){
2421
+ var addEntries=parseMemoryAddEntries(lg.output||'');
2422
+ if(addEntries.length>0){
2423
+ inputHtml='<div class="log-add-detail">';
2424
+ addEntries.forEach(function(e){
2425
+ 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>';
2426
+ });
2427
+ inputHtml+='</div>';
2428
+ }
2429
+ }else if(parsed.type==='auto_recall'||parsed.type==='tool_call'){
2430
+ inputDisplay=JSON.stringify({query:parsed.query},null,2);
2431
+ }else{
2432
+ inputDisplay=JSON.stringify(parsed,null,2);
2433
+ }
2434
+ }catch(_){inputDisplay=lg.input;}
2435
+ try{
2436
+ var rd2=null;try{rd2=JSON.parse(lg.output);}catch(_e){}
2437
+ if(rd2&&rd2.candidates){outputHtml=buildRecallDetailHtml(rd2);}
2438
+ }catch(_){}
2250
2439
  const summary=buildLogSummary(lg);
2251
2440
  return '<div class="log-entry" id="log-'+i+'">'+
2252
2441
  '<div class="log-header" onclick="toggleLog('+i+')">'+
@@ -2260,11 +2449,11 @@ function renderLogs(logs){
2260
2449
  '<div class="log-detail" id="log-detail-'+i+'">'+
2261
2450
  '<div class="log-io-section">'+
2262
2451
  '<div class="log-io-label">\u25B6 '+t('logs.input')+'</div>'+
2263
- '<pre class="log-io-content">'+escapeHtml(inputDisplay)+'</pre>'+
2452
+ (inputHtml?inputHtml:'<pre class="log-io-content">'+escapeHtml(inputDisplay)+'</pre>')+
2264
2453
  '</div>'+
2265
2454
  '<div class="log-io-section">'+
2266
2455
  '<div class="log-io-label">\u25C0 '+t('logs.output')+'</div>'+
2267
- '<pre class="log-io-content">'+escapeHtml(lg.output)+'</pre>'+
2456
+ (outputHtml?outputHtml:'<pre class="log-io-content">'+escapeHtml(lg.output)+'</pre>')+
2268
2457
  '</div>'+
2269
2458
  '</div>'+
2270
2459
  '</div>';
@@ -2307,8 +2496,6 @@ async function loadMetrics(){
2307
2496
  document.getElementById('mSessions').textContent=formatNum(d.totals.sessions);
2308
2497
  document.getElementById('mEmbeddings').textContent=formatNum(d.totals.embeddings);
2309
2498
  renderChartWrites(d.writesPerDay);
2310
- renderBreakdown(d.roleBreakdown,'breakdownRole');
2311
- renderBreakdown(d.kindBreakdown,'breakdownKind');
2312
2499
  loadToolMetrics();
2313
2500
  }
2314
2501
 
@@ -2451,14 +2638,16 @@ async function openTaskDetail(taskId){
2451
2638
  if(task.chunks.length===0){
2452
2639
  document.getElementById('taskDetailChunks').innerHTML='<div style="color:var(--text-muted);padding:12px;font-size:13px">'+t('tasks.nochunks')+'</div>';
2453
2640
  }else{
2454
- document.getElementById('taskDetailChunks').innerHTML=task.chunks.map(c=>{
2641
+ document.getElementById('taskDetailChunks').innerHTML=task.chunks.map(function(c,i){
2455
2642
  var roleLabel=c.role==='user'?t('tasks.role.user'):c.role==='assistant'?t('tasks.role.assistant'):c.role.toUpperCase();
2456
2643
  return '<div class="task-chunk-item role-'+c.role+'">'+
2457
2644
  '<div class="task-chunk-role '+c.role+'">'+roleLabel+'</div>'+
2458
- '<div class="task-chunk-bubble" onclick="this.classList.toggle(\\\'expanded\\\')">'+esc(c.content)+'</div>'+
2645
+ '<div class="task-chunk-bubble collapsed" id="chunk_b_'+i+'">'+esc(c.content)+'</div>'+
2646
+ '<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>'+
2459
2647
  '<div class="task-chunk-time">'+formatTime(c.createdAt)+'</div>'+
2460
2648
  '</div>';
2461
2649
  }).join('');
2650
+ setTimeout(function(){initChunkExpanders(task.chunks.length)},50);
2462
2651
  }
2463
2652
  }catch(e){
2464
2653
  document.getElementById('taskDetailTitle').textContent=t('tasks.error');
@@ -2522,6 +2711,33 @@ function renderTaskSkillSection(task){
2522
2711
  }
2523
2712
  }
2524
2713
 
2714
+ function initChunkExpanders(count){
2715
+ for(var i=0;i<count;i++){
2716
+ var b=document.getElementById('chunk_b_'+i);
2717
+ var e=document.getElementById('chunk_e_'+i);
2718
+ if(b && b.scrollHeight > b.clientHeight + 4){
2719
+ e.style.display='flex';
2720
+ } else if(b) {
2721
+ b.classList.remove('collapsed');
2722
+ }
2723
+ }
2724
+ }
2725
+ function toggleChunkExpand(i){
2726
+ var b=document.getElementById('chunk_b_'+i);
2727
+ var e=document.getElementById('chunk_e_'+i);
2728
+ if(!b||!e)return;
2729
+ var expanding=b.classList.contains('collapsed');
2730
+ if(expanding){
2731
+ b.classList.remove('collapsed');
2732
+ e.classList.add('is-expanded');
2733
+ e.querySelector('.expand-label').textContent=t('tasks.collapse');
2734
+ }else{
2735
+ b.classList.add('collapsed');
2736
+ e.classList.remove('is-expanded');
2737
+ e.querySelector('.expand-label').textContent=t('tasks.expand');
2738
+ }
2739
+ }
2740
+
2525
2741
  function closeTaskDetail(event){
2526
2742
  if(event && event.target!==document.getElementById('taskDetailOverlay')) return;
2527
2743
  document.getElementById('taskDetailOverlay').classList.remove('show');
@@ -2795,7 +3011,7 @@ function classifyError(msg){
2795
3011
  if(msg.indexOf('ECONNREFUSED')>=0) return 'Connection refused';
2796
3012
  if(msg.indexOf('ENOTFOUND')>=0) return 'DNS resolution failed';
2797
3013
  if(msg.indexOf('403')>=0) return 'Forbidden (403)';
2798
- return msg.length>50?msg.slice(0,47)+'...':msg;
3014
+ return msg;
2799
3015
  }
2800
3016
 
2801
3017
  function shortenModel(s){return s?s.replace('openai_compatible/','').replace('openai/',''):'\u2014';}
@@ -2984,6 +3200,7 @@ async function saveConfig(){
2984
3200
  // 2) Test embedding
2985
3201
  try{
2986
3202
  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||''})});
3203
+ if(er.status===401){done();toast(t('settings.session.expired'),'error');return;}
2987
3204
  var ed=await er.json();
2988
3205
  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;}
2989
3206
  document.getElementById('testEmbResult').className='test-result ok';document.getElementById('testEmbResult').innerHTML='\\u2705 '+t('settings.test.ok');
@@ -2993,6 +3210,7 @@ async function saveConfig(){
2993
3210
  if(hasSumConfig&&cfg.summarizer){
2994
3211
  try{
2995
3212
  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||''})});
3213
+ if(sr.status===401){done();toast(t('settings.session.expired'),'error');return;}
2996
3214
  var sd=await sr.json();
2997
3215
  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;}
2998
3216
  document.getElementById('testSumResult').className='test-result ok';document.getElementById('testSumResult').innerHTML='\\u2705 '+t('settings.test.ok');
@@ -3003,6 +3221,7 @@ async function saveConfig(){
3003
3221
  if(hasSkillConfig&&cfg.skillEvolution.summarizer){
3004
3222
  try{
3005
3223
  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||''})});
3224
+ if(kr.status===401){done();toast(t('settings.session.expired'),'error');return;}
3006
3225
  var kd=await kr.json();
3007
3226
  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;}
3008
3227
  document.getElementById('testSkillResult').className='test-result ok';document.getElementById('testSkillResult').innerHTML='\\u2705 '+t('settings.test.ok');
@@ -3060,18 +3279,20 @@ async function testModel(type){
3060
3279
  try{
3061
3280
  var body={type:type,provider:provider,model:model,endpoint:endpoint,apiKey:apiKey};
3062
3281
  var r=await fetch('/api/test-model',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
3282
+ if(r.status===401){resultEl.className='test-result fail';resultEl.innerHTML='\\u274C '+t('settings.session.expired');btn.disabled=false;return;}
3063
3283
  var d=await r.json();
3064
3284
  if(d.ok){
3065
3285
  resultEl.className='test-result ok';
3066
- resultEl.innerHTML='\\u2705 '+t('settings.test.ok')+'<div style="margin-top:4px;font-size:11px;color:var(--text-muted)">'+esc(d.detail||'')+'</div>';
3286
+ 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>':'');
3067
3287
  }else{
3068
- var errMsg=d.error||'Unknown error';
3288
+ var errMsg=(d.error||'Unknown error').replace(/:\s*$/,'').trim();
3069
3289
  resultEl.className='test-result fail';
3070
- 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>';
3290
+ 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>':'');
3071
3291
  }
3072
3292
  }catch(e){
3293
+ var catchMsg=(e.message||'Network error').replace(/:\s*$/,'').trim();
3073
3294
  resultEl.className='test-result fail';
3074
- 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>';
3295
+ 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>':'');
3075
3296
  }finally{btn.disabled=false;}
3076
3297
  }
3077
3298
 
@@ -3383,17 +3604,6 @@ function renderToolAgg(data){
3383
3604
  '</tbody></table>';
3384
3605
  }
3385
3606
 
3386
- function renderBreakdown(obj,containerId){
3387
- const el=document.getElementById(containerId);
3388
- if(!el)return;
3389
- const entries=Object.entries(obj||{}).sort((a,b)=>b[1]-a[1]);
3390
- const total=entries.reduce((s,[,v])=>s+v,0)||1;
3391
- el.innerHTML=entries.map(([label,value])=>{
3392
- const pct=Math.round((value/total)*100);
3393
- 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>';
3394
- }).join('');
3395
- }
3396
-
3397
3607
  /* ─── Data loading ─── */
3398
3608
  async function loadAll(){
3399
3609
  await Promise.all([loadStats(),loadMemories()]);
@@ -3439,6 +3649,20 @@ async function loadStats(){
3439
3649
  provEl.innerHTML='<div class="provider-badge offline"><span>\\u26A0</span> '+t('embed.off')+'</div>';
3440
3650
  }
3441
3651
 
3652
+ if(!_embeddingWarningShown){
3653
+ _embeddingWarningShown=true;
3654
+ if(!d.embeddingProvider||d.embeddingProvider==='local'||d.embeddingProvider==='none'){
3655
+ showEmbeddingBanner(t('embed.warn.local'),'warning');
3656
+ }
3657
+ fetch('/api/model-health').then(r=>r.json()).then(mh=>{
3658
+ var models=mh.models||[];
3659
+ var embModel=models.find(m=>m.role==='embedding');
3660
+ if(embModel&&embModel.status==='error'){
3661
+ showEmbeddingBanner(t('embed.err.fail'),'error');
3662
+ }
3663
+ }).catch(()=>{});
3664
+ }
3665
+
3442
3666
  const sl=document.getElementById('sessionList');
3443
3667
  sl.innerHTML='<div class="session-item'+(activeSession===null?' active':'')+'" onclick="filterSession(null)"><span>'+t('sidebar.allsessions')+'</span><span class="count">'+tm+'</span></div>';
3444
3668
  (d.sessions||[]).forEach(s=>{
@@ -3462,8 +3686,6 @@ function getFilterParams(){
3462
3686
  const p=new URLSearchParams();
3463
3687
  if(activeSession) p.set('session',activeSession);
3464
3688
  if(activeRole) p.set('role',activeRole);
3465
- const kind=document.getElementById('filterKind').value;
3466
- if(kind) p.set('kind',kind);
3467
3689
  const df=document.getElementById('dateFrom').value;
3468
3690
  if(df) p.set('dateFrom',df);
3469
3691
  const dt=document.getElementById('dateTo').value;
@@ -3502,17 +3724,24 @@ async function doSearch(q){
3502
3724
  if(!q.trim()){currentPage=1;loadMemories();return}
3503
3725
  const list=document.getElementById('memoryList');
3504
3726
  list.innerHTML='<div class="spinner"></div>';
3505
- const p=getFilterParams();
3506
- p.set('q',q);
3507
- const r=await fetch('/api/search?'+p.toString());
3508
- const d=await r.json();
3509
- const meta=[];
3510
- if(d.vectorCount>0) meta.push(d.vectorCount+t('search.meta.semantic'));
3511
- if(d.ftsCount>0) meta.push(d.ftsCount+t('search.meta.text'));
3512
- meta.push(d.total+t('search.meta.results'));
3513
- document.getElementById('searchMeta').textContent=meta.join(' \\u00B7 ');
3514
- renderMemories(d.results||[]);
3515
- document.getElementById('pagination').innerHTML='';
3727
+ try{
3728
+ const p=getFilterParams();
3729
+ p.set('q',q);
3730
+ const r=await fetch('/api/search?'+p.toString());
3731
+ const d=await r.json();
3732
+ const total=d.total||0;
3733
+ const meta=[];
3734
+ if(d.vectorCount>0) meta.push(d.vectorCount+t('search.meta.semantic'));
3735
+ if(d.ftsCount>0) meta.push(d.ftsCount+t('search.meta.text'));
3736
+ meta.push(total+t('search.meta.results'));
3737
+ document.getElementById('searchMeta').textContent=meta.join(' \\u00B7 ');
3738
+ renderMemories(d.results||[]);
3739
+ document.getElementById('pagination').innerHTML='';
3740
+ }catch(e){
3741
+ document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
3742
+ renderMemories([]);
3743
+ document.getElementById('pagination').innerHTML='';
3744
+ }
3516
3745
  }
3517
3746
 
3518
3747
  function debounceSearch(){
@@ -3560,18 +3789,19 @@ function renderMemories(items){
3560
3789
  list.innerHTML=items.map(m=>{
3561
3790
  const time=m.created_at?new Date(typeof m.created_at==='number'?m.created_at:m.created_at).toLocaleString('zh-CN'):'';
3562
3791
  const role=m.role||'user';
3563
- const kind=m.kind||'paragraph';
3564
- const summary=esc(m.summary||m.content?.slice(0,120)||'');
3565
- const content=esc(m.content||'');
3792
+ const rawSummary=m.summary||'';
3793
+ const rawContent=m.content||'';
3794
+ const content=esc(rawContent);
3566
3795
  const id=m.id;
3567
3796
  const vscore=m._vscore?'<span class="vscore-badge">'+Math.round(m._vscore*100)+'%</span>':'';
3568
3797
  const sid=m.session_key||'';
3569
3798
  const sidShort=sid.length>18?sid.slice(0,6)+'..'+sid.slice(-6):sid;
3570
3799
  const mc=m.merge_count||0;
3800
+ const cardTitle=esc(rawSummary||rawContent||'');
3571
3801
  const mergeBadge=mc>0?'<span class="merge-badge">\\u{1F504} '+t('card.evolved')+' '+mc+t('card.times')+'</span>':'';
3572
3802
  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>':'';
3573
3803
  const ds=m.dedup_status||'active';
3574
- const isInactive=ds==='duplicate'||ds==='merged';
3804
+ const isInactive=ds==='merged';
3575
3805
  const dedupBadge=ds==='duplicate'?'<span class="dedup-badge duplicate">'+t('card.dedupDuplicate')+'</span>':ds==='merged'?'<span class="dedup-badge merged">'+t('card.dedupMerged')+'</span>':'';
3576
3806
  const isImported=sid.startsWith('openclaw-import-')||sid.startsWith('openclaw-session-');
3577
3807
  const importBadge=isImported?'<span class="import-badge">\u{1F990} '+t('card.imported')+'</span>':'';
@@ -3579,7 +3809,7 @@ function renderMemories(items){
3579
3809
  const isPublicMem=ownerVal==='public';
3580
3810
  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>';
3581
3811
  let dedupInfo='';
3582
- if(isInactive){
3812
+ if(ds==='duplicate'||ds==='merged'){
3583
3813
  const reason=m.dedup_reason?'<span style="font-size:11px;color:var(--text-muted)">'+t('card.dedupReason')+esc(m.dedup_reason)+'</span>':'';
3584
3814
  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>':'';
3585
3815
  dedupInfo='<div style="margin-top:6px;font-size:11px">'+target+' '+reason+'</div>';
@@ -3602,8 +3832,23 @@ function renderMemories(items){
3602
3832
  }catch(e){}
3603
3833
  }
3604
3834
  return '<div class="memory-card'+(isInactive?' dedup-inactive':'')+'">'+
3605
- '<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>'+
3606
- '<div class="card-summary">'+summary+'</div>'+
3835
+ '<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>'+
3836
+ '<div class="card-summary">'+cardTitle+'</div>'+
3837
+ (function(){
3838
+ if(mc<=0) return '';
3839
+ var mergeHtml='<div class="card-merged-info">';
3840
+ mergeHtml+='<div class="card-merged-label">\\u{1F504} '+t('card.mergedInfo')+' ('+mc+t('card.times')+')</div>';
3841
+ var sources=m.merge_sources||[];
3842
+ if(sources.length>0){
3843
+ mergeHtml+='<div style="display:flex;flex-wrap:wrap;gap:6px">';
3844
+ sources.forEach(function(s){
3845
+ mergeHtml+='<span class="dedup-target-link" onclick="scrollToMemory(\\''+s.id+'\\')">\\u{1F517} '+s.id.slice(0,8)+'...</span>';
3846
+ });
3847
+ mergeHtml+='</div>';
3848
+ }
3849
+ mergeHtml+='</div>';
3850
+ return mergeHtml;
3851
+ })()+
3607
3852
  dedupInfo+
3608
3853
  '<div class="card-content" id="content-'+id+'"><pre>'+content+'</pre></div>'+
3609
3854
  historyHtml+
@@ -3682,12 +3927,11 @@ async function showMemoryModal(chunkId){
3682
3927
  const m=data.memory;
3683
3928
  const role=(m.role||'unknown').toUpperCase();
3684
3929
  const roleCls=(m.role||'').toLowerCase();
3685
- const kind=m.kind||'paragraph';
3686
3930
  const ds=m.dedup_status||'active';
3687
3931
  const time=new Date(m.created_at).toLocaleString('zh-CN');
3688
3932
  const updated=m.updated_at?new Date(m.updated_at).toLocaleString('zh-CN'):'';
3689
3933
  let html='<div class="modal-memory-card">';
3690
- html+='<div class="modal-header-row"><span class="role-tag '+roleCls+'">'+role+'</span><span class="kind-tag">'+kind+'</span>';
3934
+ html+='<div class="modal-header-row"><span class="role-tag '+roleCls+'">'+role+'</span>';
3691
3935
  if(ds!=='active') html+='<span class="dedup-badge '+(ds==='duplicate'?'duplicate':'merged')+'">'+ds+'</span>';
3692
3936
  html+='</div>';
3693
3937
  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>';
@@ -3745,17 +3989,6 @@ function renderSummaryHtml(raw){
3745
3989
  }
3746
3990
 
3747
3991
  /* ─── CRUD ─── */
3748
- function openCreateModal(){
3749
- editingId=null;
3750
- document.getElementById('modalTitle').textContent=t('modal.new');
3751
- document.getElementById('modalSubmit').textContent=t('modal.create');
3752
- document.getElementById('mRole').value='user';
3753
- document.getElementById('mContent').value='';
3754
- document.getElementById('mSummary').value='';
3755
- document.getElementById('mKind').value='paragraph';
3756
- document.getElementById('modalOverlay').classList.add('show');
3757
- }
3758
-
3759
3992
  function openEditModal(id){
3760
3993
  const m=memoryCache[id];
3761
3994
  if(!m){toast(t('toast.notfound'),'error');return}
@@ -3765,7 +3998,6 @@ function openEditModal(id){
3765
3998
  document.getElementById('mRole').value=m.role||'user';
3766
3999
  document.getElementById('mContent').value=m.content||'';
3767
4000
  document.getElementById('mSummary').value=m.summary||'';
3768
- document.getElementById('mKind').value=m.kind||'paragraph';
3769
4001
  document.getElementById('modalOverlay').classList.add('show');
3770
4002
  }
3771
4003
 
@@ -3774,21 +4006,16 @@ function closeModal(){
3774
4006
  }
3775
4007
 
3776
4008
  async function submitModal(){
4009
+ if(!editingId)return;
3777
4010
  const data={
3778
4011
  role:document.getElementById('mRole').value,
3779
4012
  content:document.getElementById('mContent').value,
3780
4013
  summary:document.getElementById('mSummary').value,
3781
- kind:document.getElementById('mKind').value,
3782
4014
  };
3783
4015
  if(!data.content.trim()){toast(t('modal.err.empty'),'error');return}
3784
- let r;
3785
- if(editingId){
3786
- r=await fetch('/api/memory/'+editingId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
3787
- } else {
3788
- r=await fetch('/api/memory',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
3789
- }
4016
+ const r=await fetch('/api/memory/'+editingId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
3790
4017
  const d=await r.json();
3791
- if(d.ok){toast(editingId?t('toast.updated'):t('toast.created'),'success');closeModal();loadAll();}
4018
+ if(d.ok){toast(t('toast.updated'),'success');closeModal();loadAll();}
3792
4019
  else{toast(d.error||t('toast.opfail'),'error')}
3793
4020
  }
3794
4021
 
@@ -3811,12 +4038,15 @@ async function toggleMemoryPublic(id,setPublic){
3811
4038
  }
3812
4039
 
3813
4040
  async function clearAll(){
3814
- if(!confirm(t('confirm.clearall')))return;
3815
- if(!confirm(t('confirm.clearall2')))return;
3816
- const r=await fetch('/api/memories',{method:'DELETE'});
3817
- const d=await r.json();
3818
- if(d.ok){toast(t('toast.cleared'),'success');loadAll();}
3819
- else{toast(t('toast.clearfail'),'error')}
4041
+ try{
4042
+ if(!confirm(t('confirm.clearall')))return;
4043
+ if(!confirm(t('confirm.clearall2')))return;
4044
+ const r=await fetch('/api/memories',{method:'DELETE'});
4045
+ if(r.status===401){toast(t('settings.session.expired'),'error');return;}
4046
+ const d=await r.json();
4047
+ if(d.ok){toast(t('toast.cleared'),'success');loadAll();}
4048
+ else{toast(t('toast.clearfail'),'error')}
4049
+ }catch(e){toast('Error: '+e.message,'error')}
3820
4050
  }
3821
4051
 
3822
4052
  /* ─── Migration ─── */
@@ -3836,7 +4066,7 @@ let migrateStats={stored:0,skipped:0,merged:0,errors:0};
3836
4066
  });
3837
4067
  })();
3838
4068
 
3839
- async function migrateScan(){
4069
+ async function migrateScan(showToast){
3840
4070
  const btn=document.getElementById('migrateScanBtn');
3841
4071
  btn.disabled=true;
3842
4072
  btn.textContent=t('migrate.scanning');
@@ -3869,9 +4099,31 @@ async function migrateScan(){
3869
4099
  t('migrate.config.warn.desc')+' ('+parts.join(', ')+')';
3870
4100
  }
3871
4101
 
4102
+ const imported=d.importedChunkCount||0;
4103
+ const remaining=Math.max(0,(d.totalItems||0)-imported);
4104
+
3872
4105
  if(d.totalItems>0 && d.configReady){
3873
4106
  document.getElementById('migrateStartBtn').style.display='inline-flex';
3874
4107
  document.getElementById('migrateConcurrencyRow').style.display='inline-flex';
4108
+ if(d.hasImportedData){
4109
+ document.getElementById('migrateStartBtn').textContent=t('migrate.resume');
4110
+ }else{
4111
+ document.getElementById('migrateStartBtn').textContent=t('migrate.start');
4112
+ }
4113
+ }
4114
+
4115
+ var hintEl=document.getElementById('migrateImportedHint');
4116
+ if(!hintEl){
4117
+ hintEl=document.createElement('div');
4118
+ hintEl.id='migrateImportedHint';
4119
+ hintEl.style.cssText='font-size:12px;color:var(--text-sec);padding:6px 0';
4120
+ document.getElementById('migrateActions').appendChild(hintEl);
4121
+ }
4122
+ if(imported>0){
4123
+ hintEl.textContent=t('migrate.imported.hint').replace('{n}',imported);
4124
+ hintEl.style.display='block';
4125
+ }else{
4126
+ hintEl.style.display='none';
3875
4127
  }
3876
4128
 
3877
4129
  if(d.totalItems===0){
@@ -3881,6 +4133,7 @@ async function migrateScan(){
3881
4133
  if(d.hasImportedData){
3882
4134
  document.getElementById('postprocessSection').style.display='block';
3883
4135
  }
4136
+ if(showToast) toast(t('migrate.scan.done').replace('{n}',remaining),'success');
3884
4137
  }catch(e){
3885
4138
  toast('Scan failed: '+e.message,'error');
3886
4139
  }finally{
@@ -3890,27 +4143,36 @@ async function migrateScan(){
3890
4143
  }
3891
4144
 
3892
4145
  function migrateStart(){
3893
- if(!migrateScanData||!migrateScanData.configReady)return;
3894
- if(!confirm(t('migrate.start')+'?'))return;
4146
+ const isResume=document.getElementById('migrateStartBtn').textContent===t('migrate.resume');
4147
+ if(!isResume){
4148
+ if(!migrateScanData||!migrateScanData.configReady){
4149
+ toast(t('migrate.scan.required'),'error');
4150
+ return;
4151
+ }
4152
+ if(!confirm(t('migrate.start')+'?'))return;
4153
+ }
3895
4154
 
3896
4155
  const concSel=document.getElementById('migrateConcurrency');
3897
4156
  const concurrency=concSel?parseInt(concSel.value,10)||1:1;
3898
4157
 
3899
4158
  window._migrateRunning=true;
3900
- _migrateStatusChecked=false;
4159
+ _migrateStatusChecked=true;
3901
4160
  document.getElementById('migrateStartBtn').style.display='none';
3902
4161
  document.getElementById('migrateScanBtn').disabled=true;
4162
+ var hintEl=document.getElementById('migrateImportedHint');
4163
+ if(hintEl) hintEl.style.display='none';
3903
4164
  document.getElementById('migrateConcurrencyRow').style.display='none';
3904
4165
  document.getElementById('migrateConcurrencyWarn').style.display='none';
3905
4166
  document.getElementById('migrateProgress').style.display='block';
3906
4167
  document.getElementById('migrateLiveLog').innerHTML='';
3907
4168
  migrateStats={stored:0,skipped:0,merged:0,errors:0};
3908
4169
  updateMigrateStats();
4170
+ document.getElementById('migrateBar').style.width='0%';
4171
+ document.getElementById('migrateCounter').textContent='';
3909
4172
 
3910
4173
  document.getElementById('migrateStopBtn').disabled=false;
3911
- document.getElementById('migrateBar').style.width='0%';
4174
+ document.getElementById('migrateStopBtn').style.display='inline-flex';
3912
4175
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#6366f1,#8b5cf6)';
3913
- document.getElementById('migrateCounter').textContent='';
3914
4176
  const body=JSON.stringify({sources:['sqlite','sessions'],concurrency});
3915
4177
  connectMigrateSSE('/api/migrate/start','POST',body);
3916
4178
  }
@@ -3947,7 +4209,7 @@ function readSSEStream(r){
3947
4209
  const NL=String.fromCharCode(10);
3948
4210
  function pump(){
3949
4211
  reader.read().then(({done,value})=>{
3950
- if(done){if(!migrateDoneCalled&&!window._migrateRunning)onMigrateDone(false);return;}
4212
+ if(done){if(!migrateDoneCalled)onMigrateDone(false);return;}
3951
4213
  buf+=decoder.decode(value,{stream:true});
3952
4214
  const lines=buf.split(NL);
3953
4215
  buf=lines.pop()||'';
@@ -3970,7 +4232,7 @@ function readSSEStream(r){
3970
4232
 
3971
4233
  var _migrateStatusChecked=false;
3972
4234
  async function checkMigrateStatus(){
3973
- if(_migrateStatusChecked) return;
4235
+ if(_migrateStatusChecked||window._migrateRunning) return;
3974
4236
  _migrateStatusChecked=true;
3975
4237
  try{
3976
4238
  const r=await fetch('/api/migrate/status');
@@ -3992,7 +4254,18 @@ async function checkMigrateStatus(){
3992
4254
  document.getElementById('migrateCounter').textContent=s.processed+' / '+s.total+' ('+pct+'%)';
3993
4255
  const label=s.phase==='sqlite'?t('migrate.phase.sqlite'):t('migrate.phase.sessions');
3994
4256
  document.getElementById('migratePhaseLabel').textContent=label;
4257
+ document.getElementById('migrateStopBtn').style.display='inline-flex';
4258
+ if(s.processed>0){
4259
+ const log=document.getElementById('migrateLiveLog');
4260
+ const hint=document.createElement('div');
4261
+ hint.style.cssText='text-align:center;padding:8px 12px;color:var(--text-muted);font-size:11px;border-bottom:1px solid var(--border)';
4262
+ hint.textContent=t('migrate.reconnect.hint').replace('{n}',s.processed);
4263
+ log.appendChild(hint);
4264
+ }
3995
4265
  connectMigrateSSE('/api/migrate/stream','GET',null);
4266
+ fetch('/api/migrate/scan').then(function(sr){return sr.json()}).then(function(sd){
4267
+ if(sd&&sd.hasImportedData) document.getElementById('postprocessSection').style.display='block';
4268
+ }).catch(function(){});
3996
4269
  }else if(s.done&&(s.stored>0||s.skipped>0||s.stopped)){
3997
4270
  migrateStats={stored:s.stored,skipped:s.skipped,merged:s.merged,errors:s.errors};
3998
4271
  updateMigrateStats();
@@ -4090,18 +4363,23 @@ function onMigrateDone(wasStopped,skipReload){
4090
4363
  document.getElementById('migrateScanBtn').disabled=false;
4091
4364
  document.getElementById('migrateStopBtn').disabled=true;
4092
4365
  document.getElementById('migrateStopBtn').textContent=t('migrate.stop');
4366
+ document.getElementById('migrateStopBtn').style.display='none';
4093
4367
  if(wasStopped){
4094
4368
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#f59e0b,#fbbf24)';
4095
4369
  document.getElementById('migrateStartBtn').style.display='inline-flex';
4096
4370
  document.getElementById('migrateStartBtn').textContent=t('migrate.resume');
4371
+ document.getElementById('migratePhaseLabel').textContent=t('migrate.phase.stopped');
4097
4372
  }else{
4098
4373
  document.getElementById('migrateBar').style.width='100%';
4099
4374
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#22c55e,#16a34a)';
4100
4375
  const total=migrateStats.stored+migrateStats.skipped+migrateStats.merged+migrateStats.errors;
4101
4376
  if(total>0) document.getElementById('migrateCounter').textContent=total+' / '+total+' (100%)';
4377
+ document.getElementById('migratePhaseLabel').textContent=t('migrate.phase.done');
4102
4378
  }
4103
4379
  fetch('/api/migrate/scan').then(r=>{if(!r.ok)throw new Error();return r.json()}).then(d=>{
4104
- if(d&&d.hasImportedData) document.getElementById('postprocessSection').style.display='block';
4380
+ if(d&&d.hasImportedData){
4381
+ document.getElementById('postprocessSection').style.display='block';
4382
+ }
4105
4383
  }).catch(()=>{});
4106
4384
  if(!skipReload) loadAll();
4107
4385
  }
@@ -4229,12 +4507,18 @@ function connectPPSSE(){
4229
4507
  }).catch(function(){});
4230
4508
  }else if(s.done){
4231
4509
  document.getElementById('postprocessSection').style.display='block';
4232
- ppStats={tasks:s.tasksCreated||0,skills:s.skillsCreated||0,errors:s.errors||0,skipped:0};
4510
+ ppStats={tasks:s.tasksCreated||0,skills:s.skillsCreated||0,errors:s.errors||0,skipped:s.skippedSessions||0};
4233
4511
  updatePPStats();
4234
4512
  document.getElementById('ppProgress').style.display='block';
4235
- var pct2=s.total>0?Math.round((s.processed/s.total)*100):0;
4236
- document.getElementById('ppBar').style.width=pct2+'%';
4237
- document.getElementById('ppCounter').textContent=s.processed+' / '+s.total+' ('+pct2+'%)';
4513
+ var totalAll=(s.total||0)+(s.skippedSessions||0);
4514
+ if(totalAll>0){
4515
+ document.getElementById('ppBar').style.width='100%';
4516
+ document.getElementById('ppCounter').textContent=totalAll+' / '+totalAll+' (100%)';
4517
+ }else{
4518
+ var pct2=s.total>0?Math.round((s.processed/s.total)*100):0;
4519
+ document.getElementById('ppBar').style.width=pct2+'%';
4520
+ document.getElementById('ppCounter').textContent=s.processed+' / '+s.total+' ('+pct2+'%)';
4521
+ }
4238
4522
  ppDone(!!s.stopped,false,true);
4239
4523
  }
4240
4524
  }).catch(function(){});
@@ -4242,9 +4526,11 @@ function connectPPSSE(){
4242
4526
 
4243
4527
  function handlePPEvent(evtType,data){
4244
4528
  if(evtType==='progress'){
4245
- var pct=data.total>0?Math.round((data.processed/data.total)*100):0;
4246
- document.getElementById('ppBar').style.width=pct+'%';
4247
- document.getElementById('ppCounter').textContent=data.processed+' / '+data.total+' ('+pct+'%)';
4529
+ if(data.total>0){
4530
+ var pct=Math.round((data.processed/data.total)*100);
4531
+ document.getElementById('ppBar').style.width=pct+'%';
4532
+ document.getElementById('ppCounter').textContent=data.processed+' / '+data.total+' ('+pct+'%)';
4533
+ }
4248
4534
  }else if(evtType==='info'){
4249
4535
  if(data.alreadyProcessed>0){
4250
4536
  ppStats.skipped=data.alreadyProcessed;
@@ -4253,6 +4539,10 @@ function handlePPEvent(evtType,data){
4253
4539
  }
4254
4540
  if(data.pending===0){
4255
4541
  appendPPLogItem({step:'done',session:t('pp.info.allDone'),index:'',total:''});
4542
+ document.getElementById('ppPhaseLabel').textContent=t('pp.info.allDone');
4543
+ document.getElementById('ppBar').style.width='100%';
4544
+ document.getElementById('ppBar').style.background='linear-gradient(90deg,#22c55e,#16a34a)';
4545
+ document.getElementById('ppCounter').textContent=data.alreadyProcessed+' / '+data.totalSessions;
4256
4546
  }else{
4257
4547
  document.getElementById('ppPhaseLabel').textContent=t('pp.info.pending').replace('{n}',data.pending);
4258
4548
  }
@@ -4264,12 +4554,10 @@ function handlePPEvent(evtType,data){
4264
4554
  document.getElementById('ppPhaseLabel').textContent=t('pp.running')+' — '+actionLabel+' — '+label;
4265
4555
  }
4266
4556
  if(data.step==='done'){
4267
- if(data.action==='skill-only'){
4268
- ppStats.skills++;
4269
- }else{
4557
+ if(data.action!=='skill-only'){
4270
4558
  ppStats.tasks++;
4559
+ updatePPStats();
4271
4560
  }
4272
- updatePPStats();
4273
4561
  }else if(data.step==='error'){
4274
4562
  ppStats.errors++;
4275
4563
  updatePPStats();
@@ -4297,6 +4585,7 @@ function ppDone(wasStopped,wasFailed,skipReload){
4297
4585
  document.getElementById('ppStopBtn').style.display='none';
4298
4586
  document.getElementById('ppStartBtn').style.display='inline-flex';
4299
4587
  document.getElementById('ppStartBtn').textContent=wasStopped?t('pp.resume'):t('pp.start');
4588
+ document.getElementById('ppStartBtn').disabled=false;
4300
4589
  var doneEl=document.getElementById('ppDone');
4301
4590
  doneEl.style.display='block';
4302
4591
  if(wasFailed){
@@ -4304,21 +4593,57 @@ function ppDone(wasStopped,wasFailed,skipReload){
4304
4593
  doneEl.style.color='#ef4444';
4305
4594
  doneEl.textContent=t('pp.failed')||'Processing failed — check error above';
4306
4595
  document.getElementById('ppBar').style.background='linear-gradient(90deg,#ef4444,#dc2626)';
4596
+ document.getElementById('ppPhaseLabel').textContent=t('pp.failed');
4307
4597
  }else if(wasStopped){
4308
4598
  doneEl.style.background='rgba(245,158,11,.06)';
4309
4599
  doneEl.style.color='#f59e0b';
4310
4600
  doneEl.textContent=t('pp.stopped');
4311
4601
  document.getElementById('ppBar').style.background='linear-gradient(90deg,#f59e0b,#fbbf24)';
4602
+ document.getElementById('ppPhaseLabel').textContent=t('pp.stopped');
4312
4603
  }else{
4313
4604
  doneEl.style.background='rgba(34,197,94,.06)';
4314
4605
  doneEl.style.color='#22c55e';
4315
- doneEl.textContent=t('pp.done')+' ('+t('pp.stat.tasks')+': '+ppStats.tasks+', '+t('pp.stat.skills')+': '+ppStats.skills+')';
4316
4606
  document.getElementById('ppBar').style.width='100%';
4317
4607
  document.getElementById('ppBar').style.background='linear-gradient(90deg,#22c55e,#16a34a)';
4608
+ document.getElementById('ppPhaseLabel').textContent=t('pp.done');
4609
+ var ppTotal=ppStats.tasks+ppStats.skipped+ppStats.errors;
4610
+ if(ppTotal>0) document.getElementById('ppCounter').textContent=ppTotal+' / '+ppTotal+' (100%)';
4611
+ fetch('/api/migrate/postprocess/status').then(function(r){return r.json()}).then(function(st){
4612
+ var totalTasks=st.existingTasks||0;
4613
+ var totalSkills=st.existingSkills||0;
4614
+ var lines=[];
4615
+ if(ppStats.tasks>0) lines.push(t('pp.stat.tasks')+' +'+ppStats.tasks);
4616
+ if(ppStats.skills>0) lines.push(t('pp.stat.skills')+' +'+ppStats.skills);
4617
+ if(ppStats.skipped>0) lines.push(t('pp.stat.skipped')+': '+ppStats.skipped);
4618
+ var runText=lines.length>0?' ('+lines.join(', ')+')':'';
4619
+ var totalText=' — '+t('pp.stat.tasks')+' '+totalTasks+', '+t('pp.stat.skills.total')+' '+totalSkills;
4620
+ doneEl.textContent=t('pp.done')+runText+totalText;
4621
+ }).catch(function(){
4622
+ var parts=[];
4623
+ if(ppStats.tasks>0) parts.push(t('pp.stat.tasks')+': '+ppStats.tasks);
4624
+ if(ppStats.skills>0) parts.push(t('pp.stat.skills')+': '+ppStats.skills);
4625
+ if(ppStats.skipped>0) parts.push(t('pp.stat.skipped')+': '+ppStats.skipped);
4626
+ doneEl.textContent=t('pp.done')+(parts.length>0?' ('+parts.join(', ')+')':'');
4627
+ });
4318
4628
  }
4319
4629
  if(!skipReload) loadAll();
4320
4630
  }
4321
4631
 
4632
+ /* ─── Embedding Banner ─── */
4633
+ function showEmbeddingBanner(msg,type){
4634
+ if(document.getElementById('embBanner')) return;
4635
+ var cls=type==='error'?'emb-banner error':'emb-banner warning';
4636
+ var icon=type==='error'?'\\u274C':'\\u26A0\\uFE0F';
4637
+ var btn='<button class="emb-banner-btn" onclick="switchView(\\'settings\\');this.parentElement.remove()">'+t('embed.banner.goto')+'</button>';
4638
+ var close='<button class="emb-banner-close" onclick="this.parentElement.remove()">&times;</button>';
4639
+ var el=document.createElement('div');
4640
+ el.id='embBanner';
4641
+ el.className=cls;
4642
+ el.innerHTML=icon+' <span>'+esc(msg)+'</span>'+btn+close;
4643
+ var mc=document.querySelector('.main-content');
4644
+ if(mc) mc.parentElement.insertBefore(el,mc);
4645
+ }
4646
+
4322
4647
  /* ─── Toast ─── */
4323
4648
  function toast(msg,type='info'){
4324
4649
  const c=document.getElementById('toasts');
@@ -4337,16 +4662,64 @@ function toggleViewerTheme(){const el=document.documentElement;const cur=el.getA
4337
4662
  initViewerTheme();
4338
4663
 
4339
4664
  /* ─── Update check ─── */
4665
+ function waitForGatewayAndReload(maxAttempts,attempt){
4666
+ attempt=attempt||0;
4667
+ if(attempt>=maxAttempts){window.location.reload();return;}
4668
+ setTimeout(function(){
4669
+ fetch('/api/update-check').then(function(r){
4670
+ if(r.ok) window.location.reload();
4671
+ else waitForGatewayAndReload(maxAttempts,attempt+1);
4672
+ }).catch(function(){waitForGatewayAndReload(maxAttempts,attempt+1);});
4673
+ },3000);
4674
+ }
4675
+ function doUpdateInstall(packageSpec,btnEl){
4676
+ btnEl.disabled=true;
4677
+ btnEl.textContent=t('update.installing');
4678
+ fetch('/api/update-install',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({packageSpec:packageSpec})})
4679
+ .then(function(r){return r.json()})
4680
+ .then(function(d){
4681
+ if(d.ok){
4682
+ btnEl.textContent=t('update.success');
4683
+ btnEl.style.background='#22c55e';
4684
+ btnEl.style.color='#fff';
4685
+ waitForGatewayAndReload(20);
4686
+ }else{
4687
+ btnEl.textContent=t('update.failed')+': '+(d.error||'').slice(0,80);
4688
+ btnEl.style.background='#ef4444';
4689
+ btnEl.style.color='#fff';
4690
+ btnEl.disabled=false;
4691
+ setTimeout(function(){btnEl.textContent=t('update.btn');btnEl.style.background='';btnEl.style.color='';},5000);
4692
+ }
4693
+ })
4694
+ .catch(function(e){
4695
+ btnEl.textContent=t('update.failed');
4696
+ btnEl.disabled=false;
4697
+ setTimeout(function(){btnEl.textContent=t('update.btn');btnEl.style.background='';btnEl.style.color='';},5000);
4698
+ });
4699
+ }
4340
4700
  async function checkForUpdate(){
4341
4701
  try{
4342
4702
  const r=await fetch('/api/update-check');
4343
4703
  if(!r.ok)return;
4344
4704
  const d=await r.json();
4345
4705
  if(!d.updateAvailable)return;
4706
+ const pkgSpec=d.installCommand?d.installCommand.replace(/^openclaw plugins install\s+/,''):(d.packageName+'@'+d.latest);
4346
4707
  const banner=document.createElement('div');
4347
4708
  banner.id='updateBanner';
4348
4709
  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)';
4349
- 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>';
4710
+ var leftSpan=document.createElement('span');
4711
+ leftSpan.innerHTML='🔔 '+t('update.available')+': <b>v'+esc(d.current)+'</b> → <b>v'+esc(d.latest)+'</b>';
4712
+ var btnUpdate=document.createElement('button');
4713
+ btnUpdate.textContent=t('update.btn');
4714
+ 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';
4715
+ btnUpdate.onclick=function(){doUpdateInstall(pkgSpec,btnUpdate)};
4716
+ var btnClose=document.createElement('button');
4717
+ btnClose.innerHTML='&times;';
4718
+ btnClose.style.cssText='background:none;border:none;color:#fff;font-size:18px;cursor:pointer;padding:0 4px';
4719
+ btnClose.onclick=function(){banner.remove();document.body.style.paddingTop='';};
4720
+ leftSpan.appendChild(btnUpdate);
4721
+ banner.appendChild(leftSpan);
4722
+ banner.appendChild(btnClose);
4350
4723
  document.body.prepend(banner);
4351
4724
  document.body.style.paddingTop='48px';
4352
4725
  }catch(e){}
@@ -4372,4 +4745,5 @@ checkAuth();
4372
4745
 
4373
4746
  </body>
4374
4747
  </html>`;
4748
+ }
4375
4749
  //# sourceMappingURL=html.js.map