@memtensor/memos-local-openclaw-plugin 1.0.4-beta.4 → 1.0.4-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 (152) hide show
  1. package/README.md +22 -39
  2. package/dist/capture/index.d.ts.map +1 -1
  3. package/dist/capture/index.js +6 -0
  4. package/dist/capture/index.js.map +1 -1
  5. package/dist/config.d.ts +1 -2
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +3 -72
  8. package/dist/config.js.map +1 -1
  9. package/dist/embedding/index.d.ts +2 -4
  10. package/dist/embedding/index.d.ts.map +1 -1
  11. package/dist/embedding/index.js +1 -17
  12. package/dist/embedding/index.js.map +1 -1
  13. package/dist/index.d.ts +0 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +3 -4
  16. package/dist/index.js.map +1 -1
  17. package/dist/ingest/providers/index.d.ts +2 -10
  18. package/dist/ingest/providers/index.d.ts.map +1 -1
  19. package/dist/ingest/providers/index.js +43 -209
  20. package/dist/ingest/providers/index.js.map +1 -1
  21. package/dist/ingest/providers/openai.d.ts +0 -1
  22. package/dist/ingest/providers/openai.d.ts.map +1 -1
  23. package/dist/ingest/providers/openai.js +0 -1
  24. package/dist/ingest/providers/openai.js.map +1 -1
  25. package/dist/ingest/task-processor.js +1 -1
  26. package/dist/ingest/task-processor.js.map +1 -1
  27. package/dist/recall/engine.js +1 -1
  28. package/dist/recall/engine.js.map +1 -1
  29. package/dist/shared/llm-call.d.ts +2 -4
  30. package/dist/shared/llm-call.d.ts.map +1 -1
  31. package/dist/shared/llm-call.js +81 -20
  32. package/dist/shared/llm-call.js.map +1 -1
  33. package/dist/skill/evaluator.d.ts.map +1 -1
  34. package/dist/skill/evaluator.js +2 -2
  35. package/dist/skill/evaluator.js.map +1 -1
  36. package/dist/skill/evolver.d.ts +2 -0
  37. package/dist/skill/evolver.d.ts.map +1 -1
  38. package/dist/skill/evolver.js +3 -0
  39. package/dist/skill/evolver.js.map +1 -1
  40. package/dist/skill/generator.d.ts.map +1 -1
  41. package/dist/skill/generator.js +4 -4
  42. package/dist/skill/generator.js.map +1 -1
  43. package/dist/skill/upgrader.js +1 -1
  44. package/dist/skill/upgrader.js.map +1 -1
  45. package/dist/skill/validator.js +1 -1
  46. package/dist/skill/validator.js.map +1 -1
  47. package/dist/storage/ensure-binding.d.ts.map +1 -1
  48. package/dist/storage/ensure-binding.js +1 -3
  49. package/dist/storage/ensure-binding.js.map +1 -1
  50. package/dist/storage/sqlite.d.ts +0 -294
  51. package/dist/storage/sqlite.d.ts.map +1 -1
  52. package/dist/storage/sqlite.js +0 -821
  53. package/dist/storage/sqlite.js.map +1 -1
  54. package/dist/telemetry.d.ts +12 -5
  55. package/dist/telemetry.d.ts.map +1 -1
  56. package/dist/telemetry.js +135 -38
  57. package/dist/telemetry.js.map +1 -1
  58. package/dist/tools/index.d.ts +0 -1
  59. package/dist/tools/index.d.ts.map +1 -1
  60. package/dist/tools/index.js +1 -3
  61. package/dist/tools/index.js.map +1 -1
  62. package/dist/tools/memory-search.d.ts +2 -3
  63. package/dist/tools/memory-search.d.ts.map +1 -1
  64. package/dist/tools/memory-search.js +7 -48
  65. package/dist/tools/memory-search.js.map +1 -1
  66. package/dist/types.d.ts +2 -49
  67. package/dist/types.d.ts.map +1 -1
  68. package/dist/types.js.map +1 -1
  69. package/dist/viewer/html.d.ts.map +1 -1
  70. package/dist/viewer/html.js +471 -2974
  71. package/dist/viewer/html.js.map +1 -1
  72. package/dist/viewer/server.d.ts +0 -45
  73. package/dist/viewer/server.d.ts.map +1 -1
  74. package/dist/viewer/server.js +18 -1155
  75. package/dist/viewer/server.js.map +1 -1
  76. package/index.ts +42 -430
  77. package/openclaw.plugin.json +1 -2
  78. package/package.json +3 -4
  79. package/scripts/postinstall.cjs +46 -283
  80. package/skill/memos-memory-guide/SKILL.md +2 -26
  81. package/src/capture/index.ts +8 -0
  82. package/src/config.ts +3 -94
  83. package/src/embedding/index.ts +1 -21
  84. package/src/index.ts +4 -7
  85. package/src/ingest/providers/index.ts +46 -246
  86. package/src/ingest/providers/openai.ts +1 -1
  87. package/src/ingest/task-processor.ts +1 -1
  88. package/src/recall/engine.ts +1 -1
  89. package/src/shared/llm-call.ts +95 -23
  90. package/src/skill/evaluator.ts +2 -3
  91. package/src/skill/evolver.ts +5 -0
  92. package/src/skill/generator.ts +4 -6
  93. package/src/skill/upgrader.ts +1 -1
  94. package/src/skill/validator.ts +1 -1
  95. package/src/storage/ensure-binding.ts +1 -3
  96. package/src/storage/sqlite.ts +0 -1085
  97. package/src/telemetry.ts +152 -39
  98. package/src/tools/index.ts +0 -1
  99. package/src/tools/memory-search.ts +8 -57
  100. package/src/types.ts +2 -44
  101. package/src/viewer/html.ts +471 -2974
  102. package/src/viewer/server.ts +21 -1070
  103. package/dist/client/connector.d.ts +0 -30
  104. package/dist/client/connector.d.ts.map +0 -1
  105. package/dist/client/connector.js +0 -219
  106. package/dist/client/connector.js.map +0 -1
  107. package/dist/client/hub.d.ts +0 -61
  108. package/dist/client/hub.d.ts.map +0 -1
  109. package/dist/client/hub.js +0 -148
  110. package/dist/client/hub.js.map +0 -1
  111. package/dist/client/skill-sync.d.ts +0 -29
  112. package/dist/client/skill-sync.d.ts.map +0 -1
  113. package/dist/client/skill-sync.js +0 -216
  114. package/dist/client/skill-sync.js.map +0 -1
  115. package/dist/hub/auth.d.ts +0 -19
  116. package/dist/hub/auth.d.ts.map +0 -1
  117. package/dist/hub/auth.js +0 -70
  118. package/dist/hub/auth.js.map +0 -1
  119. package/dist/hub/server.d.ts +0 -41
  120. package/dist/hub/server.d.ts.map +0 -1
  121. package/dist/hub/server.js +0 -747
  122. package/dist/hub/server.js.map +0 -1
  123. package/dist/hub/user-manager.d.ts +0 -29
  124. package/dist/hub/user-manager.d.ts.map +0 -1
  125. package/dist/hub/user-manager.js +0 -125
  126. package/dist/hub/user-manager.js.map +0 -1
  127. package/dist/openclaw-api.d.ts +0 -53
  128. package/dist/openclaw-api.d.ts.map +0 -1
  129. package/dist/openclaw-api.js +0 -189
  130. package/dist/openclaw-api.js.map +0 -1
  131. package/dist/sharing/types.contract.d.ts +0 -2
  132. package/dist/sharing/types.contract.d.ts.map +0 -1
  133. package/dist/sharing/types.contract.js +0 -3
  134. package/dist/sharing/types.contract.js.map +0 -1
  135. package/dist/sharing/types.d.ts +0 -80
  136. package/dist/sharing/types.d.ts.map +0 -1
  137. package/dist/sharing/types.js +0 -3
  138. package/dist/sharing/types.js.map +0 -1
  139. package/dist/tools/network-memory-detail.d.ts +0 -4
  140. package/dist/tools/network-memory-detail.d.ts.map +0 -1
  141. package/dist/tools/network-memory-detail.js +0 -34
  142. package/dist/tools/network-memory-detail.js.map +0 -1
  143. package/src/client/connector.ts +0 -218
  144. package/src/client/hub.ts +0 -189
  145. package/src/client/skill-sync.ts +0 -202
  146. package/src/hub/auth.ts +0 -78
  147. package/src/hub/server.ts +0 -740
  148. package/src/hub/user-manager.ts +0 -139
  149. package/src/openclaw-api.ts +0 -287
  150. package/src/sharing/types.contract.ts +0 -40
  151. package/src/sharing/types.ts +0 -102
  152. package/src/tools/network-memory-detail.ts +0 -34
@@ -5,8 +5,7 @@ return `<!DOCTYPE html>
5
5
  <head>
6
6
  <meta charset="UTF-8">
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
- <link rel="icon" href="https://statics.memtensor.com.cn/logo/color-m.svg" type="image/svg+xml">
9
- <title>MemOS 记忆</title>
8
+ <title>OpenClaw Memory - Powered by MemOS</title>
10
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
11
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
11
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
@@ -59,9 +58,8 @@ return `<!DOCTYPE html>
59
58
  [data-theme="light"] .analytics-card.amber .ac-value{color:#d97706}
60
59
  [data-theme="light"] .analytics-section{background:#fff;border-color:var(--border);box-shadow:0 1px 3px rgba(0,0,0,.04)}
61
60
  [data-theme="light"] .analytics-section::before{background:none}
62
- [data-theme="light"] .chart-bar{box-shadow:0 1px 4px rgba(99,102,241,.12)}
63
- [data-theme="light"] .chart-bar:hover{box-shadow:0 3px 12px rgba(79,70,229,.2)}
64
- [data-theme="light"] .chart-bars::before{opacity:.3}
61
+ [data-theme="light"] .chart-bar{box-shadow:none}
62
+ [data-theme="light"] .chart-bar:hover{box-shadow:0 2px 8px rgba(79,70,229,.15)}
65
63
  [data-theme="light"] .tool-chart-tooltip{background:rgba(17,24,39,.92);color:#e8eaed;border-color:rgba(99,102,241,.3);box-shadow:0 8px 24px rgba(0,0,0,.2)}
66
64
  [data-theme="light"] .tool-chart-tooltip .tt-time{color:#a5b4fc}
67
65
  [data-theme="light"] .tool-chart-tooltip .tt-val{color:#e8eaed}
@@ -106,16 +104,10 @@ input,textarea,select{font-family:inherit;font-size:inherit}
106
104
 
107
105
  /* ─── App Layout (dark dashboard, same as www) ─── */
108
106
  .app{display:none;flex-direction:column;min-height:100vh}
109
- .topbar{background:rgba(11,13,17,.88);border-bottom:1px solid var(--border);padding:0 max(32px,calc((100% - 1400px)/2 + 32px));height:56px;display:flex;align-items:center;position:sticky;top:0;z-index:100;backdrop-filter:blur(12px)}
110
- .topbar .brand{display:flex;align-items:center;gap:8px;font-weight:700;font-size:15px;color:var(--text);letter-spacing:-.02em;flex-shrink:0}
111
- .topbar .brand .icon{width:24px;height:24px;display:flex;align-items:center;justify-content:center;font-size:18px;background:none;border-radius:0}
112
- .topbar .brand .brand-title{font-size:13px;font-weight:500;opacity:.7}
113
- .topbar .brand .brand-col{display:flex;flex-direction:column;gap:0}
114
- .topbar .brand .brand-powered{font-size:9px;font-weight:500;color:var(--text-sec);opacity:.55;letter-spacing:.02em;line-height:1}
107
+ .topbar{background:rgba(11,13,17,.88);border-bottom:1px solid var(--border);padding:0 28px;height:56px;display:flex;align-items:center;position:sticky;top:0;z-index:100;backdrop-filter:blur(12px)}
108
+ .topbar .brand{display:flex;align-items:center;gap:10px;font-weight:700;font-size:15px;color:var(--text);letter-spacing:-.02em;flex-shrink:0}
109
+ .topbar .brand .icon{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:22px;background:none;border-radius:0}
115
110
  .topbar .brand .sub{font-weight:400;color:var(--text-muted);font-size:11px}
116
- .memos-logo{display:inline-flex;align-items:center}
117
- .memos-logo svg{height:28px;width:28px}
118
- .brand-sep{width:1px;height:20px;background:var(--border);opacity:.5;margin:0 2px}
119
111
  .version-badge{font-size:10px;font-weight:600;color:var(--text-muted);background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.1);padding:1px 7px;border-radius:6px;margin-left:6px;letter-spacing:.02em;user-select:all}
120
112
  [data-theme="light"] .version-badge{background:rgba(0,0,0,.05);border-color:rgba(0,0,0,.08);color:var(--text-sec)}
121
113
  .topbar-center{flex:1;display:flex;justify-content:center}
@@ -153,101 +145,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
153
145
  .search-bar input:focus{border-color:var(--pri);box-shadow:0 0 0 3px var(--pri-glow)}
154
146
  .search-bar .search-icon{position:absolute;left:14px;top:50%;transform:translateY(-50%);color:var(--text-muted);font-size:14px;pointer-events:none}
155
147
  .search-meta{font-size:12px;color:var(--text-sec);margin-bottom:14px;padding:0 2px}
156
- .scope-select{padding:10px 12px;border:1px solid var(--border);border-radius:10px;background:var(--bg-card);color:var(--text);font-size:13px;min-width:110px;outline:none}
157
- .sharing-inline-meta{font-size:12px;color:var(--text-muted);margin:-8px 0 14px 2px}
158
- .sharing-sidebar-card{margin:14px 0 18px;border:1px solid var(--border);background:var(--bg-card);border-radius:12px;padding:12px;box-shadow:var(--shadow-sm)}
159
- .sharing-sidebar-card .title{font-size:12px;font-weight:700;color:var(--text);margin-bottom:8px;text-transform:uppercase;letter-spacing:.04em}
160
- .sharing-sidebar-card .status{font-size:13px;color:var(--text-sec);line-height:1.5}
161
- .sharing-sidebar-card .status strong{color:var(--text)}
162
- .sharing-sidebar-card .hint{margin-top:8px;font-size:11px;color:var(--text-muted)}
163
- .sharing-sidebar-card .user-row{display:flex;align-items:center;gap:8px;margin-bottom:10px}
164
- .sharing-sidebar-card .user-row .username{font-size:13px;font-weight:600;color:var(--text)}
165
- .sharing-sidebar-card .role-badge{display:inline-block;font-size:10px;font-weight:600;padding:2px 8px;border-radius:9999px;line-height:1.4;letter-spacing:.02em}
166
- .sharing-sidebar-card .role-badge.admin{background:rgba(52,199,89,.15);color:#34c759}
167
- .sharing-sidebar-card .role-badge.client{background:rgba(175,82,222,.15);color:#af52de}
168
- .sharing-sidebar-card .info-grid{display:grid;grid-template-columns:auto 1fr;gap:4px 10px;font-size:12px;margin-bottom:8px}
169
- .sharing-sidebar-card .info-grid .label{color:var(--text-muted);font-weight:500;white-space:nowrap}
170
- .sharing-sidebar-card .info-grid .value{color:var(--text-sec);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
171
- .sharing-sidebar-card .api-badge{display:inline-block;font-size:10px;font-weight:600;padding:2px 8px;border-radius:9999px;background:rgba(142,142,147,.12);color:var(--text-muted);letter-spacing:.02em}
172
- [data-theme="light"] .sharing-sidebar-card .role-badge.admin{background:rgba(5,150,105,.1);color:#059669}
173
- [data-theme="light"] .sharing-sidebar-card .role-badge.client{background:rgba(124,58,237,.1);color:#7c3aed}
174
- .result-section{margin-bottom:18px;border:1px solid var(--border);border-radius:14px;background:var(--bg-card);overflow:hidden}
175
- .result-section-header{display:flex;justify-content:space-between;align-items:center;padding:12px 14px;border-bottom:1px solid var(--border);background:rgba(255,255,255,.02)}
176
- .result-section-title{font-size:14px;font-weight:700;color:var(--text)}
177
- .result-section-sub{font-size:12px;color:var(--text-muted)}
178
- .search-hit-list{padding:12px;display:flex;flex-direction:column;gap:10px}
179
- .search-hit-card,.hub-hit-card,.hub-skill-card{border:1px solid var(--border);border-radius:12px;background:var(--bg);padding:12px;box-shadow:var(--shadow-sm)}
180
- .search-hit-card .summary,.hub-hit-card .summary,.hub-skill-card .summary{font-size:14px;font-weight:600;color:var(--text);margin-bottom:6px}
181
- .search-hit-card .excerpt,.hub-hit-card .excerpt,.hub-skill-card .excerpt{font-size:12px;color:var(--text-sec);line-height:1.55;white-space:pre-wrap}
182
- .search-hit-meta,.hub-hit-meta,.hub-skill-meta{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px;font-size:11px;color:var(--text-muted)}
183
- .meta-chip{display:inline-flex;align-items:center;gap:5px;padding:4px 8px;border:1px solid var(--border);border-radius:999px;background:var(--bg-card)}
184
- .hub-hit-actions,.hub-skill-actions,.task-share-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
185
- .sharing-settings-grid{display:grid;grid-template-columns:1.1fr .9fr;gap:18px}
186
- .sharing-panel{border:1px solid var(--border);border-radius:14px;background:var(--bg-card);padding:14px;box-shadow:var(--shadow-sm)}
187
- .sharing-panel h4{font-size:14px;font-weight:700;color:var(--text);margin:0 0 10px 0}
188
- .sharing-panel .line{font-size:13px;color:var(--text-sec);margin-bottom:8px;line-height:1.55}
189
- .sharing-panel .line strong{color:var(--text)}
190
- .pending-user-list{display:flex;flex-direction:column;gap:10px}
191
- .pending-user-card{border:1px solid var(--border);border-radius:12px;padding:12px;background:var(--bg)}
192
- .pending-user-name{font-size:14px;font-weight:700;color:var(--text)}
193
- .pending-user-meta{font-size:12px;color:var(--text-sec);margin-top:4px}
194
- .pending-user-actions{display:flex;gap:8px;margin-top:10px}
195
- /* ─── Admin Panel ─── */
196
- .admin-header{position:relative;padding:28px 28px 20px;background:linear-gradient(135deg,rgba(99,102,241,.08) 0%,rgba(139,92,246,.06) 50%,rgba(6,182,212,.05) 100%);border:1px solid rgba(99,102,241,.12);border-radius:16px;margin-bottom:20px;overflow:hidden}
197
- .admin-header::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--violet),var(--pri),var(--cyan));border-radius:16px 16px 0 0}
198
- .admin-header-top{display:flex;justify-content:space-between;align-items:center}
199
- .admin-header h2{font-size:20px;font-weight:800;color:var(--text);display:flex;align-items:center;gap:10px;margin:0}
200
- .admin-header h2 .ah-icon{width:36px;height:36px;border-radius:10px;background:linear-gradient(135deg,var(--pri),var(--violet));display:flex;align-items:center;justify-content:center;font-size:18px;box-shadow:0 4px 12px rgba(99,102,241,.25)}
201
- .admin-header-sub{font-size:12px;color:var(--text-muted);margin-top:6px}
202
- .admin-stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:10px;margin-top:18px}
203
- .admin-stat-box{position:relative;text-align:center;padding:16px 8px 14px;background:var(--bg-card);border:1px solid var(--border);border-radius:12px;overflow:hidden;transition:all .2s}
204
- .admin-stat-box:hover{border-color:rgba(99,102,241,.25);transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,0,0,.06)}
205
- .admin-stat-box::before{content:'';position:absolute;top:0;left:50%;transform:translateX(-50%);width:40px;height:2px;border-radius:0 0 4px 4px}
206
- .admin-stat-box:nth-child(1)::before{background:var(--green)}
207
- .admin-stat-box:nth-child(2)::before{background:var(--amber)}
208
- .admin-stat-box:nth-child(3)::before{background:var(--violet)}
209
- .admin-stat-box:nth-child(4)::before{background:var(--cyan)}
210
- .admin-stat-box:nth-child(5)::before{background:var(--pri)}
211
- .admin-stat-box:nth-child(6)::before{background:var(--rose)}
212
- .admin-stat-box .as-icon{font-size:16px;margin-bottom:4px;display:block}
213
- .admin-stat-box .val{font-size:22px;font-weight:800;color:var(--text);font-variant-numeric:tabular-nums}
214
- .admin-stat-box .lbl{font-size:10px;color:var(--text-muted);margin-top:3px;letter-spacing:.03em}
215
- .admin-tabs{display:flex;gap:4px;padding:4px;background:var(--bg);border:1px solid var(--border);border-radius:14px;overflow-x:auto;-webkit-overflow-scrolling:touch;margin-bottom:20px}
216
- .admin-tab{position:relative;display:flex;align-items:center;gap:6px;padding:9px 16px;border:none;background:transparent;color:var(--text-muted);font-size:13px;font-weight:500;cursor:pointer;border-radius:10px;transition:all .2s;white-space:nowrap;font-family:inherit}
217
- .admin-tab:hover{background:rgba(99,102,241,.06);color:var(--text)}
218
- .admin-tab.active{background:var(--bg-card);color:var(--text);font-weight:600;box-shadow:0 2px 8px rgba(0,0,0,.06),0 0 0 1px rgba(99,102,241,.1)}
219
- .admin-tab .at-icon{font-size:14px;line-height:1}
220
- .admin-tab .at-count{font-size:10px;font-weight:700;padding:1px 6px;border-radius:8px;background:rgba(99,102,241,.1);color:var(--pri);min-width:18px;text-align:center}
221
- .admin-tab.active .at-count{background:rgba(99,102,241,.15)}
222
- .admin-panel{display:none}
223
- .admin-panel.active{display:block}
224
- .admin-card{border:1px solid var(--border);border-radius:12px;padding:14px 16px;background:var(--bg-card);margin-bottom:10px;transition:all .2s;position:relative;overflow:hidden}
225
- .admin-card::before{content:'';position:absolute;top:0;left:0;bottom:0;width:3px;border-radius:3px 0 0 3px;background:var(--pri);opacity:.5}
226
- .admin-card:hover{border-color:rgba(99,102,241,.25);box-shadow:0 2px 12px rgba(0,0,0,.04);transform:translateY(-1px)}
227
- .admin-card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}
228
- .admin-card-title{font-size:14px;font-weight:700;color:var(--text)}
229
- .admin-card-meta{font-size:12px;color:var(--text-muted);line-height:1.5}
230
- .admin-card-actions{display:flex;gap:8px;margin-top:10px}
231
- .admin-badge{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:700;padding:3px 10px;border-radius:999px;letter-spacing:.02em}
232
- .admin-badge.admin{background:rgba(52,199,89,.15);color:#34c759}
233
- .admin-badge.member{background:rgba(142,142,147,.12);color:var(--text-muted)}
234
- .admin-badge.pending{background:rgba(255,159,10,.15);color:#ff9f0a}
235
- .admin-badge.public{background:rgba(99,102,241,.1);color:var(--pri)}
236
- .admin-badge.group{background:rgba(139,92,246,.1);color:var(--violet)}
237
- .admin-empty{font-size:13px;color:var(--text-muted);padding:40px 20px;text-align:center;border:1px dashed var(--border);border-radius:12px;background:var(--bg)}
238
- .admin-empty .ae-icon{font-size:28px;display:block;margin-bottom:8px;opacity:.5}
239
- [data-theme="light"] .admin-badge.admin{background:rgba(5,150,105,.1);color:#059669}
240
- [data-theme="light"] .admin-badge.member{background:rgba(0,0,0,.06);color:#6b7280}
241
- [data-theme="light"] .admin-badge.pending{background:rgba(245,158,11,.1);color:#d97706}
242
- [data-theme="light"] .admin-header{background:linear-gradient(135deg,rgba(99,102,241,.05) 0%,rgba(139,92,246,.04) 50%,rgba(6,182,212,.03) 100%)}
243
- .task-detail-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
244
- .shared-memory-overlay,.shared-memory-overlay.show{display:none}
245
- .shared-memory-overlay.show{display:flex;position:fixed;inset:0;align-items:center;justify-content:center;background:rgba(0,0,0,.55);z-index:1200;padding:24px}
246
- .shared-memory-panel{width:min(860px,95vw);max-height:85vh;overflow:auto;border:1px solid var(--border);border-radius:18px;background:var(--bg-card);box-shadow:var(--shadow-lg);padding:20px}
247
- .shared-memory-panel h3{font-size:18px;color:var(--text);margin-bottom:10px}
248
- .shared-memory-panel .content{font-size:13px;color:var(--text-sec);line-height:1.7;white-space:pre-wrap;background:var(--bg);border:1px solid var(--border);border-radius:12px;padding:14px;margin-top:12px}
249
- .hub-source-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border-radius:999px;background:rgba(34,197,94,.12);color:var(--green);font-size:11px;font-weight:700;border:1px solid rgba(34,197,94,.22)}
250
- @media (max-width: 960px){.sharing-settings-grid{grid-template-columns:1fr}.search-bar{flex-wrap:wrap}.scope-select{width:100%}.task-detail-actions{width:100%;justify-content:flex-start}}
251
148
 
252
149
  .filter-bar{display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap}
253
150
  .filter-chip{padding:5px 14px;border:1px solid var(--border);border-radius:6px;background:transparent;color:var(--text-sec);font-size:12px;font-weight:500;transition:all .15s}
@@ -294,67 +191,20 @@ input,textarea,select{font-family:inherit;font-size:inherit}
294
191
  .memory-card.dedup-inactive{opacity:.55;border-style:dashed}
295
192
  .memory-card.dedup-inactive:hover{opacity:.85}
296
193
  .dedup-target-link{font-size:11px;color:var(--pri);cursor:pointer;text-decoration:underline;margin-left:4px}
297
- .memory-modal-overlay{position:fixed;inset:0;background:rgba(5,8,16,.75);z-index:9999;display:none;align-items:center;justify-content:center;backdrop-filter:blur(12px) saturate(1.2)}
194
+ .memory-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:9999;display:none;align-items:center;justify-content:center;backdrop-filter:blur(4px)}
298
195
  .memory-modal-overlay.show{display:flex}
299
- [data-theme="light"] .memory-modal-overlay{background:rgba(0,0,0,.45)}
300
- .memory-modal{position:relative;background:linear-gradient(160deg,#0d1117 0%,#161b22 50%,#0d1117 100%);border:1px solid rgba(99,102,241,.2);border-radius:20px;width:min(600px,92vw);max-height:82vh;display:flex;flex-direction:column;box-shadow:0 32px 100px rgba(0,0,0,.6),0 0 60px rgba(99,102,241,.08),inset 0 1px 0 rgba(255,255,255,.06);animation:mmSlideIn .35s cubic-bezier(.22,1,.36,1);overflow:hidden}
301
- [data-theme="light"] .memory-modal{background:linear-gradient(160deg,#ffffff 0%,#f8f9fc 50%,#ffffff 100%);border-color:rgba(99,102,241,.15);box-shadow:0 32px 100px rgba(0,0,0,.12),0 0 40px rgba(99,102,241,.06)}
302
- @keyframes mmSlideIn{from{opacity:0;transform:scale(.92) translateY(20px)}to{opacity:1;transform:scale(1) translateY(0)}}
303
- .memory-modal::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,#6366f1,#8b5cf6,#ec4899,#f43f5e,#6366f1);background-size:200% 100%;animation:mmGradientMove 3s linear infinite;border-radius:20px 20px 0 0;z-index:1}
304
- @keyframes mmGradientMove{0%{background-position:0% 50%}100%{background-position:200% 50%}}
305
- .memory-modal::after{content:'';position:absolute;inset:0;background:radial-gradient(ellipse at 20% 0%,rgba(99,102,241,.06) 0%,transparent 60%),radial-gradient(ellipse at 80% 100%,rgba(236,72,153,.04) 0%,transparent 60%);pointer-events:none;border-radius:20px}
306
- [data-theme="light"] .memory-modal::after{background:radial-gradient(ellipse at 20% 0%,rgba(99,102,241,.04) 0%,transparent 60%),radial-gradient(ellipse at 80% 100%,rgba(236,72,153,.03) 0%,transparent 60%)}
307
- .memory-modal-title{position:relative;z-index:2;display:flex;align-items:center;justify-content:space-between;padding:18px 24px 14px;font-size:12px;font-weight:700;color:var(--text-sec);letter-spacing:.06em;text-transform:uppercase}
308
- .memory-modal-title .mm-tl{display:flex;align-items:center;gap:8px}
309
- .memory-modal-title .mm-tl-icon{width:28px;height:28px;border-radius:8px;background:linear-gradient(135deg,rgba(99,102,241,.15),rgba(139,92,246,.1));display:flex;align-items:center;justify-content:center;font-size:14px;border:1px solid rgba(99,102,241,.2)}
310
- [data-theme="light"] .memory-modal-title .mm-tl-icon{background:linear-gradient(135deg,rgba(99,102,241,.1),rgba(139,92,246,.06));border-color:rgba(99,102,241,.12)}
311
- .memory-modal-title .mm-close{width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:10px;border:1px solid rgba(255,255,255,.06);background:rgba(255,255,255,.04);color:var(--text-sec);cursor:pointer;font-size:16px;transition:all .2s}
312
- .memory-modal-title .mm-close:hover{background:rgba(255,77,77,.12);border-color:rgba(255,77,77,.2);color:#f87171;transform:rotate(90deg)}
313
- [data-theme="light"] .memory-modal-title .mm-close{border-color:rgba(0,0,0,.06);background:rgba(0,0,0,.03)}
314
- [data-theme="light"] .memory-modal-title .mm-close:hover{background:rgba(255,77,77,.08);border-color:rgba(255,77,77,.15)}
315
- .memory-modal-body{position:relative;z-index:2;padding:0;overflow-y:auto;flex:1}
316
- .mm-hero{padding:0 24px 20px}
317
- .mm-hero-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:14px}
318
- .mm-role-chip{display:inline-flex;align-items:center;gap:5px;font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;padding:4px 12px;border-radius:20px;background:linear-gradient(135deg,rgba(99,102,241,.15),rgba(139,92,246,.1));color:#a5b4fc;border:1px solid rgba(99,102,241,.2)}
319
- .mm-role-chip.user{background:linear-gradient(135deg,rgba(16,185,129,.15),rgba(52,211,153,.1));color:#6ee7b7;border-color:rgba(16,185,129,.2)}
320
- .mm-role-chip.assistant{background:linear-gradient(135deg,rgba(99,102,241,.15),rgba(139,92,246,.1));color:#a5b4fc;border-color:rgba(99,102,241,.2)}
321
- .mm-role-chip.system{background:linear-gradient(135deg,rgba(251,191,36,.15),rgba(245,158,11,.1));color:#fcd34d;border-color:rgba(251,191,36,.2)}
322
- [data-theme="light"] .mm-role-chip{color:#6366f1}
323
- [data-theme="light"] .mm-role-chip.user{color:#059669}
324
- [data-theme="light"] .mm-role-chip.assistant{color:#6366f1}
325
- [data-theme="light"] .mm-role-chip.system{color:#d97706}
326
- .mm-dedup-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:3px 10px;border-radius:20px;text-transform:uppercase;letter-spacing:.04em}
327
- .mm-dedup-chip.duplicate{background:rgba(239,68,68,.12);color:#fca5a5;border:1px solid rgba(239,68,68,.2)}
328
- .mm-dedup-chip.merged{background:rgba(251,191,36,.12);color:#fcd34d;border:1px solid rgba(251,191,36,.2)}
329
- [data-theme="light"] .mm-dedup-chip.duplicate{color:#dc2626}
330
- [data-theme="light"] .mm-dedup-chip.merged{color:#d97706}
331
- .mm-id{font-family:'SF Mono',Consolas,'Courier New',monospace;font-size:10px;color:var(--text-muted);background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.06);padding:3px 10px;border-radius:20px;cursor:pointer;transition:all .2s;display:inline-flex;align-items:center;gap:4px}
332
- .mm-id::before{content:'\u{1F517}';font-size:9px}
333
- .mm-id:hover{background:rgba(99,102,241,.12);border-color:rgba(99,102,241,.25);color:var(--text)}
334
- [data-theme="light"] .mm-id{background:rgba(0,0,0,.03);border-color:rgba(0,0,0,.06)}
335
- [data-theme="light"] .mm-id:hover{background:rgba(99,102,241,.08);border-color:rgba(99,102,241,.15)}
336
- .mm-summary{font-size:15px;font-weight:600;color:var(--text);line-height:1.6;letter-spacing:-.01em;padding:16px 20px;background:rgba(255,255,255,.02);border-radius:12px;border:1px solid rgba(255,255,255,.04);position:relative}
337
- .mm-summary::before{content:'';position:absolute;left:0;top:12px;bottom:12px;width:3px;border-radius:2px;background:linear-gradient(180deg,#6366f1,#8b5cf6)}
338
- [data-theme="light"] .mm-summary{background:rgba(99,102,241,.02);border-color:rgba(99,102,241,.06)}
339
- .mm-section{padding:0 24px 16px}
340
- .mm-section-label{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted);margin-bottom:8px;display:flex;align-items:center;gap:6px}
341
- .mm-section-label::before{content:'';width:6px;height:6px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#8b5cf6);flex-shrink:0}
342
- .mm-content{font-family:'SF Mono',Consolas,'Courier New',monospace;font-size:12px;line-height:1.75;color:var(--text);white-space:pre-wrap;word-break:break-all;background:rgba(0,0,0,.3);border-radius:12px;padding:16px 18px;max-height:240px;overflow-y:auto;margin:0;border:1px solid rgba(255,255,255,.04);position:relative}
343
- [data-theme="light"] .mm-content{background:rgba(0,0,0,.03);border-color:rgba(0,0,0,.06)}
344
- .mm-meta{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:8px;padding:16px 24px;border-top:1px solid rgba(255,255,255,.04);background:rgba(0,0,0,.1)}
345
- [data-theme="light"] .mm-meta{border-top-color:rgba(0,0,0,.04);background:rgba(0,0,0,.015)}
346
- .mm-meta-chip{display:flex;flex-direction:column;gap:3px;font-size:11px;color:var(--text-sec);background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.04);padding:10px 12px;border-radius:10px;transition:all .2s}
347
- .mm-meta-chip:hover{background:rgba(255,255,255,.06);border-color:rgba(99,102,241,.15)}
348
- [data-theme="light"] .mm-meta-chip{background:rgba(0,0,0,.02);border-color:rgba(0,0,0,.04)}
349
- [data-theme="light"] .mm-meta-chip:hover{background:rgba(99,102,241,.04);border-color:rgba(99,102,241,.1)}
350
- .mm-meta-chip strong{color:var(--text-muted);font-weight:600;font-size:9px;text-transform:uppercase;letter-spacing:.06em}
351
- .mm-meta-chip span{color:var(--text);font-weight:500;font-size:12px;word-break:break-all}
352
- .mm-dedup{padding:0 24px 16px}
353
- .mm-dedup-box{background:linear-gradient(135deg,rgba(251,191,36,.06),rgba(245,158,11,.03));border:1px solid rgba(251,191,36,.12);border-radius:12px;padding:14px 16px;font-size:12px;color:var(--text-sec);line-height:1.6;display:flex;align-items:flex-start;gap:8px}
354
- .mm-dedup-box::before{content:'\u26A0\uFE0F';flex-shrink:0;font-size:14px}
355
- .mm-footer{padding:12px 24px 16px;display:flex;align-items:center;justify-content:center}
356
- .mm-footer .dedup-target-link{font-size:11px;color:#818cf8;cursor:pointer;padding:6px 16px;border-radius:8px;border:1px solid rgba(99,102,241,.2);background:rgba(99,102,241,.06);transition:all .2s;font-weight:500}
357
- .mm-footer .dedup-target-link:hover{background:rgba(99,102,241,.12);border-color:rgba(99,102,241,.3)}
196
+ .memory-modal{background:var(--bg-card);border:1px solid var(--border);border-radius:16px;width:min(600px,90vw);max-height:80vh;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,.4);animation:modalIn .2s ease-out}
197
+ @keyframes modalIn{from{opacity:0;transform:scale(.95) translateY(10px)}to{opacity:1;transform:scale(1) translateY(0)}}
198
+ .memory-modal-title{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--border);font-size:14px;font-weight:700}
199
+ .memory-modal-body{padding:20px;overflow-y:auto;flex:1}
200
+ .modal-memory-card{display:flex;flex-direction:column;gap:14px}
201
+ .modal-header-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
202
+ .modal-field{display:flex;flex-direction:column;gap:4px}
203
+ .modal-field-label{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1px;color:var(--text-sec)}
204
+ .modal-field-val{font-size:13px;color:var(--text);line-height:1.5}
205
+ .modal-field-content{font-family:'SF Mono',Consolas,monospace;font-size:12px;line-height:1.6;color:var(--text);white-space:pre-wrap;word-break:break-all;background:rgba(0,0,0,.15);border-radius:8px;padding:12px;max-height:240px;overflow-y:auto;margin:0}
206
+ [data-theme="light"] .modal-field-content{background:rgba(0,0,0,.04)}
207
+ .modal-meta-row{display:flex;flex-wrap:wrap;gap:12px;font-size:11px;color:var(--text-sec);padding:8px 0;border-top:1px dashed var(--border)}
358
208
  [data-theme="light"] .merge-history{background:rgba(0,0,0,.04)}
359
209
  [data-theme="light"] .merge-history-item{border-bottom-color:rgba(0,0,0,.06)}
360
210
  .card-merged-info{margin-top:8px;padding:8px 12px;background:rgba(16,185,129,.06);border:1px dashed rgba(16,185,129,.2);border-radius:8px;font-size:12px;line-height:1.6;color:var(--text-sec)}
@@ -392,7 +242,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
392
242
  .form-group textarea{min-height:100px;resize:vertical}
393
243
  .modal-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:28px}
394
244
 
395
-
396
245
  /* ─── Toast ─── */
397
246
  .emb-banner{display:flex;align-items:center;gap:10px;padding:12px 20px;font-size:13px;font-weight:500;border-radius:10px;margin:0 32px 0;animation:slideIn .3s ease}
398
247
  .emb-banner.warning{background:rgba(245,158,11,.1);color:#d97706;border:1px solid rgba(245,158,11,.25)}
@@ -586,9 +435,9 @@ input,textarea,select{font-family:inherit;font-size:inherit}
586
435
  .nav-tabs .tab.active{color:var(--text);background:rgba(255,255,255,.1);border-color:var(--border);box-shadow:0 1px 4px rgba(0,0,0,.15)}
587
436
  [data-theme="light"] .nav-tabs{background:rgba(0,0,0,.05)}
588
437
  [data-theme="light"] .nav-tabs .tab.active{background:#fff;border-color:rgba(0,0,0,.1);box-shadow:0 1px 3px rgba(0,0,0,.08);color:var(--text)}
589
- .analytics-view,.settings-view,.logs-view,.migrate-view,.admin-view{display:none;flex:1;min-width:0;flex-direction:column;gap:20px}
590
- .analytics-view.show,.settings-view.show,.logs-view.show,.migrate-view.show,.admin-view.show{display:flex}
591
- .feed-wrap,.tasks-view,.skills-view,.analytics-view,.settings-view,.logs-view,.migrate-view,.admin-view{max-width:960px}
438
+ .analytics-view,.settings-view,.logs-view,.migrate-view{display:none;flex:1;min-width:0;flex-direction:column;gap:20px}
439
+ .analytics-view.show,.settings-view.show,.logs-view.show,.migrate-view.show{display:flex}
440
+ .feed-wrap,.tasks-view,.skills-view,.analytics-view,.settings-view,.logs-view,.migrate-view{max-width:960px}
592
441
 
593
442
  /* ─── Logs ─── */
594
443
  .logs-toolbar{display:flex;align-items:center;justify-content:space-between;padding:8px 0}
@@ -706,66 +555,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
706
555
  .settings-section{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:24px 28px}
707
556
  .settings-section h3{font-size:13px;font-weight:700;color:var(--text);margin-bottom:16px;display:flex;align-items:center;gap:8px}
708
557
  .settings-section h3 .icon{font-size:16px;opacity:.8}
709
- .settings-tabs-bar{display:flex;gap:4px;margin-bottom:24px;padding:4px;background:var(--bg);border:1px solid var(--border);border-radius:14px;overflow-x:auto;-webkit-overflow-scrolling:touch}
710
- .settings-tab-btn{position:relative;display:flex;align-items:center;gap:7px;padding:10px 16px;border:none;background:transparent;color:var(--text-muted);font-size:13px;font-weight:500;cursor:pointer;border-radius:10px;transition:all .25s ease;white-space:nowrap;font-family:inherit}
711
- .settings-tab-btn:hover{background:rgba(99,102,241,.06);color:var(--text)}
712
- .settings-tab-btn.active{background:var(--bg-card);color:var(--text);font-weight:600;box-shadow:0 2px 8px rgba(0,0,0,.06),0 0 0 1px rgba(99,102,241,.1)}
713
- .settings-tab-btn .stab-icon{font-size:15px;line-height:1}
714
- .settings-tab-btn .stab-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
715
- .settings-tab-btn[data-tab="models"] .stab-dot{background:#6366f1}
716
- .settings-tab-btn[data-tab="hub"] .stab-dot{background:#06b6d4}
717
- .settings-tab-btn[data-tab="general"] .stab-dot{background:#10b981}
718
- .settings-tab-btn.active .stab-dot{box-shadow:0 0 6px currentColor}
719
- [data-theme="light"] .settings-tab-btn.active{box-shadow:0 2px 8px rgba(0,0,0,.05),0 0 0 1px rgba(99,102,241,.08)}
720
- .settings-cards-grid{display:flex;flex-direction:column;gap:24px}
721
- .settings-card[data-stab]{display:none}
722
- .settings-card[data-stab].stab-active{display:block}
723
- .settings-card{position:relative;background:var(--bg-card);border:1px solid var(--border);border-radius:16px;overflow:hidden;transition:border-color .3s,box-shadow .3s}
724
- .settings-card::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;opacity:.5;transition:opacity .3s}
725
- .settings-card:hover{border-color:rgba(99,102,241,.25);box-shadow:0 0 24px rgba(99,102,241,.06),0 8px 32px rgba(0,0,0,.1)}
726
- .settings-card:hover::before{opacity:1}
727
- .settings-card.card-models::before{background:linear-gradient(90deg,#6366f1,#8b5cf6,#f59e0b)}
728
- .settings-card.card-hub::before{background:linear-gradient(90deg,#06b6d4,#22d3ee,#67e8f9)}
729
- .settings-card.card-general::before{background:linear-gradient(90deg,#10b981,#34d399,#6ee7b7)}
730
- .settings-card-header{display:flex;align-items:center;gap:14px;padding:22px 28px 0}
731
- .settings-card-icon{width:44px;height:44px;display:flex;align-items:center;justify-content:center;border-radius:12px;font-size:22px;flex-shrink:0;border:1px solid transparent}
732
- .settings-card-title-wrap{flex:1;min-width:0}
733
- .settings-card-title{font-size:16px;font-weight:700;color:var(--text);letter-spacing:.01em}
734
- .settings-card-desc{font-size:11px;color:var(--text-muted);margin-top:3px;font-weight:400;line-height:1.4}
735
- .settings-card-body{padding:18px 28px 24px}
736
- .settings-card-divider{height:1px;background:var(--border);margin:18px 0;opacity:.6}
737
- .settings-card-subtitle{font-size:12px;font-weight:700;color:var(--text-sec);margin-bottom:10px;letter-spacing:.01em}
738
- .hub-info-card{border:1px solid var(--border);border-radius:12px;padding:14px 16px;background:var(--bg);position:relative;overflow:hidden;margin-bottom:12px}
739
- .hub-info-card::before{content:'';position:absolute;top:0;left:0;bottom:0;width:3px;border-radius:3px 0 0 3px}
740
- .hub-info-card.hic-share{border-color:rgba(99,102,241,.2);background:linear-gradient(135deg,rgba(99,102,241,.04),rgba(139,92,246,.03))}
741
- .hub-info-card.hic-share::before{background:linear-gradient(180deg,var(--pri),var(--violet))}
742
- .hub-info-card.hic-status::before{background:var(--green)}
743
- .hub-info-card.hic-team::before{background:var(--violet)}
744
- .hub-info-card.hic-pending::before{background:var(--amber)}
745
- .hub-info-card .hic-title{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:700;color:var(--text);margin-bottom:10px}
746
- .hub-info-card .hic-title .hic-icon{font-size:14px}
747
- .hub-info-card .hic-grid{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;font-size:12px;align-items:baseline}
748
- .hub-info-card .hic-grid .hic-label{color:var(--text-muted);white-space:nowrap}
749
- .hub-info-card .hic-grid .hic-value{color:var(--text);font-weight:500;word-break:break-all}
750
- .hub-info-card .hic-grid .hic-value.mono{font-family:monospace;font-size:11px;cursor:pointer;user-select:all}
751
- .hub-info-card .hic-grid .hic-value.mono:hover{color:var(--pri)}
752
- .hub-info-card .hic-badge{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:9999px}
753
- .hub-info-card .hic-badge.connected{background:rgba(52,211,153,.12);color:#34d399}
754
- .hub-info-card .hic-badge.disconnected{background:rgba(239,68,68,.1);color:#ef4444}
755
- .hub-info-card .hic-badge.admin{background:rgba(52,199,89,.12);color:#34c759}
756
- .hub-info-card .hic-badge.pending{background:rgba(251,191,36,.12);color:#fbbf24}
757
- .hub-info-card .hic-dot{width:6px;height:6px;border-radius:50%;display:inline-block}
758
- .hub-info-card .hic-dot.green{background:#34d399;box-shadow:0 0 6px #34d399}
759
- .hub-info-card .hic-dot.red{background:#ef4444}
760
- .hub-info-card .hic-dot.amber{background:#fbbf24;box-shadow:0 0 4px #fbbf24}
761
- .hub-info-card .hic-empty{font-size:12px;color:var(--text-muted);text-align:center;padding:12px 0}
762
- .hub-info-card .hic-actions{display:flex;gap:8px;margin-top:10px}
763
- [data-theme="light"] .hub-info-card.hic-share{background:linear-gradient(135deg,rgba(99,102,241,.03),rgba(139,92,246,.02))}
764
- .settings-card .settings-section{background:none;border:none;padding:0;border-radius:0;margin-bottom:16px}
765
- .settings-card .settings-section:last-child{margin-bottom:0}
766
- .settings-card .settings-section h3{margin-bottom:12px}
767
- [data-theme="light"] .settings-card{box-shadow:0 1px 3px rgba(0,0,0,.04)}
768
- [data-theme="light"] .settings-card:hover{box-shadow:0 0 24px rgba(79,70,229,.05),0 8px 32px rgba(0,0,0,.06)}
769
558
  .settings-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}
770
559
  @media(max-width:800px){.settings-grid{grid-template-columns:1fr}}
771
560
  .settings-field{display:flex;flex-direction:column;gap:4px}
@@ -773,8 +562,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
773
562
  .settings-field input,.settings-field select{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:8px 12px;color:var(--text);font-size:13px;font-family:inherit;transition:border-color .15s}
774
563
  .settings-field input:focus,.settings-field select:focus{outline:none;border-color:var(--pri)}
775
564
  .settings-field input[type="password"]{font-family:'Courier New',monospace;letter-spacing:.05em}
776
- .field-hint{font-size:10px;color:var(--text-muted);line-height:1.5}
777
- .settings-field .field-hint{margin-top:2px}
565
+ .settings-field .field-hint{font-size:10px;color:var(--text-muted);margin-top:2px}
778
566
  .settings-field.full-width{grid-column:1/-1}
779
567
  .settings-toggle{display:flex;align-items:center;gap:10px;padding:4px 0}
780
568
  .settings-toggle label{font-size:12px;font-weight:500;color:var(--text-sec);text-transform:none;letter-spacing:0}
@@ -790,36 +578,14 @@ input,textarea,select{font-family:inherit;font-size:inherit}
790
578
  .test-result.ok{color:#22c55e}
791
579
  .test-result.fail{color:var(--rose)}
792
580
  .test-result.loading{color:var(--text-muted)}
793
- .settings-actions{display:flex;gap:12px;justify-content:flex-end;align-items:center;margin-top:20px;padding-top:16px;border-top:1px solid var(--border);flex-wrap:nowrap}
794
- .settings-actions .btn{flex:0 0 auto;min-width:0;padding:8px 24px;font-size:13px}
581
+ .settings-actions{display:flex;gap:12px;justify-content:flex-end;align-items:center;margin-top:16px;padding-top:16px;border-top:1px solid var(--border)}
582
+ .settings-actions .btn{min-width:110px;padding:10px 20px;font-size:13px}
795
583
  .settings-actions .btn-primary{background:rgba(99,102,241,.08);color:var(--pri);border:1px solid rgba(99,102,241,.25);font-weight:600}
796
584
  .settings-actions .btn-primary:hover{background:rgba(99,102,241,.14);border-color:var(--pri)}
797
585
  [data-theme="light"] .settings-actions .btn-primary{background:rgba(79,70,229,.06);color:#4f46e5;border:1px solid rgba(79,70,229,.2)}
798
586
  [data-theme="light"] .settings-actions .btn-primary:hover{background:rgba(79,70,229,.1);border-color:#4f46e5}
799
587
  .settings-saved{display:inline-flex;align-items:center;gap:6px;color:var(--green);font-size:12px;font-weight:600;opacity:0;transition:opacity .3s}
800
588
  .settings-saved.show{opacity:1}
801
- .team-guide{margin-bottom:20px;border:1px solid rgba(6,182,212,.2);border-radius:12px;background:linear-gradient(135deg,rgba(6,182,212,.04),rgba(99,102,241,.03));position:relative;overflow:hidden;padding:20px 22px 18px;display:none}
802
- .team-guide::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,#06b6d4,#6366f1,#8b5cf6);opacity:.6}
803
- .team-guide-title{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:700;color:var(--text);margin-bottom:4px}
804
- .team-guide-subtitle{font-size:11.5px;color:var(--text-muted);line-height:1.6;margin-bottom:16px}
805
- .team-guide-options{display:grid;grid-template-columns:1fr 1fr;gap:14px}
806
- @media(max-width:800px){.team-guide-options{grid-template-columns:1fr}}
807
- .team-guide-opt{border:1px solid var(--border);border-radius:10px;padding:16px;background:var(--bg-card);transition:border-color .2s,box-shadow .2s;cursor:default}
808
- .team-guide-opt:hover{border-color:rgba(99,102,241,.3);box-shadow:0 4px 16px rgba(0,0,0,.06)}
809
- .team-guide-opt-header{display:flex;align-items:center;gap:10px;margin-bottom:8px}
810
- .team-guide-opt-icon{width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:8px;font-size:16px;flex-shrink:0}
811
- .team-guide-opt-title{font-size:13px;font-weight:700;color:var(--text)}
812
- .team-guide-opt-desc{font-size:11px;color:var(--text-sec);line-height:1.7;margin-bottom:12px}
813
- .team-guide-steps{padding:0 0 0 20px;margin:0 0 12px;counter-reset:none}
814
- .team-guide-steps li{font-size:11px;color:var(--text-muted);line-height:1.9}
815
- .team-guide-steps li::marker{color:var(--pri);font-weight:700;font-size:11px}
816
- .team-guide-opt .btn-guide{font-size:11px;padding:5px 14px;border-radius:6px;font-weight:600;border:1px solid rgba(99,102,241,.25);background:rgba(99,102,241,.08);color:var(--pri);cursor:pointer;transition:background .15s,border-color .15s}
817
- .team-guide-opt .btn-guide:hover{background:rgba(99,102,241,.14);border-color:var(--pri)}
818
- .team-guide-dismiss{position:absolute;top:10px;right:12px;background:none;border:none;color:var(--text-muted);font-size:15px;cursor:pointer;padding:4px;line-height:1;opacity:.5;transition:opacity .15s}
819
- .team-guide-dismiss:hover{opacity:1}
820
- [data-theme="light"] .team-guide{background:linear-gradient(135deg,rgba(6,182,212,.03),rgba(79,70,229,.02));border-color:rgba(6,182,212,.15)}
821
- [data-theme="light"] .team-guide-opt{box-shadow:0 1px 3px rgba(0,0,0,.03)}
822
- [data-theme="light"] .team-guide-opt:hover{box-shadow:0 4px 16px rgba(0,0,0,.04)}
823
589
  .model-health-bar{margin-bottom:20px;border-radius:var(--radius-lg);overflow:visible}
824
590
  .mh-table{width:100%;border-collapse:separate;border-spacing:0;font-size:12px}
825
591
  .mh-table th{text-align:left;padding:6px 12px;font-size:10px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em;background:var(--bg);border-bottom:1px solid var(--border)}
@@ -879,20 +645,18 @@ input,textarea,select{font-family:inherit;font-size:inherit}
879
645
  .analytics-section::before{display:none}
880
646
  .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}
881
647
  .analytics-section h3 .icon{font-size:14px;opacity:.6}
882
- .chart-bars{display:flex;align-items:flex-end;gap:0;padding:12px 0 4px;min-height:200px;position:relative;width:100%}
883
- .chart-bars::before{content:'';position:absolute;left:0;right:0;bottom:24px;height:1px;background:var(--border);opacity:.4}
884
- .chart-bar-wrap{flex:1 1 0;min-width:0;display:flex;flex-direction:column;align-items:center;gap:4px;position:relative;cursor:pointer}
885
- .chart-bar-col{width:100%;height:160px;display:flex;flex-direction:column;justify-content:flex-end;align-items:center}
886
- .chart-bar-wrap:hover .chart-bar{opacity:1;filter:brightness(1.2);transform:scaleY(1.02);transform-origin:bottom}
648
+ .chart-bars{display:flex;align-items:flex-end;gap:4px;padding:8px 0;overflow-x:auto;justify-content:center}
649
+ .chart-bar-wrap{flex:1;min-width:28px;max-width:80px;display:flex;flex-direction:column;align-items:center;gap:4px;position:relative}
650
+ .chart-bar-col{width:100%;height:160px;display:flex;flex-direction:column;justify-content:flex-end;align-items:stretch}
651
+ .chart-bar-wrap:hover .chart-bar{opacity:1}
887
652
  .chart-bar-wrap:hover .chart-bar-label{color:var(--text)}
888
653
  .chart-bar-wrap:hover .chart-tip{opacity:1;transform:translateX(-50%) translateY(0)}
889
- .chart-tip{position:absolute;top:-8px;left:50%;transform:translateX(-50%) translateY(6px);background:rgba(15,18,25,.95);border:1px solid rgba(99,102,241,.3);color:#e8eaed;padding:3px 10px;border-radius:8px;font-size:10px;font-weight:600;white-space:nowrap;z-index:5;pointer-events:none;box-shadow:0 4px 16px rgba(0,0,0,.3);opacity:0;transition:all .2s cubic-bezier(.22,1,.36,1)}
890
- [data-theme="light"] .chart-tip{background:rgba(17,24,39,.9)}
891
- .chart-bar{width:100%;max-width:20px;min-width:4px;border-radius:3px 3px 1px 1px;background:linear-gradient(180deg,#818cf8 0%,#6366f1 100%);opacity:.85;transition:all .25s cubic-bezier(.22,1,.36,1);box-shadow:0 1px 4px rgba(99,102,241,.12)}
892
- .chart-bar.violet{background:linear-gradient(180deg,#8b5cf6 0%,#6366f1 100%)}
893
- .chart-bar.green{background:linear-gradient(180deg,#34d399 0%,#10b981 100%);box-shadow:0 1px 4px rgba(16,185,129,.12)}
894
- .chart-bar.zero{width:100%;max-width:20px;min-width:4px;height:2px!important;background:var(--border);opacity:.25;border-radius:1px;box-shadow:none}
895
- .chart-bar-label{font-size:8px;line-height:1;min-height:10px;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:40px;text-align:center;transition:color .15s;letter-spacing:0}
654
+ .chart-tip{position:absolute;top:-6px;left:50%;transform:translateX(-50%) translateY(4px);background:var(--bg-card);border:1px solid var(--border-glow);color:var(--text);padding:2px 8px;border-radius:6px;font-size:10px;font-weight:600;white-space:nowrap;z-index:5;pointer-events:none;box-shadow:var(--shadow);opacity:0;transition:all .15s ease}
655
+ .chart-bar{width:100%;border-radius:3px 3px 1px 1px;background:#818cf8;opacity:.75;transition:all .2s ease}
656
+ .chart-bar.violet{background:#6366f1}
657
+ .chart-bar.green{background:var(--green)}
658
+ .chart-bar.zero{background:var(--border);opacity:.3;border-radius:2px}
659
+ .chart-bar-label{font-size:9px;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;text-align:center;transition:color .15s}
896
660
  .chart-legend{display:flex;gap:14px;margin-top:12px;flex-wrap:wrap;font-size:11px;color:var(--text-sec);font-weight:500}
897
661
  .chart-legend span{display:inline-flex;align-items:center;gap:5px}
898
662
  .chart-legend .dot{width:8px;height:8px;border-radius:2px}
@@ -946,7 +710,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
946
710
  [data-theme="light"] .auth-theme-toggle .theme-icon-dark{display:none}
947
711
 
948
712
  @media(max-width:1100px){.analytics-cards{grid-template-columns:repeat(3,1fr)}}
949
- @media(max-width:900px){.main-content{flex-direction:column;padding:20px}.sidebar{width:100%}.sidebar .stats-grid{grid-template-columns:repeat(4,1fr)}.analytics-cards{grid-template-columns:repeat(2,1fr)}.topbar{padding:0 16px;gap:8px}.topbar .brand .brand-title{display:none}.topbar .brand .brand-powered{display:none}.topbar-center{justify-content:flex-start}}
713
+ @media(max-width:900px){.main-content{flex-direction:column;padding:20px}.sidebar{width:100%}.sidebar .stats-grid{grid-template-columns:repeat(4,1fr)}.analytics-cards{grid-template-columns:repeat(2,1fr)}.topbar{padding:0 16px;gap:8px}.topbar .brand span{display:none}.topbar-center{justify-content:flex-start}}
950
714
  </style>
951
715
  </head>
952
716
  <body>
@@ -958,9 +722,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
958
722
  <button class="auth-theme-toggle" onclick="toggleLang()" aria-label="Switch language"><span data-i18n="lang.switch">EN</span></button>
959
723
  </div>
960
724
  <div class="auth-card">
961
- <div class="logo" style="display:flex;flex-direction:column;align-items:center;gap:10px">
962
- <svg width="56" height="56" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="aLG" 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(#aLG)"/><path d="M20 45C5 40 0 50 5 60C10 70 20 65 25 55C28 48 25 45 20 45Z" fill="url(#aLG)"/><path d="M100 45C115 40 120 50 115 60C110 70 100 65 95 55C92 48 95 45 100 45Z" fill="url(#aLG)"/><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>
963
- </div>
725
+ <div class="logo"><svg width="60" height="60" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="aLG" 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(#aLG)"/><path d="M20 45C5 40 0 50 5 60C10 70 20 65 25 55C28 48 25 45 20 45Z" fill="url(#aLG)"/><path d="M100 45C115 40 120 50 115 60C110 70 100 65 95 55C92 48 95 45 100 45Z" fill="url(#aLG)"/><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>
964
726
  <h1 data-i18n="title">OpenClaw Memory</h1>
965
727
  <p style="font-size:12px;color:var(--text-sec);margin-bottom:6px" data-i18n="subtitle">Powered by MemOS</p>
966
728
  <p data-i18n="setup.desc">Set a password to protect your memories</p>
@@ -978,9 +740,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
978
740
  <button class="auth-theme-toggle" onclick="toggleLang()" aria-label="Switch language"><span data-i18n="lang.switch">EN</span></button>
979
741
  </div>
980
742
  <div class="auth-card">
981
- <div class="logo" style="display:flex;flex-direction:column;align-items:center;gap:10px">
982
- <svg width="56" height="56" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="bLG" 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(#bLG)"/><path d="M20 45C5 40 0 50 5 60C10 70 20 65 25 55C28 48 25 45 20 45Z" fill="url(#bLG)"/><path d="M100 45C115 40 120 50 115 60C110 70 100 65 95 55C92 48 95 45 100 45Z" fill="url(#bLG)"/><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>
983
- </div>
743
+ <div class="logo"><svg width="60" height="60" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="bLG" 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(#bLG)"/><path d="M20 45C5 40 0 50 5 60C10 70 20 65 25 55C28 48 25 45 20 45Z" fill="url(#bLG)"/><path d="M100 45C115 40 120 50 115 60C110 70 100 65 95 55C92 48 95 45 100 45Z" fill="url(#bLG)"/><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>
984
744
  <h1 data-i18n="title">OpenClaw Memory</h1>
985
745
  <p style="font-size:12px;color:var(--text-sec);margin-bottom:6px" data-i18n="subtitle">Powered by MemOS</p>
986
746
  <p data-i18n="login.desc">Enter your password to access memories</p>
@@ -1032,8 +792,8 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1032
792
  <div class="app" id="app">
1033
793
  <div class="topbar">
1034
794
  <div class="brand">
1035
- <span class="memos-logo"><svg width="28" height="28" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="topLG" 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(#topLG)"/><path d="M20 45C5 40 0 50 5 60C10 70 20 65 25 55C28 48 25 45 20 45Z" fill="url(#topLG)"/><path d="M100 45C115 40 120 50 115 60C110 70 100 65 95 55C92 48 95 45 100 45Z" fill="url(#topLG)"/><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></span>
1036
- <div class="brand-col"><span data-i18n="title" class="brand-title">MemOS</span><span data-i18n="subtitle" class="brand-powered">Powered by MemOS</span></div>${vBadge}
795
+ <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>
796
+ <span data-i18n="title">OpenClaw Memory</span>${vBadge}
1037
797
  </div>
1038
798
  <div class="topbar-center">
1039
799
  <nav class="nav-tabs">
@@ -1043,7 +803,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1043
803
  <button class="tab" data-view="analytics" onclick="switchView('analytics')" data-i18n="tab.analytics">\u{1F4CA} Analytics</button>
1044
804
  <button class="tab" data-view="logs" onclick="switchView('logs')" data-i18n="tab.logs">\u{1F4DD} Logs</button>
1045
805
  <button class="tab" data-view="import" onclick="switchView('import')" data-i18n="tab.import">\u{1F4E5} Import</button>
1046
- <button class="tab" data-view="admin" onclick="switchView('admin')" data-i18n="tab.admin">\u{1F6E1} Admin</button>
1047
806
  <button class="tab" data-view="settings" onclick="switchView('settings')" data-i18n="tab.settings">\u2699 Settings</button>
1048
807
  </nav>
1049
808
  </div>
@@ -1063,16 +822,12 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1063
822
  <div class="stat-card amber"><div class="stat-value" id="statEmbeddings">-</div><div class="stat-label" data-i18n="stat.embeddings">Embeddings</div></div>
1064
823
  <div class="stat-card rose"><div class="stat-value" id="statTimeSpan">-</div><div class="stat-label" data-i18n="stat.days">Days</div></div>
1065
824
  </div>
1066
- <div id="sidebarSharingSection" style="display:none">
1067
- <div class="sharing-sidebar-card">
1068
- <div class="title" style="display:flex;align-items:center;gap:8px"><span data-i18n="sidebar.hub">\u{1F310} Team Sharing</span><span id="sharingSidebarConnBadge"></span></div>
1069
- <div class="status" id="sharingSidebarStatus"></div>
1070
- <div class="hint" id="sharingSidebarHint"></div>
1071
- </div>
825
+ <div id="sidebarSessionSection">
826
+ <div id="embeddingStatus"></div>
827
+ <div class="section-title" data-i18n="sidebar.sessions">Sessions</div>
828
+ <div class="session-list" id="sessionList"></div>
829
+ <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>
1072
830
  </div>
1073
- <div id="embeddingStatus"></div>
1074
- <div class="session-list" id="sessionList" style="display:none"></div>
1075
- <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>
1076
831
  </div>
1077
832
 
1078
833
  <div class="feed-wrap" id="feedWrap">
@@ -1080,17 +835,8 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1080
835
  <div class="search-bar">
1081
836
  <span class="search-icon">\u{1F50D}</span>
1082
837
  <input type="text" id="searchInput" data-i18n-ph="search.placeholder" placeholder="Search memories (supports semantic search)..." oninput="debounceSearch()">
1083
- <select id="filterOwner" class="filter-select" onchange="onOwnerFilterChange()">
1084
- <option value="" data-i18n="filter.allowners">All owners</option>
1085
- <option value="public" data-i18n="filter.public">Public</option>
1086
- </select>
1087
- <select id="memorySearchScope" class="filter-select" onchange="onMemoryScopeChange()">
1088
- <option value="local" data-i18n="scope.local">Local</option>
1089
- <option value="all" data-i18n="scope.hub">Hub</option>
1090
- </select>
1091
838
  </div>
1092
839
  <div class="search-meta" id="searchMeta"></div>
1093
- <div class="search-meta" id="sharingSearchMeta"></div>
1094
840
  <div class="filter-bar" id="filterBar">
1095
841
  <button class="filter-chip active" data-role="" onclick="setRoleFilter(this,'')" data-i18n="filter.all">All</button>
1096
842
  <button class="filter-chip" data-role="user" onclick="setRoleFilter(this,'user')">User</button>
@@ -1102,8 +848,9 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1102
848
  <option value="oldest" data-i18n="filter.oldest">Oldest first</option>
1103
849
  </select>
1104
850
  <span class="filter-sep"></span>
1105
- <select id="filterSession" class="filter-select" onchange="filterSession(this.value||null)">
1106
- <option value="" data-i18n="filter.allsessions">All sessions</option>
851
+ <select id="filterOwner" class="filter-select" onchange="applyFilters()">
852
+ <option value="" data-i18n="filter.allowners">All owners</option>
853
+ <option value="public" data-i18n="filter.public">Public</option>
1107
854
  </select>
1108
855
  </div>
1109
856
  <div class="date-filter">
@@ -1128,10 +875,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1128
875
  <button class="filter-chip" data-task-status="active" onclick="setTaskStatusFilter(this,'active')" data-i18n="tasks.status.active">Active</button>
1129
876
  <button class="filter-chip" data-task-status="completed" onclick="setTaskStatusFilter(this,'completed')" data-i18n="tasks.status.completed">Completed</button>
1130
877
  <button class="filter-chip" data-task-status="skipped" onclick="setTaskStatusFilter(this,'skipped')" data-i18n="tasks.status.skipped">Skipped</button>
1131
- <select id="taskSearchScope" class="scope-select" onchange="onTaskScopeChange()">
1132
- <option value="local" data-i18n="scope.local">Local</option>
1133
- <option value="all" data-i18n="scope.hub">Hub</option>
1134
- </select>
1135
878
  <button class="btn btn-sm btn-ghost" onclick="loadTasks()" style="margin-left:auto" data-i18n="refresh">\u21BB Refresh</button>
1136
879
  </div>
1137
880
  </div>
@@ -1141,10 +884,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1141
884
  <div class="task-detail-panel" onclick="event.stopPropagation()">
1142
885
  <div class="task-detail-header">
1143
886
  <h2 id="taskDetailTitle"></h2>
1144
- <div style="display:flex;gap:8px;align-items:center">
1145
- <div id="taskShareActions" style="display:flex;gap:8px;align-items:center"></div>
1146
- <button class="btn btn-icon" onclick="closeTaskDetail()" title="Close">\u2715</button>
1147
- </div>
887
+ <button class="btn btn-icon" onclick="closeTaskDetail()" title="Close">\u2715</button>
1148
888
  </div>
1149
889
  <div class="task-detail-meta" id="taskDetailMeta"></div>
1150
890
  <div class="task-skill-section" id="taskSkillSection"></div>
@@ -1155,27 +895,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1155
895
  </div>
1156
896
  </div>
1157
897
  </div>
1158
- <div class="shared-memory-overlay" id="sharedMemoryOverlay" onclick="closeSharedMemoryDetail(event)">
1159
- <div class="shared-memory-panel" onclick="event.stopPropagation()">
1160
- <div class="task-detail-header">
1161
- <h3 id="sharedMemoryTitle">Shared Memory</h3>
1162
- <button class="btn btn-icon" onclick="closeSharedMemoryDetail()" title="Close">✕</button>
1163
- </div>
1164
- <div class="task-detail-meta" id="sharedMemoryMeta"></div>
1165
- <div class="task-detail-summary" id="sharedMemorySummary"></div>
1166
- <div class="content" id="sharedMemoryContent"></div>
1167
- </div>
1168
- </div>
1169
898
  <div class="skills-view" id="skillsView">
1170
- <div class="search-bar">
1171
- <span class="search-icon">🔍</span>
1172
- <input type="text" id="skillSearchInput" placeholder="Search skills..." data-i18n-ph="skills.search.placeholder" oninput="debounceSkillSearch()">
1173
- <select id="skillSearchScope" class="scope-select" onchange="onSkillScopeChange()">
1174
- <option value="local" data-i18n="scope.local">Local</option>
1175
- <option value="all" data-i18n="scope.hub">Hub</option>
1176
- </select>
1177
- </div>
1178
- <div class="search-meta" id="skillSearchMeta"></div>
1179
899
  <div class="tasks-header">
1180
900
  <div class="tasks-stats">
1181
901
  <div class="tasks-stat"><span class="tasks-stat-value" id="skillsTotalCount">-</span><span class="tasks-stat-label" data-i18n="skills.total">Total Skills</span></div>
@@ -1199,10 +919,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1199
919
  </div>
1200
920
  </div>
1201
921
  <div class="tasks-list" id="skillsList"><div class="spinner"></div></div>
1202
- <div id="hubSkillsSection" style="display:none;margin-top:16px">
1203
- <div class="section-title" style="margin-bottom:12px" data-i18n="skills.hub.title">\u{1F310} Hub Skills</div>
1204
- <div class="tasks-list" id="hubSkillsList"></div>
1205
- </div>
1206
922
  </div>
1207
923
  <div class="task-detail-overlay" id="skillDetailOverlay" onclick="closeSkillDetail(event)">
1208
924
  <div class="task-detail-panel" onclick="event.stopPropagation()">
@@ -1216,7 +932,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1216
932
  </div>
1217
933
  <div class="task-detail-meta" id="skillDetailMeta"></div>
1218
934
  <div class="skill-detail-desc" id="skillDetailDesc"></div>
1219
- <div id="skillShareActions" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:8px 0"></div>
1220
935
  <div class="task-detail-chunks-title" data-i18n="skills.files">Skill Files</div>
1221
936
  <div class="skill-files-list" id="skillFilesList"></div>
1222
937
  <div class="task-detail-chunks-title" id="skillContentTitle" data-i18n="skills.content">SKILL.md Content</div>
@@ -1282,380 +997,186 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1282
997
 
1283
998
  <!-- ─── Settings View ─── -->
1284
999
  <div class="settings-view" id="settingsView">
1285
- <div class="settings-tabs-bar">
1286
- <button class="settings-tab-btn active" data-tab="models" onclick="switchSettingsTab('models',this)"><span class="stab-dot"></span><span data-i18n="settings.models">AI Models</span></button>
1287
- <button class="settings-tab-btn" data-tab="hub" onclick="switchSettingsTab('hub',this)"><span class="stab-dot"></span><span data-i18n="settings.hub">Hub & Team</span></button>
1288
- <button class="settings-tab-btn" data-tab="general" onclick="switchSettingsTab('general',this)"><span class="stab-dot"></span><span data-i18n="settings.general">General</span></button>
1289
- </div>
1290
- <div class="settings-cards-grid">
1291
-
1292
- <!-- ══ Card: AI Models (Embedding + Summarizer + Skill) ══ -->
1293
- <div class="settings-card card-models stab-active" data-stab="models">
1294
- <div class="settings-card-header">
1295
- <div class="settings-card-icon" style="background:rgba(99,102,241,.1);border-color:rgba(99,102,241,.15)">\u{1F9E0}</div>
1296
- <div class="settings-card-title-wrap">
1297
- <div class="settings-card-title" data-i18n="settings.models">AI Models</div>
1298
- <div class="settings-card-desc" data-i18n="settings.models.desc">Configure embedding, summarizer and skill evolution models</div>
1299
- </div>
1000
+ <div class="settings-group" id="settingsModelConfig">
1001
+ <h2 class="settings-group-title"><span data-i18n="settings.modelconfig">Model Configuration</span></h2>
1002
+ <div class="settings-section">
1003
+ <h3><span class="icon">\u{1F4CA}</span> <span data-i18n="settings.modelhealth">Model Health</span></h3>
1004
+ <div class="model-health-bar" id="modelHealthBar">
1005
+ <div style="font-size:12px;color:var(--text-muted);width:100%">Loading model status...</div>
1300
1006
  </div>
1301
- <div class="settings-card-body">
1302
- <!-- Embedding Model section -->
1303
- <div class="settings-card-subtitle">\u{1F4E1} <span data-i18n="settings.embedding">Embedding Model</span></div>
1304
- <div class="field-hint" style="margin-bottom:10px" data-i18n="settings.embedding.desc">Vector embedding model for memory search and retrieval</div>
1305
- <div class="settings-grid">
1306
- <div class="settings-field">
1307
- <label data-i18n="settings.provider">Provider</label>
1308
- <select id="cfgEmbProvider" onchange="onProviderChange('embedding')">
1309
- <option value="openai_compatible">OpenAI Compatible</option>
1310
- <option value="openai">OpenAI</option>
1311
- <option value="siliconflow">SiliconFlow (\u7845\u57FA\u6D41\u52A8)</option>
1312
- <option value="zhipu">Zhipu AI (\u667A\u8C31)</option>
1313
- <option value="bailian">Alibaba Bailian (\u767E\u70BC)</option>
1314
- <option value="gemini">Gemini</option>
1315
- <option value="azure_openai">Azure OpenAI</option>
1316
- <option value="cohere">Cohere</option>
1317
- <option value="mistral">Mistral</option>
1318
- <option value="voyage">Voyage</option>
1319
- <option value="local">Local</option>
1320
- <option value="openclaw">OpenClaw Host</option>
1321
- </select>
1322
- </div>
1323
- <div class="settings-field">
1324
- <label data-i18n="settings.model">Model</label>
1325
- <input type="text" id="cfgEmbModel" placeholder="e.g. bge-m3">
1326
- </div>
1327
- <div class="settings-field full-width">
1328
- <label>Endpoint</label>
1329
- <input type="text" id="cfgEmbEndpoint" placeholder="https://...">
1330
- </div>
1331
- <div class="settings-field">
1332
- <label>API Key</label>
1333
- <input type="password" id="cfgEmbApiKey" placeholder="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022">
1334
- </div>
1335
- </div>
1336
- <div class="test-conn-row">
1337
- <button class="btn btn-sm btn-ghost" onclick="testModel('embedding')" id="testEmbBtn" data-i18n="settings.test">Test Connection</button>
1338
- <span class="test-result" id="testEmbResult"></span>
1339
- </div>
1340
-
1341
- <div class="settings-card-divider"></div>
1342
-
1343
- <!-- Summarizer Model section -->
1344
- <div class="settings-card-subtitle">\u{1F4DD} <span data-i18n="settings.summarizer">Summarizer Model</span></div>
1345
- <div class="field-hint" style="margin-bottom:10px" data-i18n="settings.summarizer.desc">LLM for memory summarization, deduplication and analysis</div>
1346
- <div class="settings-grid">
1347
- <div class="settings-field">
1348
- <label data-i18n="settings.provider">Provider</label>
1349
- <select id="cfgSumProvider" onchange="onProviderChange('summarizer')">
1350
- <option value="openai_compatible">OpenAI Compatible</option>
1351
- <option value="openai">OpenAI</option>
1352
- <option value="siliconflow">SiliconFlow (\u7845\u57FA\u6D41\u52A8)</option>
1353
- <option value="zhipu">Zhipu AI (\u667A\u8C31)</option>
1354
- <option value="deepseek">DeepSeek</option>
1355
- <option value="bailian">Alibaba Bailian (\u767E\u70BC)</option>
1356
- <option value="moonshot">Moonshot (Kimi)</option>
1357
- <option value="anthropic">Anthropic</option>
1358
- <option value="gemini">Gemini</option>
1359
- <option value="azure_openai">Azure OpenAI</option>
1360
- <option value="bedrock">Bedrock</option>
1361
- <option value="openclaw">OpenClaw Host</option>
1362
- </select>
1363
- </div>
1364
- <div class="settings-field">
1365
- <label data-i18n="settings.model">Model</label>
1366
- <input type="text" id="cfgSumModel" placeholder="e.g. gpt-4o-mini">
1367
- </div>
1368
- <div class="settings-field full-width">
1369
- <label>Endpoint</label>
1370
- <input type="text" id="cfgSumEndpoint" placeholder="https://...">
1371
- </div>
1372
- <div class="settings-field">
1373
- <label>API Key</label>
1374
- <input type="password" id="cfgSumApiKey" placeholder="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022">
1375
- </div>
1376
- <div class="settings-field">
1377
- <label data-i18n="settings.temperature">Temperature</label>
1378
- <input type="number" id="cfgSumTemp" step="0.1" min="0" max="2" placeholder="0">
1379
- </div>
1380
- </div>
1381
- <div class="test-conn-row">
1382
- <button class="btn btn-sm btn-ghost" onclick="testModel('summarizer')" id="testSumBtn" data-i18n="settings.test">Test Connection</button>
1383
- <span class="test-result" id="testSumResult"></span>
1384
- </div>
1385
-
1386
- <div class="settings-card-divider"></div>
1387
-
1388
- <!-- Skill Evolution section -->
1389
- <div class="settings-card-subtitle">\u{1F527} <span data-i18n="settings.skill">Skill Evolution</span></div>
1390
- <div class="field-hint" style="margin-bottom:10px" data-i18n="settings.skill.desc">Auto-extract reusable skills from conversation patterns</div>
1391
- <div class="settings-grid">
1392
- <div class="settings-toggle">
1393
- <label class="toggle-switch"><input type="checkbox" id="cfgSkillEnabled"><span class="toggle-slider"></span></label>
1394
- <label data-i18n="settings.skill.enabled">Enable Skill Evolution</label>
1395
- </div>
1396
- <div class="settings-toggle">
1397
- <label class="toggle-switch"><input type="checkbox" id="cfgSkillAutoInstall"><span class="toggle-slider"></span></label>
1398
- <label data-i18n="settings.skill.autoinstall">Auto Install Skills</label>
1399
- </div>
1400
- <div class="settings-field">
1401
- <label data-i18n="settings.skill.confidence">Min Confidence</label>
1402
- <input type="number" id="cfgSkillConfidence" step="0.1" min="0" max="1" placeholder="0.7">
1403
- </div>
1404
- <div class="settings-field">
1405
- <label data-i18n="settings.skill.minchunks">Min Chunks</label>
1406
- <input type="number" id="cfgSkillMinChunks" placeholder="6">
1407
- </div>
1408
- </div>
1409
- <div style="margin-top:14px">
1410
- <div class="settings-card-subtitle" style="margin-bottom:4px" data-i18n="settings.skill.model">Skill Dedicated Model</div>
1411
- <div class="field-hint" style="margin-bottom:12px" data-i18n="settings.skill.model.hint">If not configured, the main Summarizer Model above will be used for skill generation. Configure a dedicated model here for higher quality skill output.</div>
1412
- <div class="settings-grid">
1413
- <div class="settings-field">
1414
- <label data-i18n="settings.provider">Provider</label>
1415
- <select id="cfgSkillProvider" onchange="onProviderChange('skill')">
1416
- <option value="">\u2014 <span data-i18n="settings.skill.usemain">Use main summarizer</span> \u2014</option>
1417
- <option value="openai_compatible">OpenAI Compatible</option>
1418
- <option value="openai">OpenAI</option>
1419
- <option value="siliconflow">SiliconFlow (\u7845\u57FA\u6D41\u52A8)</option>
1420
- <option value="zhipu">Zhipu AI (\u667A\u8C31)</option>
1421
- <option value="deepseek">DeepSeek</option>
1422
- <option value="bailian">Alibaba Bailian (\u767E\u70BC)</option>
1423
- <option value="moonshot">Moonshot (Kimi)</option>
1424
- <option value="anthropic">Anthropic</option>
1425
- <option value="gemini">Gemini</option>
1426
- <option value="azure_openai">Azure OpenAI</option>
1427
- <option value="bedrock">Bedrock</option>
1428
- <option value="openclaw">OpenClaw Host</option>
1429
- </select>
1430
- </div>
1431
- <div class="settings-field">
1432
- <label data-i18n="settings.model">Model</label>
1433
- <input type="text" id="cfgSkillModel" placeholder="e.g. claude-4.6-opus">
1434
- </div>
1435
- <div class="settings-field full-width">
1436
- <label>Endpoint</label>
1437
- <input type="text" id="cfgSkillEndpoint" placeholder="https://...">
1438
- </div>
1439
- <div class="settings-field">
1440
- <label>API Key</label>
1441
- <input type="password" id="cfgSkillApiKey" placeholder="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022">
1442
- </div>
1443
- </div>
1444
- <div class="test-conn-row">
1445
- <button class="btn btn-sm btn-ghost" onclick="testModel('skill')" id="testSkillBtn" data-i18n="settings.test">Test Connection</button>
1446
- <span class="test-result" id="testSkillResult"></span>
1447
- </div>
1448
- </div>
1449
-
1450
- <div class="settings-card-divider"></div>
1451
- <div class="settings-actions">
1452
- <span class="settings-saved" id="modelsSaved">\u2713 <span data-i18n="settings.saved">Saved</span></span>
1453
- <button class="btn btn-ghost" onclick="loadConfig()" data-i18n="settings.reset">Reset</button>
1454
- <button class="btn btn-primary" onclick="saveModelsConfig()" data-i18n="settings.save">Save Settings</button>
1455
- </div>
1456
- <div style="font-size:11px;color:var(--text-muted);text-align:right;margin-top:4px" data-i18n="settings.restart.hint">Some changes require restarting the OpenClaw gateway to take effect.</div>
1007
+ </div>
1008
+ <div class="settings-section">
1009
+ <h3><span class="icon">\u{1F4E1}</span> <span data-i18n="settings.embedding">Embedding Model</span></h3>
1010
+ <div class="settings-grid">
1011
+ <div class="settings-field">
1012
+ <label data-i18n="settings.provider">Provider</label>
1013
+ <select id="cfgEmbProvider" onchange="onProviderChange('embedding')">
1014
+ <option value="openai_compatible">OpenAI Compatible</option>
1015
+ <option value="openai">OpenAI</option>
1016
+ <option value="siliconflow">SiliconFlow (\u7845\u57FA\u6D41\u52A8)</option>
1017
+ <option value="zhipu">Zhipu AI (\u667A\u8C31)</option>
1018
+ <option value="bailian">Alibaba Bailian (\u767E\u70BC)</option>
1019
+ <option value="gemini">Gemini</option>
1020
+ <option value="azure_openai">Azure OpenAI</option>
1021
+ <option value="cohere">Cohere</option>
1022
+ <option value="mistral">Mistral</option>
1023
+ <option value="voyage">Voyage</option>
1024
+ <option value="local">Local</option>
1025
+ </select>
1026
+ </div>
1027
+ <div class="settings-field">
1028
+ <label data-i18n="settings.model">Model</label>
1029
+ <input type="text" id="cfgEmbModel" placeholder="e.g. bge-m3">
1030
+ </div>
1031
+ <div class="settings-field full-width">
1032
+ <label>Endpoint</label>
1033
+ <input type="text" id="cfgEmbEndpoint" placeholder="https://...">
1034
+ </div>
1035
+ <div class="settings-field">
1036
+ <label>API Key</label>
1037
+ <input type="password" id="cfgEmbApiKey" placeholder="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022">
1457
1038
  </div>
1458
1039
  </div>
1040
+ <div class="test-conn-row">
1041
+ <button class="btn btn-sm btn-ghost" onclick="testModel('embedding')" id="testEmbBtn" data-i18n="settings.test">Test Connection</button>
1042
+ <span class="test-result" id="testEmbResult"></span>
1043
+ </div>
1044
+ </div>
1459
1045
 
1460
- <!-- ══ Card: Hub & Team ══ -->
1461
- <div class="settings-card card-hub" id="settingsSharingConfig" data-stab="hub">
1462
- <div class="settings-card-header">
1463
- <div class="settings-card-icon" style="background:rgba(6,182,212,.1);border-color:rgba(6,182,212,.15)">\u{1F310}</div>
1464
- <div class="settings-card-title-wrap">
1465
- <div class="settings-card-title" data-i18n="settings.hub">Hub & Team</div>
1466
- <div class="settings-card-desc" data-i18n="settings.hub.desc">Share memories, tasks and skills with your team</div>
1467
- </div>
1046
+ <div class="settings-section">
1047
+ <h3><span class="icon">\u{1F9E0}</span> <span data-i18n="settings.summarizer">Summarizer Model</span></h3>
1048
+ <div class="settings-grid">
1049
+ <div class="settings-field">
1050
+ <label data-i18n="settings.provider">Provider</label>
1051
+ <select id="cfgSumProvider" onchange="onProviderChange('summarizer')">
1052
+ <option value="openai_compatible">OpenAI Compatible</option>
1053
+ <option value="openai">OpenAI</option>
1054
+ <option value="siliconflow">SiliconFlow (\u7845\u57FA\u6D41\u52A8)</option>
1055
+ <option value="zhipu">Zhipu AI (\u667A\u8C31)</option>
1056
+ <option value="deepseek">DeepSeek</option>
1057
+ <option value="bailian">Alibaba Bailian (\u767E\u70BC)</option>
1058
+ <option value="moonshot">Moonshot (Kimi)</option>
1059
+ <option value="anthropic">Anthropic</option>
1060
+ <option value="gemini">Gemini</option>
1061
+ <option value="azure_openai">Azure OpenAI</option>
1062
+ <option value="bedrock">Bedrock</option>
1063
+ </select>
1468
1064
  </div>
1469
- <div class="settings-card-body">
1470
- <!-- team setup guide (inside Hub card) -->
1471
- <div class="team-guide" id="teamSetupGuide">
1472
- <button class="team-guide-dismiss" onclick="dismissTeamGuide()" title="Dismiss">&times;</button>
1473
- <div class="team-guide-title">\u{1F680} <span data-i18n="guide.title">Get Started with Team Collaboration</span></div>
1474
- <div class="team-guide-subtitle" data-i18n="guide.subtitle">MemOS supports team memory sharing. Choose one of the following options to enable collaboration, or continue using local-only mode.</div>
1475
- <div class="team-guide-options">
1476
- <div class="team-guide-opt">
1477
- <div class="team-guide-opt-header">
1478
- <div class="team-guide-opt-icon" style="background:rgba(6,182,212,.1);border:1px solid rgba(6,182,212,.15)">\u{1F310}</div>
1479
- <div class="team-guide-opt-title" data-i18n="guide.join.title">Join a Remote Team</div>
1480
- </div>
1481
- <div class="team-guide-opt-desc" data-i18n="guide.join.desc">Your team already has a Hub running? Join it to share memories, tasks and skills with team members.</div>
1482
- <ol class="team-guide-steps">
1483
- <li><span data-i18n="guide.join.s1">Ask your Hub admin for the Hub Address and Team Token</span></li>
1484
- <li><span data-i18n="guide.join.s2">Enable sharing above, select "Client" mode</span></li>
1485
- <li><span data-i18n="guide.join.s3">Fill in Hub Address and Team Token, click "Test Connection"</span></li>
1486
- <li><span data-i18n="guide.join.s4">Save settings, restart the OpenClaw gateway, then refresh this page</span></li>
1487
- </ol>
1488
- <button class="btn-guide" onclick="guideGoToHub('client')" data-i18n="guide.join.btn">\u2192 Configure Client Mode</button>
1489
- </div>
1490
- <div class="team-guide-opt">
1491
- <div class="team-guide-opt-header">
1492
- <div class="team-guide-opt-icon" style="background:rgba(139,92,246,.1);border:1px solid rgba(139,92,246,.15)">\u{1F5A5}\uFE0F</div>
1493
- <div class="team-guide-opt-title" data-i18n="guide.hub.title">Start Your Own Hub</div>
1494
- </div>
1495
- <div class="team-guide-opt-desc" data-i18n="guide.hub.desc">Be the team server. Run a Hub on this device so others can connect and share memories with you.</div>
1496
- <ol class="team-guide-steps">
1497
- <li><span data-i18n="guide.hub.s1">Enable sharing above, select "Hub" mode</span></li>
1498
- <li><span data-i18n="guide.hub.s2">Set a team name, save settings, restart the gateway, then refresh this page</span></li>
1499
- <li><span data-i18n="guide.hub.s3">Share the Hub Address and Team Token with your team members</span></li>
1500
- <li><span data-i18n="guide.hub.s4">Approve join requests in the Admin Panel</span></li>
1501
- </ol>
1502
- <button class="btn-guide" onclick="guideGoToHub('hub')" data-i18n="guide.hub.btn">\u2192 Configure Hub Mode</button>
1503
- </div>
1504
- </div>
1505
- </div>
1506
-
1507
- <div class="field-hint" style="margin-bottom:12px" data-i18n="settings.hub.enable.hint">Enable to share memories, tasks and skills with your team. When disabled, all features work normally in local-only mode.</div>
1508
- <div class="settings-toggle" style="margin-bottom:16px">
1509
- <label class="toggle-switch">
1510
- <input type="checkbox" id="cfgSharingEnabled" onchange="onSharingToggle()">
1511
- <span class="toggle-slider"></span>
1512
- </label>
1513
- <label data-i18n="settings.hub.enable.label">Enable Hub Sharing</label>
1514
- </div>
1515
-
1516
- <div id="sharingConfigPanel" style="display:none">
1517
- <div style="margin-bottom:14px">
1518
- <label style="font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.04em;display:block;margin-bottom:6px" data-i18n="settings.hub.role">Role</label>
1519
- <div style="display:flex;gap:8px">
1520
- <button class="btn btn-sm" id="btnRoleHub" onclick="selectSharingRole('hub')" data-i18n="settings.hub.role.hub">Hub (Server)</button>
1521
- <button class="btn btn-sm" id="btnRoleClient" onclick="selectSharingRole('client')" data-i18n="settings.hub.role.client">Client (Connect to Hub)</button>
1522
- </div>
1523
- <div class="field-hint" style="margin-top:6px" data-i18n="settings.hub.role.hint">Hub: this device runs the central server. Client: connect to an existing Hub. The two modes are mutually exclusive.</div>
1524
- </div>
1525
-
1526
- <div id="hubModeConfig" style="display:none">
1527
- <input type="hidden" id="cfgHubTeamToken" value="">
1528
- <div class="settings-grid">
1529
- <div class="settings-field">
1530
- <label data-i18n="settings.hub.port">Hub Port</label>
1531
- <input type="number" id="cfgHubPort" placeholder="18800" value="18800">
1532
- <div class="field-hint" data-i18n="settings.hub.port.hint">Port for Hub service. Default: 18800</div>
1533
- </div>
1534
- <div class="settings-field">
1535
- <label data-i18n="settings.hub.teamName">Team Name</label>
1536
- <input type="text" id="cfgHubTeamName" placeholder="My Team">
1537
- <div class="field-hint" data-i18n="settings.hub.teamName.hint">Your team display name</div>
1538
- </div>
1539
- </div>
1540
- <div id="hubShareInfo" style="display:none;margin-top:14px;background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:14px 18px">
1541
- <div style="font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.04em;margin-bottom:10px" data-i18n="settings.hub.shareInfo.title">Share this info with your team members:</div>
1542
- <div id="hubShareInfoContent" style="display:grid;grid-template-columns:auto 1fr;gap:6px 12px;font-size:12px;align-items:center"></div>
1543
- </div>
1544
- <div style="margin-top:16px;display:flex;align-items:center;gap:12px">
1545
- <button class="btn btn-sm btn-primary" onclick="switchView('admin')" id="hubAdminEntryBtn" style="display:none" data-i18n="sharing.openAdmin">\u{1F6E1} Open Admin Panel</button>
1546
- </div>
1547
- </div>
1548
-
1549
- <div id="clientModeConfig" style="display:none">
1550
- <div style="background:rgba(99,102,241,.08);border:1px solid rgba(99,102,241,.2);border-radius:10px;padding:14px 18px;margin-bottom:14px;font-size:12px;line-height:1.7;color:var(--text-sec)">
1551
- <div style="font-weight:700;color:var(--text);margin-bottom:4px" data-i18n="settings.hub.clientSteps.title">Quick Setup (3 steps)</div>
1552
- <div><span style="color:var(--accent)">1.</span> <span data-i18n="settings.hub.clientSteps.s1">Ask your Hub admin for Hub Address and Team Token</span></div>
1553
- <div><span style="color:var(--accent)">2.</span> <span data-i18n="settings.hub.clientSteps.s2">Fill them in below, click "Test Connection" to verify</span></div>
1554
- <div><span style="color:var(--accent)">3.</span> <span data-i18n="settings.hub.clientSteps.s3">Click "Save Settings", restart OpenClaw gateway, then refresh this page</span></div>
1555
- </div>
1556
- <div class="settings-grid">
1557
- <div class="settings-field full-width">
1558
- <label data-i18n="settings.hub.hubAddress">Hub Address</label>
1559
- <input type="text" id="cfgClientHubAddress" placeholder="e.g. 192.168.1.100:18800">
1560
- <div class="field-hint" data-i18n="settings.hub.hubAddress.hint">Hub server address, e.g. 192.168.1.100:18800 or hub.example.com:18800</div>
1561
- </div>
1562
- <div class="settings-field">
1563
- <label data-i18n="settings.hub.teamTokenClient">Team Token</label>
1564
- <input type="text" id="cfgClientTeamToken" placeholder="">
1565
- <div class="field-hint" data-i18n="settings.hub.teamTokenClient.hint">Get this from your Hub admin to join the team</div>
1566
- </div>
1567
- <input type="hidden" id="cfgClientUserToken" value="">
1568
- </div>
1569
- <div style="margin-top:12px">
1570
- <button class="btn btn-sm btn-primary" id="btnTestHubConn" onclick="testHubConnection()" data-i18n="settings.hub.testConnection">Test Connection</button>
1571
- <span id="hubConnTestResult" style="margin-left:10px;font-size:12px"></span>
1572
- </div>
1573
- </div>
1574
- </div>
1575
-
1576
- <div id="sharingPanelsWrap" style="display:none">
1577
- <div class="settings-card-divider"></div>
1578
- <div id="sharingStatusPanel"></div>
1579
- <div id="sharingTeamPanel"></div>
1580
- <div id="sharingAdminPanel"></div>
1581
- </div>
1582
-
1583
- <div class="settings-card-divider"></div>
1584
- <div class="settings-actions">
1585
- <span class="settings-saved" id="hubSaved">\u2713 <span data-i18n="settings.saved">Saved</span></span>
1586
- <button class="btn btn-ghost" onclick="loadConfig()" data-i18n="settings.reset">Reset</button>
1587
- <button class="btn btn-primary" onclick="saveHubConfig()" data-i18n="settings.save">Save Settings</button>
1588
- </div>
1589
- <div style="font-size:11px;color:var(--text-muted);text-align:right;margin-top:4px" data-i18n="settings.restart.hint">Some changes require restarting the OpenClaw gateway to take effect.</div>
1065
+ <div class="settings-field">
1066
+ <label data-i18n="settings.model">Model</label>
1067
+ <input type="text" id="cfgSumModel" placeholder="e.g. gpt-4o-mini">
1068
+ </div>
1069
+ <div class="settings-field full-width">
1070
+ <label>Endpoint</label>
1071
+ <input type="text" id="cfgSumEndpoint" placeholder="https://...">
1072
+ </div>
1073
+ <div class="settings-field">
1074
+ <label>API Key</label>
1075
+ <input type="password" id="cfgSumApiKey" placeholder="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022">
1076
+ </div>
1077
+ <div class="settings-field">
1078
+ <label data-i18n="settings.temperature">Temperature</label>
1079
+ <input type="number" id="cfgSumTemp" step="0.1" min="0" max="2" placeholder="0">
1590
1080
  </div>
1591
1081
  </div>
1082
+ <div class="test-conn-row">
1083
+ <button class="btn btn-sm btn-ghost" onclick="testModel('summarizer')" id="testSumBtn" data-i18n="settings.test">Test Connection</button>
1084
+ <span class="test-result" id="testSumResult"></span>
1085
+ </div>
1086
+ </div>
1087
+ </div>
1592
1088
 
1593
- <!-- ══ Card: General ══ -->
1594
- <div class="settings-card card-general" id="settingsModelConfig" data-stab="general">
1595
- <div class="settings-card-header">
1596
- <div class="settings-card-icon" style="background:rgba(16,185,129,.1);border-color:rgba(16,185,129,.15)">\u2699\uFE0F</div>
1597
- <div class="settings-card-title-wrap">
1598
- <div class="settings-card-title" data-i18n="settings.general">General</div>
1599
- <div class="settings-card-desc" data-i18n="settings.general.desc">System status, ports and telemetry</div>
1600
- </div>
1089
+ <div class="settings-section">
1090
+ <h3><span class="icon">\u{1F527}</span> <span data-i18n="settings.skill">Skill Evolution</span></h3>
1091
+ <div class="settings-grid">
1092
+ <div class="settings-toggle">
1093
+ <label class="toggle-switch"><input type="checkbox" id="cfgSkillEnabled"><span class="toggle-slider"></span></label>
1094
+ <label data-i18n="settings.skill.enabled">Enable Skill Evolution</label>
1601
1095
  </div>
1602
- <div class="settings-card-body">
1603
- <div class="settings-card-subtitle" data-i18n="settings.modelhealth">\u{1F4CA} Model Health</div>
1604
- <div class="model-health-bar" id="modelHealthBar">
1605
- <div style="font-size:12px;color:var(--text-muted);width:100%">Loading model status...</div>
1096
+ <div class="settings-toggle">
1097
+ <label class="toggle-switch"><input type="checkbox" id="cfgSkillAutoInstall"><span class="toggle-slider"></span></label>
1098
+ <label data-i18n="settings.skill.autoinstall">Auto Install Skills</label>
1099
+ </div>
1100
+ <div class="settings-field">
1101
+ <label data-i18n="settings.skill.confidence">Min Confidence</label>
1102
+ <input type="number" id="cfgSkillConfidence" step="0.1" min="0" max="1" placeholder="0.7">
1103
+ </div>
1104
+ <div class="settings-field">
1105
+ <label data-i18n="settings.skill.minchunks">Min Chunks</label>
1106
+ <input type="number" id="cfgSkillMinChunks" placeholder="6">
1107
+ </div>
1108
+ </div>
1109
+ <div style="margin-top:16px;padding-top:16px;border-top:1px solid var(--border)">
1110
+ <h4 style="font-size:12px;font-weight:600;color:var(--text-sec);margin-bottom:12px"><span data-i18n="settings.skill.model">Skill Dedicated Model</span></h4>
1111
+ <div class="field-hint" style="margin-bottom:12px" data-i18n="settings.skill.model.hint">If not configured, the main Summarizer Model above will be used for skill generation. Configure a dedicated model here for higher quality skill output.</div>
1112
+ <div class="settings-grid">
1113
+ <div class="settings-field">
1114
+ <label data-i18n="settings.provider">Provider</label>
1115
+ <select id="cfgSkillProvider" onchange="onProviderChange('skill')">
1116
+ <option value="">— <span data-i18n="settings.skill.usemain">Use main summarizer</span> —</option>
1117
+ <option value="openai_compatible">OpenAI Compatible</option>
1118
+ <option value="openai">OpenAI</option>
1119
+ <option value="siliconflow">SiliconFlow (\u7845\u57FA\u6D41\u52A8)</option>
1120
+ <option value="zhipu">Zhipu AI (\u667A\u8C31)</option>
1121
+ <option value="deepseek">DeepSeek</option>
1122
+ <option value="bailian">Alibaba Bailian (\u767E\u70BC)</option>
1123
+ <option value="moonshot">Moonshot (Kimi)</option>
1124
+ <option value="anthropic">Anthropic</option>
1125
+ <option value="gemini">Gemini</option>
1126
+ <option value="azure_openai">Azure OpenAI</option>
1127
+ <option value="bedrock">Bedrock</option>
1128
+ </select>
1606
1129
  </div>
1607
- <div class="settings-card-divider"></div>
1608
- <div class="settings-grid">
1609
- <div class="settings-field">
1610
- <label data-i18n="settings.viewerport">Viewer Port</label>
1611
- <input type="number" id="cfgViewerPort" placeholder="18799">
1612
- <div class="field-hint" data-i18n="settings.viewerport.hint">Requires restart to take effect</div>
1613
- </div>
1130
+ <div class="settings-field">
1131
+ <label data-i18n="settings.model">Model</label>
1132
+ <input type="text" id="cfgSkillModel" placeholder="e.g. claude-4.6-opus">
1614
1133
  </div>
1615
- <div class="settings-card-divider"></div>
1616
- <div class="settings-toggle">
1617
- <label class="toggle-switch"><input type="checkbox" id="cfgTelemetryEnabled" checked><span class="toggle-slider"></span></label>
1618
- <label data-i18n="settings.telemetry.enabled">Enable Anonymous Telemetry</label>
1134
+ <div class="settings-field full-width">
1135
+ <label>Endpoint</label>
1136
+ <input type="text" id="cfgSkillEndpoint" placeholder="https://...">
1619
1137
  </div>
1620
- <div class="field-hint" style="margin-top:6px" data-i18n="settings.telemetry.hint">Anonymous usage analytics to help improve the plugin. Only sends tool names, latencies, and version info. No memory content, queries, or personal data is ever sent.</div>
1621
-
1622
- <div class="settings-card-divider"></div>
1623
- <div class="settings-actions">
1624
- <span class="settings-saved" id="generalSaved">\u2713 <span data-i18n="settings.saved">Saved</span></span>
1625
- <button class="btn btn-ghost" onclick="loadConfig()" data-i18n="settings.reset">Reset</button>
1626
- <button class="btn btn-primary" onclick="saveGeneralConfig()" data-i18n="settings.save">Save Settings</button>
1138
+ <div class="settings-field">
1139
+ <label>API Key</label>
1140
+ <input type="password" id="cfgSkillApiKey" placeholder="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022">
1627
1141
  </div>
1628
1142
  </div>
1143
+ <div class="test-conn-row">
1144
+ <button class="btn btn-sm btn-ghost" onclick="testModel('skill')" id="testSkillBtn" data-i18n="settings.test">Test Connection</button>
1145
+ <span class="test-result" id="testSkillResult"></span>
1146
+ </div>
1629
1147
  </div>
1630
-
1631
1148
  </div>
1632
1149
 
1633
-
1634
- </div>
1635
-
1636
- <!-- ─── Admin Page ─── -->
1637
- <div class="admin-view" id="adminView">
1638
- <div id="adminNotEnabled" style="display:none"></div>
1639
- <div id="adminMainContent">
1640
- <div class="admin-header">
1641
- <div class="admin-header-top">
1642
- <h2><span class="ah-icon">\u{1F6E1}</span> <span data-i18n="admin.title">Hub Admin Panel</span></h2>
1643
- <button class="btn btn-sm btn-ghost" onclick="loadAdminData()" style="backdrop-filter:blur(8px)" data-i18n="admin.refresh">\u21BB Refresh</button>
1150
+ <div class="settings-section">
1151
+ <h3><span class="icon">\u{1F4CA}</span> <span data-i18n="settings.telemetry">Telemetry</span></h3>
1152
+ <div class="settings-grid">
1153
+ <div class="settings-toggle">
1154
+ <label class="toggle-switch"><input type="checkbox" id="cfgTelemetryEnabled" checked><span class="toggle-slider"></span></label>
1155
+ <label data-i18n="settings.telemetry.enabled">Enable Anonymous Telemetry</label>
1156
+ </div>
1157
+ <div class="settings-field full-width">
1158
+ <div class="field-hint" data-i18n="settings.telemetry.hint">Anonymous usage analytics to help improve the plugin. Only sends tool names, latencies, and version info. No memory content, queries, or personal data is ever sent.</div>
1644
1159
  </div>
1645
- <div class="admin-header-sub" data-i18n="admin.subtitle">Manage team members, groups, and shared resources</div>
1646
- <div class="admin-stat-row" id="adminStats"></div>
1647
1160
  </div>
1648
- <div class="admin-tabs" id="adminTabsBar">
1649
- <button class="admin-tab active" onclick="switchAdminTab('users',this)"><span class="at-icon">\u{1F465}</span> <span data-i18n="admin.tab.users">Users</span> <span class="at-count" id="adminTabCountUsers">0</span></button>
1650
- <button class="admin-tab" onclick="switchAdminTab('sharedMemories',this)"><span class="at-icon">\u{1F4AD}</span> <span data-i18n="admin.tab.sharedMemories">Shared Memories</span> <span class="at-count" id="adminTabCountMemories">0</span></button>
1651
- <button class="admin-tab" onclick="switchAdminTab('memories',this)"><span class="at-icon">\u{1F4CB}</span> <span data-i18n="admin.tab.memories">Shared Tasks</span> <span class="at-count" id="adminTabCountTasks">0</span></button>
1652
- <button class="admin-tab" onclick="switchAdminTab('skills',this)"><span class="at-icon">\u{1F9E0}</span> <span data-i18n="admin.tab.skills">Shared Skills</span> <span class="at-count" id="adminTabCountSkills">0</span></button>
1161
+ </div>
1162
+
1163
+ <div class="settings-section">
1164
+ <h3><span class="icon">\u{1F4BE}</span> <span data-i18n="settings.general">General</span></h3>
1165
+ <div class="settings-grid">
1166
+ <div class="settings-field">
1167
+ <label data-i18n="settings.viewerport">Viewer Port</label>
1168
+ <input type="number" id="cfgViewerPort" placeholder="18799">
1169
+ <div class="field-hint" data-i18n="settings.viewerport.hint">Requires restart to take effect</div>
1170
+ </div>
1653
1171
  </div>
1654
- <div class="admin-panel active" id="adminUsersPanel"></div>
1655
- <div class="admin-panel" id="adminSharedMemoriesPanel"></div>
1656
- <div class="admin-panel" id="adminMemoriesPanel"></div>
1657
- <div class="admin-panel" id="adminSkillsPanel"></div>
1658
1172
  </div>
1173
+
1174
+ <div class="settings-actions">
1175
+ <span class="settings-saved" id="settingsSaved">\u2713 <span data-i18n="settings.saved">Saved</span></span>
1176
+ <button class="btn btn-ghost" onclick="loadConfig()" data-i18n="settings.reset">Reset</button>
1177
+ <button class="btn btn-primary" onclick="saveConfig()" data-i18n="settings.save">Save Settings</button>
1178
+ </div>
1179
+ <div style="font-size:11px;color:var(--text-muted);text-align:right;margin-top:4px" data-i18n="settings.restart.hint">Some changes require restarting the OpenClaw gateway to take effect.</div>
1659
1180
  </div>
1660
1181
 
1661
1182
  <!-- ─── Import Page ─── -->
@@ -1851,13 +1372,12 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1851
1372
 
1852
1373
  <script>
1853
1374
  let activeSession=null,activeRole='',editingId=null,searchTimer=null,memoryCache={},currentPage=1,totalPages=1,totalCount=0,PAGE_SIZE=40,metricsDays=30;
1854
- let memorySearchScope='local',skillSearchScope='local',taskSearchScope='local';
1855
1375
  let _embeddingWarningShown=false;
1856
1376
 
1857
1377
  /* ─── i18n ─── */
1858
1378
  const I18N={
1859
1379
  en:{
1860
- 'title':'MemOS',
1380
+ 'title':'OpenClaw Memory',
1861
1381
  'subtitle':'Powered by MemOS',
1862
1382
  'setup.desc':'Set a password to protect your memories',
1863
1383
  'setup.pw':'Enter a password (4+ characters)',
@@ -1897,14 +1417,6 @@ const I18N={
1897
1417
  'skills.active':'Active',
1898
1418
  'skills.installed':'Installed',
1899
1419
  'skills.public':'Public',
1900
- 'skills.search.placeholder':'Search skills...',
1901
- 'skills.search.local':'Local',
1902
- 'skills.search.noresult':'No matching skills found',
1903
- 'skills.load.error':'Failed to load skills',
1904
- 'skills.hub.title':'\u{1F310} Hub Skills',
1905
- 'scope.local':'Local',
1906
- 'scope.group':'Group',
1907
- 'scope.all':'All',
1908
1420
  'skills.visibility.public':'Public',
1909
1421
  'skills.visibility.private':'Private',
1910
1422
  'skills.setPublic':'Set Public',
@@ -1943,7 +1455,6 @@ const I18N={
1943
1455
  'filter.newest':'Newest first',
1944
1456
  'filter.oldest':'Oldest first',
1945
1457
  'filter.allowners':'All owners',
1946
- 'filter.allsessions':'All sessions',
1947
1458
  'filter.public':'Public',
1948
1459
  'filter.private':'Private',
1949
1460
  'filter.allvisibility':'All visibility',
@@ -2024,22 +1535,11 @@ const I18N={
2024
1535
  'tab.import':'\u{1F4E5} Import',
2025
1536
  'tab.settings':'\u2699 Settings',
2026
1537
  'settings.modelconfig':'Model Configuration',
2027
- 'settings.models':'AI Models',
2028
- 'settings.models.desc':'Configure embedding, summarizer and skill evolution models',
2029
1538
  'settings.modelhealth':'Model Health',
2030
1539
  'settings.embedding':'Embedding Model',
2031
1540
  'settings.summarizer':'Summarizer Model',
2032
1541
  'settings.skill':'Skill Evolution',
2033
1542
  'settings.general':'General',
2034
- 'settings.embedding.desc':'Vector embedding model for memory search and retrieval',
2035
- 'settings.summarizer.desc':'LLM for memory summarization, deduplication and analysis',
2036
- 'settings.skill.desc':'Auto-extract reusable skills from conversation patterns',
2037
- 'settings.hub.desc':'Share memories, tasks and skills with your team',
2038
- 'settings.general.desc':'System status, ports and telemetry',
2039
- 'settings.hostproxy.embedding':'Use Gateway Model',
2040
- 'settings.hostproxy.completion':'Use Gateway Model',
2041
- 'settings.hostproxy.skill':'Use Gateway Model',
2042
- 'settings.hostproxy.hint.short':'Reuse gateway model config',
2043
1543
  'settings.provider':'Provider',
2044
1544
  'settings.model':'Model',
2045
1545
  'settings.temperature':'Temperature',
@@ -2048,12 +1548,12 @@ const I18N={
2048
1548
  'settings.skill.confidence':'Min Confidence',
2049
1549
  'settings.skill.minchunks':'Min Chunks',
2050
1550
  'settings.skill.model':'Skill Dedicated Model',
2051
- 'settings.skill.model.hint':'Leave empty to reuse the Summarizer model. Set a dedicated one for higher quality.',
1551
+ 'settings.skill.model.hint':'If not configured, the main Summarizer Model above will be used for skill generation. Configure a dedicated model here for higher quality skill output.',
2052
1552
  'settings.optional':'Optional',
2053
1553
  'settings.skill.usemain':'Use Main Summarizer',
2054
1554
  'settings.telemetry':'Telemetry',
2055
1555
  'settings.telemetry.enabled':'Enable Anonymous Telemetry',
2056
- 'settings.telemetry.hint':'Only collects tool names, latencies and version info. No memory content or personal data.',
1556
+ 'settings.telemetry.hint':'Anonymous usage analytics to help improve the plugin. Only sends tool names, latencies, and version info. No memory content, queries, or personal data is ever sent.',
2057
1557
  'settings.viewerport':'Viewer Port',
2058
1558
  'settings.viewerport.hint':'Requires restart to take effect',
2059
1559
  'settings.test':'Test Connection',
@@ -2064,7 +1564,7 @@ const I18N={
2064
1564
  'settings.save':'Save Settings',
2065
1565
  'settings.reset':'Reset',
2066
1566
  'settings.saved':'Saved',
2067
- 'settings.restart.hint':'Some changes require restarting the OpenClaw gateway to take effect. After restarting, please refresh this page.',
1567
+ 'settings.restart.hint':'Some changes require restarting the OpenClaw gateway to take effect.',
2068
1568
  'settings.save.fail':'Failed to save settings',
2069
1569
  'settings.save.emb.required':'Embedding model is required. Please configure an embedding model before saving.',
2070
1570
  'settings.save.emb.fail':'Embedding model test failed, cannot save',
@@ -2150,7 +1650,7 @@ const I18N={
2150
1650
  'skills.related':'Related Tasks',
2151
1651
  'skills.download':'\u2B07 Download',
2152
1652
  'skills.installed.badge':'Installed',
2153
- 'skills.empty':'No skills yet. Skills are auto-generated from completed tasks with reusable patterns.',
1653
+ 'skills.empty':'No skills yet. Skills are automatically generated from completed tasks that contain reusable experience.',
2154
1654
  'skills.loading':'Loading...',
2155
1655
  'skills.error':'Error loading skill',
2156
1656
  'skills.error.detail':'Failed to load skill: ',
@@ -2171,222 +1671,6 @@ const I18N={
2171
1671
  'tasks.error':'Error',
2172
1672
  'tasks.error.detail':'Failed to load task details',
2173
1673
  'tasks.untitled.related':'Untitled',
2174
- 'tab.admin':'\u{1F6E1} Admin',
2175
- 'settings.hub':'Hub & Team',
2176
- 'settings.hub.enable':'Enable Hub Sharing',
2177
- 'settings.hub.enable.hint':'When off, everything works locally as usual.',
2178
- 'settings.hub.enable.label':'Enable Hub Sharing',
2179
- 'settings.hub.role':'Role',
2180
- 'settings.hub.role.hub':'Hub (Server)',
2181
- 'settings.hub.role.client':'Client (Connect to Hub)',
2182
- 'settings.hub.role.hint':'Hub = run the server; Client = connect to one. These two modes are mutually exclusive.',
2183
- 'settings.hub.port':'Hub Port',
2184
- 'settings.hub.port.hint':'Port for Hub service. Default: 18800',
2185
- 'settings.hub.teamName':'Team Name',
2186
- 'settings.hub.teamName.hint':'Display name for your team',
2187
- 'settings.hub.teamToken':'Team Token',
2188
- 'settings.hub.teamToken.hint':'Auto-generated secret for clients to join. Click to copy. Share this with your team members.',
2189
- 'settings.hub.tokenCopied':'Team Token copied!',
2190
- 'settings.hub.hubSteps.title':'Quick Setup (3 steps)',
2191
- 'settings.hub.hubSteps.s1':'Fill in Team Name below (or keep default)',
2192
- 'settings.hub.hubSteps.s2':'Click "Save Settings", then restart OpenClaw gateway',
2193
- 'settings.hub.hubSteps.s3':'Share the Hub Address and Team Token below with your team members',
2194
- 'settings.hub.clientSteps.title':'Quick Setup (3 steps)',
2195
- 'settings.hub.clientSteps.s1':'Ask your Hub admin for Hub Address and Team Token',
2196
- 'settings.hub.clientSteps.s2':'Fill them in below, click "Test Connection" to verify',
2197
- 'settings.hub.clientSteps.s3':'Click "Save Settings", restart OpenClaw gateway, then refresh this page',
2198
- 'settings.hub.shareInfo.title':'Share this info with your team members:',
2199
- 'settings.hub.shareInfo.yourIP':'your-IP',
2200
- 'settings.hub.shareInfo.clickCopy':'Click to copy',
2201
- 'settings.hub.restartAlert':'Hub sharing config saved! Please restart the OpenClaw gateway for changes to take effect, then refresh this page.\\n\\nRun: openclaw gateway stop && openclaw gateway start',
2202
- 'settings.hub.hubAddress':'Hub Address',
2203
- 'settings.hub.hubAddress.hint':'Hub server address, e.g. 192.168.1.100:18800',
2204
- 'settings.hub.teamTokenClient':'Team Token',
2205
- 'settings.hub.teamTokenClient.hint':'Get this from your Hub admin to join the team',
2206
- 'settings.hub.userToken':'User Token',
2207
- 'settings.hub.userToken.hint':'Usually auto-obtained after joining. Only fill if given by admin.',
2208
- 'settings.hub.testConnection':'Test Connection',
2209
- 'settings.hub.test.noAddr':'Please enter Hub address first',
2210
- 'settings.hub.test.testing':'Testing...',
2211
- 'settings.hub.test.ok':'Connected successfully',
2212
- 'settings.hub.test.fail':'Connection failed',
2213
- 'settings.hub.connection':'Connection Status',
2214
- 'settings.hub.team':'Team & Groups',
2215
- 'settings.hub.adminPending':'Admin Pending Users',
2216
- 'sidebar.hub':'\u{1F310} Team Sharing',
2217
- 'sharing.sidebar.connected':'Connected',
2218
- 'sharing.sidebar.disconnected':'Disconnected',
2219
- 'sharing.sidebar.pending':'Pending Approval',
2220
- 'sharing.sidebar.rejected':'Rejected',
2221
- 'sharing.sidebar.starting':'Starting...',
2222
- 'sharing.sidebar.notConfigured':'Not configured',
2223
- 'sharing.sidebar.identity':'Identity:',
2224
- 'sharing.sidebar.admin':'Admin',
2225
- 'sharing.sidebar.targetHub':'Target Hub:',
2226
- 'sharing.pendingApproval.hint':'Your join request has been submitted. Please wait for the Hub admin to approve.',
2227
- 'sharing.rejected.hint':'Your join request was rejected by the Hub admin. Please contact the admin or retry.',
2228
- 'sharing.retryJoin':'Retry Join',
2229
- 'sharing.retryJoin.hint':'Clears local data and re-submits the join request',
2230
- 'sharing.retryJoin.confirm':'This will clear your current connection and re-submit a join request. Continue?',
2231
- 'sharing.retryJoin.success':'Join request re-submitted. Waiting for admin approval.',
2232
- 'sharing.retryJoin.fail':'Failed to retry join',
2233
- 'sharing.cannotJoinSelf':'Cannot join your own Hub. Please enter a remote Hub address.',
2234
- 'scope.hub':'Hub',
2235
- 'memory.detail.title':'Memory Detail',
2236
- 'memory.detail.loading':'Loading...',
2237
- 'memory.detail.notFound':'Memory not found',
2238
- 'memory.detail.copyId':'Click to copy ID',
2239
- 'memory.detail.created':'Created ',
2240
- 'memory.detail.updated':'Updated ',
2241
- 'memory.detail.viewTarget':'View target: ',
2242
- 'admin.title':'Hub Admin Panel',
2243
- 'admin.subtitle':'Manage team members and shared resources',
2244
- 'admin.refresh':'\u21BB Refresh',
2245
- 'admin.tab.users':'Users',
2246
- 'admin.tab.groups':'Groups',
2247
- 'admin.tab.memories':'Shared Tasks',
2248
- 'admin.tab.skills':'Shared Skills',
2249
- 'admin.stat.activeUsers':'Active Users',
2250
- 'admin.stat.pending':'Pending',
2251
- 'admin.stat.groups':'Groups',
2252
- 'admin.stat.sharedTasks':'Shared Tasks',
2253
- 'admin.stat.sharedSkills':'Shared Skills',
2254
- 'admin.stat.sharedMemories':'Shared Memories',
2255
- 'admin.pendingApproval':'Pending Approval',
2256
- 'admin.activeUsers':'Active Users',
2257
- 'admin.noActiveUsers':'No active users.',
2258
- 'admin.approve':'Approve',
2259
- 'admin.reject':'Reject',
2260
- 'admin.device':'Device: ',
2261
- 'admin.groups':'Groups',
2262
- 'admin.newGroup':'+ New Group',
2263
- 'admin.groupName':'Group name',
2264
- 'admin.groupDesc':'Description (optional)',
2265
- 'admin.create':'Create',
2266
- 'admin.cancel':'Cancel',
2267
- 'admin.delete':'Delete',
2268
- 'admin.members':'Members',
2269
- 'admin.noGroups':'No groups created yet.',
2270
- 'admin.noMembers':'No members.',
2271
- 'admin.add':'Add',
2272
- 'admin.remove':'Remove',
2273
- 'admin.sharedTasks':'Shared Tasks',
2274
- 'admin.noSharedTasks':'No shared tasks on Hub.',
2275
- 'admin.owner':'Owner: ',
2276
- 'admin.group':'Group: ',
2277
- 'admin.chunks':'Chunks: ',
2278
- 'admin.updated':'Updated: ',
2279
- 'admin.sharedSkills':'Shared Skills',
2280
- 'admin.noSharedSkills':'No shared skills on Hub.',
2281
- 'admin.sharedMemories':'Shared Memories',
2282
- 'admin.noSharedMemories':'No shared memories on Hub.',
2283
- 'admin.tab.sharedMemories':'Shared Memories',
2284
- 'admin.version':'v',
2285
- 'admin.quality':'Quality: ',
2286
- 'admin.membersCount':'Members ({n}):',
2287
- 'admin.noMembersYet':'No members yet.',
2288
- 'admin.loadFailed':'Failed to load admin data: ',
2289
- 'admin.noPermission':'You do not have admin permissions to access this panel.',
2290
- 'admin.groupsFailed':'Failed to load groups: ',
2291
- 'toast.userApproved':'User approved',
2292
- 'toast.userRejected':'User rejected',
2293
- 'toast.approveFail':'Approve failed',
2294
- 'toast.rejectFail':'Reject failed',
2295
- 'toast.groupCreated':'Group created',
2296
- 'toast.groupDeleted':'Group deleted',
2297
- 'toast.memberAdded':'Member added',
2298
- 'toast.memberRemoved':'Member removed',
2299
- 'toast.taskRemoved':'Task removed',
2300
- 'toast.skillRemoved':'Skill removed',
2301
- 'toast.memoryRemoved':'Memory removed',
2302
- 'toast.createFail':'Create failed',
2303
- 'toast.deleteFail':'Delete failed',
2304
- 'toast.addFail':'Add failed',
2305
- 'toast.removeFail':'Remove failed',
2306
- 'toast.groupNameRequired':'Group name is required',
2307
- 'confirm.rejectUser':'Reject this user?',
2308
- 'confirm.removeGroupMember':'Remove this member from the group?',
2309
- 'confirm.removeMember':'Remove this member?',
2310
- 'confirm.deleteGroup':'Delete group "{name}"? Members will be removed.',
2311
- 'confirm.deleteGroupShort':'Delete group "{name}"?',
2312
- 'confirm.removeTask':'Remove shared task "{name}" from Hub? This cannot be undone.',
2313
- 'confirm.removeSkill':'Remove shared skill "{name}" from Hub? This cannot be undone.',
2314
- 'confirm.removeMemory':'Remove shared memory "{name}" from Hub? This cannot be undone.',
2315
- 'sharing.disabled':'Sharing disabled',
2316
- 'sharing.disabled.hint':'Enable sharing in plugin config to connect a Hub.',
2317
- 'sharing.hubAdmin':'Hub Admin',
2318
- 'sharing.client':'Client',
2319
- 'sharing.hubMode':'Hub mode',
2320
- 'sharing.hubMode.status':'Status: not connected to self',
2321
- 'sharing.hubMode.hint':'Configure sharing.client with hubAddress and userToken pointing to this Hub to enable admin UI.',
2322
- 'sharing.clientConfigured':'Client configured',
2323
- 'sharing.clientDisconnected':'Status: disconnected',
2324
- 'sharing.clientDisconnected.hint':'Viewer will keep showing local data; Hub actions may fail until the connection is restored.',
2325
- 'sharing.clientNotConfigured':'Client not configured',
2326
- 'sharing.clientNotConfigured.hint':'Set hubAddress and userToken in sharing.client to enable team features.',
2327
- 'sharing.settingsDisabled':'Sharing is disabled.',
2328
- 'sharing.settingsDisabled.hint':'Enable sharing in config to use Hub memory and skill collaboration.',
2329
- 'sharing.noTeam':'No team connection.',
2330
- 'sharing.adminUnavailable':'Admin tools unavailable.',
2331
- 'sharing.adminEnabled':'Admin controls enabled',
2332
- 'sharing.adminPendingHint':'Pending users will appear below.',
2333
- 'sharing.notAdmin':'Current user is not an admin.',
2334
- 'sharing.pendingLoadFail':'Failed to load pending users: ',
2335
- 'sharing.noPending':'No pending users.',
2336
- 'sharing.manageGroups':'Manage Groups',
2337
- 'sharing.openAdmin':'Open Admin Panel',
2338
- 'sharing.saveUsername':'Save',
2339
- 'sharing.username.invalid':'Username must be 2-32 characters',
2340
- 'sharing.username.taken':'Username already taken',
2341
- 'sharing.username.updated':'Username updated',
2342
- 'sharing.username.error':'Failed to update username',
2343
- 'sharing.hubNotConfigured':'Hub is running but client connection is not configured.',
2344
- 'sharing.hubNotConfigured.hint':'Add sharing.client.hubAddress and sharing.client.userToken pointing to this Hub to enable the admin interface.',
2345
- 'sharing.notConnected':'Not connected to Hub.',
2346
- 'sharing.role':'Role:',
2347
- 'sharing.mode':'Mode:',
2348
- 'sharing.role.hub':'Server (hosting the Hub)',
2349
- 'sharing.role.client':'Member (connected to Hub)',
2350
- 'sharing.clientConfiguredLabel':'Client configured:',
2351
- 'sharing.configuredHub':'Configured Hub:',
2352
- 'sharing.connected':'Connected:',
2353
- 'sharing.yes':'yes',
2354
- 'sharing.no':'no',
2355
- 'sharing.user':'User:',
2356
- 'sharing.team':'Team:',
2357
- 'sharing.groups':'Groups:',
2358
- 'sharing.loading':'Loading...',
2359
- 'sharing.loadingGroups':'Loading groups...',
2360
- 'sharing.noGroupsYet':'No groups yet.',
2361
- 'search.localResults':'Local Results',
2362
- 'search.hubResults':'Hub Results',
2363
- 'search.noLocal':'No local results.',
2364
- 'search.noHub':'No Hub results.',
2365
- 'search.viewDetail':'View Detail',
2366
- 'search.sharedMemory':'Shared Memory',
2367
- 'search.loadFailed':'Failed to load shared memory',
2368
- 'share.alreadyShared':'Shared',
2369
- 'share.shareBtn':'Share',
2370
- 'share.updateBtn':'Update',
2371
- 'share.unshareBtn':'Unshare',
2372
- 'toast.taskShared':'Task shared',
2373
- 'toast.taskShareFail':'Task share failed',
2374
- 'toast.taskUnshared':'Task unshared',
2375
- 'toast.taskUnshareFail':'Task unshare failed',
2376
- 'toast.memoryShared':'Memory shared',
2377
- 'toast.memoryShareFail':'Memory share failed',
2378
- 'toast.memoryUnshared':'Memory unshared',
2379
- 'toast.memoryUnshareFail':'Memory unshare failed',
2380
- 'toast.skillShared':'Skill shared',
2381
- 'toast.skillShareFail':'Skill share failed',
2382
- 'toast.skillUnshared':'Skill unshared',
2383
- 'toast.skillUnshareFail':'Skill unshare failed',
2384
- 'share.memoryVisibilityPrompt':'Share visibility (public or group):',
2385
- 'share.memoryUnshareConfirm':'Unshare this memory?',
2386
- 'share.group':'Group',
2387
- 'share.public':'Public',
2388
- 'toast.skillPulled':'Skill pulled to local storage',
2389
- 'toast.skillPullFail':'Skill pull failed',
2390
1674
  'task.edit':'Edit',
2391
1675
  'task.delete':'Delete',
2392
1676
  'task.save':'Save',
@@ -2411,40 +1695,11 @@ const I18N={
2411
1695
  'update.installing':'Installing...',
2412
1696
  'update.success':'Updated!',
2413
1697
  'update.failed':'Update failed',
2414
- 'update.restarting':'Restarting service, page will refresh automatically...',
2415
- 'update.dismiss':'Dismiss',
2416
- 'sharing.disable.confirm.hub':'You are about to shut down the Hub server.\\n\\nWhat will happen:\\n\\u2022 All connected team members will be disconnected\\n\\u2022 They will no longer be able to sync memories, tasks, or skills\\n\\u2022 Shared data is preserved and will be available when you re-enable\\n\\nAre you sure?',
2417
- 'sharing.disable.confirm.client':'You are about to disconnect from the team Hub.\\n\\nWhat will happen:\\n\\u2022 You will no longer receive shared memories, tasks, or skills from the team\\n\\u2022 Your local data is preserved and will not be affected\\n\\u2022 You can reconnect later by re-enabling sharing\\n\\nAre you sure?',
2418
- 'sharing.disable.restartAlert':'Sharing has been disabled. Please restart the OpenClaw gateway for the change to take effect, then refresh this page.\\n\\nRun: openclaw gateway stop && openclaw gateway start',
2419
- 'admin.notEnabled.title':'Team sharing is not enabled',
2420
- 'admin.notEnabled.desc':'The Admin Panel is used to manage team members, shared memories, tasks, and skills. To use this feature, you need to enable Hub sharing first.',
2421
- 'admin.notEnabled.setupHub':'Set Up as Hub Server',
2422
- 'admin.notEnabled.joinTeam':'Join an Existing Team',
2423
- 'admin.notEnabled.hint':'If you have previously configured sharing, your data is still preserved. Re-enabling sharing will restore access to all shared content.',
2424
- 'sharing.disconnected.hint':'Unable to reach the Hub server. The Hub may be offline or the network is unavailable.',
2425
- 'sharing.retryConnection':'Retry Connection',
2426
- 'sharing.retryConnection.loading':'Connecting...',
2427
- 'sharing.retryConnection.success':'Connected successfully!',
2428
- 'sharing.retryConnection.fail':'Still unable to connect. Check if the Hub is online.',
2429
- 'guide.title':'Get Started with Team Collaboration',
2430
- 'guide.subtitle':'MemOS supports team memory sharing. Choose one of the following options to enable collaboration, or continue using local-only mode.',
2431
- 'guide.join.title':'Join a Remote Team',
2432
- 'guide.join.desc':'Your team already has a Hub running? Join it to share memories, tasks and skills with team members.',
2433
- 'guide.join.s1':'Ask your Hub admin for the Hub Address and Team Token',
2434
- 'guide.join.s2':'Go to Settings \u2192 Hub & Team, enable sharing, select "Client" mode',
2435
- 'guide.join.s3':'Fill in Hub Address and Team Token, click "Test Connection"',
2436
- 'guide.join.s4':'Save settings, restart the OpenClaw gateway, then refresh this page',
2437
- 'guide.join.btn':'\u2192 Configure Client Mode',
2438
- 'guide.hub.title':'Start Your Own Hub',
2439
- 'guide.hub.desc':'Be the team server. Run a Hub on this device so others can connect and share memories with you.',
2440
- 'guide.hub.s1':'Go to Settings \u2192 Hub & Team, enable sharing, select "Hub" mode',
2441
- 'guide.hub.s2':'Set a team name, save settings, restart the gateway, then refresh this page',
2442
- 'guide.hub.s3':'Share the Hub Address and Team Token with your team members',
2443
- 'guide.hub.s4':'Approve join requests in the Admin Panel',
2444
- 'guide.hub.btn':'\u2192 Configure Hub Mode'
1698
+ 'update.restarting':'Restarting service...',
1699
+ 'update.dismiss':'Dismiss'
2445
1700
  },
2446
1701
  zh:{
2447
- 'title':'MemOS 记忆',
1702
+ 'title':'OpenClaw 记忆',
2448
1703
  'subtitle':'由 MemOS 驱动',
2449
1704
  'setup.desc':'设置密码以保护你的记忆数据',
2450
1705
  'setup.pw':'输入密码(至少4位)',
@@ -2484,14 +1739,6 @@ const I18N={
2484
1739
  'skills.active':'生效中',
2485
1740
  'skills.installed':'已安装',
2486
1741
  'skills.public':'公开',
2487
- 'skills.search.placeholder':'搜索技能...',
2488
- 'skills.search.local':'本地',
2489
- 'skills.search.noresult':'未找到匹配的技能',
2490
- 'skills.load.error':'加载技能失败',
2491
- 'skills.hub.title':'\u{1F310} Hub 共享技能',
2492
- 'scope.local':'本地',
2493
- 'scope.group':'团队',
2494
- 'scope.all':'全部',
2495
1742
  'skills.visibility.public':'公开',
2496
1743
  'skills.visibility.private':'私有',
2497
1744
  'skills.setPublic':'设为公开',
@@ -2530,7 +1777,6 @@ const I18N={
2530
1777
  'filter.newest':'最新优先',
2531
1778
  'filter.oldest':'最早优先',
2532
1779
  'filter.allowners':'所有归属',
2533
- 'filter.allsessions':'全部会话',
2534
1780
  'filter.public':'公开',
2535
1781
  'filter.private':'私有',
2536
1782
  'filter.allvisibility':'所有可见性',
@@ -2611,22 +1857,11 @@ const I18N={
2611
1857
  'tab.import':'\u{1F4E5} 导入',
2612
1858
  'tab.settings':'\u2699 设置',
2613
1859
  'settings.modelconfig':'模型配置',
2614
- 'settings.models':'AI 模型',
2615
- 'settings.models.desc':'配置嵌入模型、摘要模型和技能进化模型',
2616
1860
  'settings.modelhealth':'模型健康',
2617
1861
  'settings.embedding':'嵌入模型',
2618
1862
  'settings.summarizer':'摘要模型',
2619
1863
  'settings.skill':'技能进化',
2620
1864
  'settings.general':'通用设置',
2621
- 'settings.embedding.desc':'向量嵌入模型,用于记忆检索和语义搜索',
2622
- 'settings.summarizer.desc':'大语言模型,用于记忆摘要、去重和分析',
2623
- 'settings.skill.desc':'从对话模式中自动提取可复用的技能',
2624
- 'settings.hub.desc':'与团队共享记忆、任务和技能',
2625
- 'settings.general.desc':'系统状态、端口和数据统计',
2626
- 'settings.hostproxy.embedding':'复用网关模型',
2627
- 'settings.hostproxy.completion':'复用网关模型',
2628
- 'settings.hostproxy.skill':'复用网关模型',
2629
- 'settings.hostproxy.hint.short':'复用网关已有的模型配置',
2630
1865
  'settings.provider':'服务商',
2631
1866
  'settings.model':'模型',
2632
1867
  'settings.temperature':'温度',
@@ -2635,12 +1870,12 @@ const I18N={
2635
1870
  'settings.skill.confidence':'最低置信度',
2636
1871
  'settings.skill.minchunks':'最少记忆片段',
2637
1872
  'settings.skill.model':'技能专用模型',
2638
- 'settings.skill.model.hint':'不配置则复用摘要模型。如需更高质量可单独指定。',
1873
+ 'settings.skill.model.hint':'不配置时默认使用上方的摘要模型进行技能生成。如需更高质量的技能输出,可在此单独配置一个更强的模型。',
2639
1874
  'settings.optional':'可选',
2640
1875
  'settings.skill.usemain':'使用主摘要模型',
2641
1876
  'settings.telemetry':'数据统计',
2642
1877
  'settings.telemetry.enabled':'启用匿名数据统计',
2643
- 'settings.telemetry.hint':'仅收集工具名称、响应时间和版本号,不涉及任何记忆内容或个人数据。',
1878
+ 'settings.telemetry.hint':'匿名使用统计,帮助改进插件。仅发送工具名称、响应时间和版本信息,不会发送任何记忆内容、搜索查询或个人数据。',
2644
1879
  'settings.viewerport':'Viewer 端口',
2645
1880
  'settings.viewerport.hint':'修改后需重启网关生效',
2646
1881
  'settings.test':'测试连接',
@@ -2651,7 +1886,7 @@ const I18N={
2651
1886
  'settings.save':'保存设置',
2652
1887
  'settings.reset':'重置',
2653
1888
  'settings.saved':'已保存',
2654
- 'settings.restart.hint':'部分设置修改后需要重启 OpenClaw 网关才能生效。重启后请刷新此页面。',
1889
+ 'settings.restart.hint':'部分设置修改后需要重启 OpenClaw 网关才能生效。',
2655
1890
  'settings.save.fail':'保存设置失败',
2656
1891
  'settings.save.emb.required':'嵌入模型为必填项,请先配置嵌入模型再保存。',
2657
1892
  'settings.save.emb.fail':'嵌入模型测试失败,无法保存',
@@ -2737,7 +1972,7 @@ const I18N={
2737
1972
  'skills.related':'关联任务',
2738
1973
  'skills.download':'\u2B07 下载',
2739
1974
  'skills.installed.badge':'已安装',
2740
- 'skills.empty':'暂无技能。技能会从已完成的任务中自动提炼生成。',
1975
+ 'skills.empty':'暂无技能。技能会从已完成的、包含可复用经验的任务中自动生成。',
2741
1976
  'skills.loading':'加载中...',
2742
1977
  'skills.error':'加载技能失败',
2743
1978
  'skills.error.detail':'加载技能失败:',
@@ -2758,222 +1993,6 @@ const I18N={
2758
1993
  'tasks.error':'出错了',
2759
1994
  'tasks.error.detail':'加载任务详情失败',
2760
1995
  'tasks.untitled.related':'未命名',
2761
- 'tab.admin':'\u{1F6E1} 管理',
2762
- 'settings.hub':'Hub 与团队',
2763
- 'settings.hub.enable':'启用 Hub 共享',
2764
- 'settings.hub.enable.hint':'关闭时仅本地使用,不影响其他功能。',
2765
- 'settings.hub.enable.label':'启用 Hub 共享',
2766
- 'settings.hub.role':'角色',
2767
- 'settings.hub.role.hub':'Hub(服务端)',
2768
- 'settings.hub.role.client':'Client(连接到 Hub)',
2769
- 'settings.hub.role.hint':'Hub = 本机做服务端;Client = 连接别人的服务端。两种模式互斥,不能同时使用。',
2770
- 'settings.hub.port':'Hub 端口',
2771
- 'settings.hub.port.hint':'Hub 服务端口,默认 18800',
2772
- 'settings.hub.teamName':'团队名称',
2773
- 'settings.hub.teamName.hint':'你的团队显示名称',
2774
- 'settings.hub.teamToken':'团队令牌',
2775
- 'settings.hub.teamToken.hint':'自动生成的密钥,点击可复制。请将此令牌分享给团队成员。',
2776
- 'settings.hub.tokenCopied':'团队令牌已复制!',
2777
- 'settings.hub.hubSteps.title':'快速配置(3 步)',
2778
- 'settings.hub.hubSteps.s1':'填写下方团队名称(或保持默认)',
2779
- 'settings.hub.hubSteps.s2':'点击"保存设置",然后重启 OpenClaw 网关',
2780
- 'settings.hub.hubSteps.s3':'将下方的 Hub 地址和团队令牌分享给团队成员',
2781
- 'settings.hub.clientSteps.title':'快速配置(3 步)',
2782
- 'settings.hub.clientSteps.s1':'向 Hub 管理员获取 Hub 地址和团队令牌',
2783
- 'settings.hub.clientSteps.s2':'填入下方,点击"测试连接"验证连通性',
2784
- 'settings.hub.clientSteps.s3':'点击「保存设置」,重启 OpenClaw 网关,然后刷新此页面',
2785
- 'settings.hub.shareInfo.title':'请将以下信息分享给团队成员:',
2786
- 'settings.hub.shareInfo.yourIP':'你的IP',
2787
- 'settings.hub.shareInfo.clickCopy':'点击复制',
2788
- 'settings.hub.restartAlert':'Hub 共享配置已保存!请重启 OpenClaw 网关使配置生效,重启后请刷新此页面。\\n\\n执行命令:openclaw gateway stop && openclaw gateway start',
2789
- 'settings.hub.hubAddress':'Hub 地址',
2790
- 'settings.hub.hubAddress.hint':'Hub 服务器地址,如 192.168.1.100:18800',
2791
- 'settings.hub.teamTokenClient':'团队令牌',
2792
- 'settings.hub.teamTokenClient.hint':'向 Hub 管理员获取此令牌以加入团队',
2793
- 'settings.hub.userToken':'用户令牌',
2794
- 'settings.hub.userToken.hint':'通常在加入团队后自动获取,无需手动填写。',
2795
- 'settings.hub.testConnection':'测试连接',
2796
- 'settings.hub.test.noAddr':'请先输入 Hub 地址',
2797
- 'settings.hub.test.testing':'测试中...',
2798
- 'settings.hub.test.ok':'连接成功',
2799
- 'settings.hub.test.fail':'连接失败',
2800
- 'settings.hub.connection':'连接状态',
2801
- 'settings.hub.team':'团队与分组',
2802
- 'settings.hub.adminPending':'管理员待审用户',
2803
- 'sidebar.hub':'\u{1F310} 团队共享',
2804
- 'sharing.sidebar.connected':'已连接',
2805
- 'sharing.sidebar.disconnected':'已断开',
2806
- 'sharing.sidebar.pending':'等待审核',
2807
- 'sharing.sidebar.rejected':'已拒绝',
2808
- 'sharing.sidebar.starting':'启动中...',
2809
- 'sharing.sidebar.notConfigured':'未配置',
2810
- 'sharing.sidebar.identity':'身份:',
2811
- 'sharing.sidebar.admin':'管理员',
2812
- 'sharing.sidebar.targetHub':'目标 Hub:',
2813
- 'sharing.pendingApproval.hint':'加入申请已提交,请等待 Hub 管理员审核通过。',
2814
- 'sharing.rejected.hint':'您的加入申请已被 Hub 管理员拒绝,请联系管理员或重新申请。',
2815
- 'sharing.retryJoin':'重新申请',
2816
- 'sharing.retryJoin.hint':'清除本地连接数据并重新提交加入申请',
2817
- 'sharing.retryJoin.confirm':'这将清除当前连接数据并重新提交加入申请,是否继续?',
2818
- 'sharing.retryJoin.success':'加入申请已重新提交,请等待管理员审核。',
2819
- 'sharing.retryJoin.fail':'重新申请失败',
2820
- 'sharing.cannotJoinSelf':'不能加入自己的 Hub,请输入远程 Hub 地址。',
2821
- 'scope.hub':'Hub',
2822
- 'memory.detail.title':'记忆详情',
2823
- 'memory.detail.loading':'加载中...',
2824
- 'memory.detail.notFound':'未找到该记忆',
2825
- 'memory.detail.copyId':'点击复制 ID',
2826
- 'memory.detail.created':'创建于 ',
2827
- 'memory.detail.updated':'更新于 ',
2828
- 'memory.detail.viewTarget':'查看目标: ',
2829
- 'admin.title':'Hub 管理面板',
2830
- 'admin.subtitle':'管理团队成员和共享资源',
2831
- 'admin.refresh':'\u21BB 刷新',
2832
- 'admin.tab.users':'用户',
2833
- 'admin.tab.groups':'分组',
2834
- 'admin.tab.memories':'共享任务',
2835
- 'admin.tab.skills':'共享技能',
2836
- 'admin.stat.activeUsers':'活跃用户',
2837
- 'admin.stat.pending':'待审核',
2838
- 'admin.stat.groups':'分组',
2839
- 'admin.stat.sharedTasks':'共享任务',
2840
- 'admin.stat.sharedSkills':'共享技能',
2841
- 'admin.stat.sharedMemories':'共享记忆',
2842
- 'admin.pendingApproval':'待审批',
2843
- 'admin.activeUsers':'活跃用户',
2844
- 'admin.noActiveUsers':'暂无活跃用户。',
2845
- 'admin.approve':'批准',
2846
- 'admin.reject':'拒绝',
2847
- 'admin.device':'设备:',
2848
- 'admin.groups':'分组',
2849
- 'admin.newGroup':'+ 新建分组',
2850
- 'admin.groupName':'分组名称',
2851
- 'admin.groupDesc':'描述(可选)',
2852
- 'admin.create':'创建',
2853
- 'admin.cancel':'取消',
2854
- 'admin.delete':'删除',
2855
- 'admin.members':'成员',
2856
- 'admin.noGroups':'暂无分组。',
2857
- 'admin.noMembers':'暂无成员。',
2858
- 'admin.add':'添加',
2859
- 'admin.remove':'移除',
2860
- 'admin.sharedTasks':'共享任务',
2861
- 'admin.noSharedTasks':'Hub 上暂无共享任务。',
2862
- 'admin.owner':'归属:',
2863
- 'admin.group':'分组:',
2864
- 'admin.chunks':'记忆片段:',
2865
- 'admin.updated':'更新于:',
2866
- 'admin.sharedSkills':'共享技能',
2867
- 'admin.noSharedSkills':'Hub 上暂无共享技能。',
2868
- 'admin.sharedMemories':'共享记忆',
2869
- 'admin.noSharedMemories':'Hub 上暂无共享记忆。',
2870
- 'admin.tab.sharedMemories':'共享记忆',
2871
- 'admin.version':'v',
2872
- 'admin.quality':'质量:',
2873
- 'admin.membersCount':'成员({n}):',
2874
- 'admin.noMembersYet':'暂无成员。',
2875
- 'admin.loadFailed':'加载管理数据失败:',
2876
- 'admin.noPermission':'您没有管理员权限,无法访问此面板。',
2877
- 'admin.groupsFailed':'加载分组失败:',
2878
- 'toast.userApproved':'用户已批准',
2879
- 'toast.userRejected':'用户已拒绝',
2880
- 'toast.approveFail':'批准失败',
2881
- 'toast.rejectFail':'拒绝失败',
2882
- 'toast.groupCreated':'分组已创建',
2883
- 'toast.groupDeleted':'分组已删除',
2884
- 'toast.memberAdded':'成员已添加',
2885
- 'toast.memberRemoved':'成员已移除',
2886
- 'toast.taskRemoved':'任务已移除',
2887
- 'toast.skillRemoved':'技能已移除',
2888
- 'toast.memoryRemoved':'记忆已移除',
2889
- 'toast.createFail':'创建失败',
2890
- 'toast.deleteFail':'删除失败',
2891
- 'toast.addFail':'添加失败',
2892
- 'toast.removeFail':'移除失败',
2893
- 'toast.groupNameRequired':'请输入分组名称',
2894
- 'confirm.rejectUser':'确定要拒绝此用户吗?',
2895
- 'confirm.removeGroupMember':'确定要将此成员移出分组吗?',
2896
- 'confirm.removeMember':'确定要移除此成员吗?',
2897
- 'confirm.deleteGroup':'确定要删除分组「{name}」吗?成员将被移除。',
2898
- 'confirm.deleteGroupShort':'确定要删除分组「{name}」吗?',
2899
- 'confirm.removeTask':'确定要从 Hub 移除共享任务「{name}」吗?此操作不可撤销。',
2900
- 'confirm.removeSkill':'确定要从 Hub 移除共享技能「{name}」吗?此操作不可撤销。',
2901
- 'confirm.removeMemory':'确定要从 Hub 移除共享记忆「{name}」吗?此操作不可撤销。',
2902
- 'sharing.disabled':'共享已禁用',
2903
- 'sharing.disabled.hint':'在插件配置中启用共享以连接 Hub。',
2904
- 'sharing.hubAdmin':'Hub 管理员',
2905
- 'sharing.client':'客户端',
2906
- 'sharing.hubMode':'Hub 模式',
2907
- 'sharing.hubMode.status':'状态:未连接到自身',
2908
- 'sharing.hubMode.hint':'配置 sharing.client 的 hubAddress 和 userToken 指向此 Hub 以启用管理界面。',
2909
- 'sharing.clientConfigured':'客户端已配置',
2910
- 'sharing.clientDisconnected':'状态:已断开',
2911
- 'sharing.clientDisconnected.hint':'查看器将继续显示本地数据;Hub 操作可能在连接恢复前失败。',
2912
- 'sharing.clientNotConfigured':'客户端未配置',
2913
- 'sharing.clientNotConfigured.hint':'设置 sharing.client 中的 hubAddress 和 userToken 以启用团队功能。',
2914
- 'sharing.settingsDisabled':'共享已禁用。',
2915
- 'sharing.settingsDisabled.hint':'在配置中启用共享以使用 Hub 记忆和技能协作。',
2916
- 'sharing.noTeam':'无团队连接。',
2917
- 'sharing.adminUnavailable':'管理工具不可用。',
2918
- 'sharing.adminEnabled':'管理控制已启用',
2919
- 'sharing.adminPendingHint':'待审用户将显示在下方。',
2920
- 'sharing.notAdmin':'当前用户不是管理员。',
2921
- 'sharing.pendingLoadFail':'加载待审用户失败:',
2922
- 'sharing.noPending':'暂无待审用户。',
2923
- 'sharing.manageGroups':'管理分组',
2924
- 'sharing.openAdmin':'打开管理面板',
2925
- 'sharing.saveUsername':'保存',
2926
- 'sharing.username.invalid':'用户名需 2-32 个字符',
2927
- 'sharing.username.taken':'用户名已被占用',
2928
- 'sharing.username.updated':'用户名已更新',
2929
- 'sharing.username.error':'更新用户名失败',
2930
- 'sharing.hubNotConfigured':'Hub 正在运行,但客户端连接未配置。',
2931
- 'sharing.hubNotConfigured.hint':'添加 sharing.client.hubAddress 和 sharing.client.userToken 指向此 Hub 以启用管理界面。',
2932
- 'sharing.notConnected':'未连接到 Hub。',
2933
- 'sharing.role':'角色:',
2934
- 'sharing.mode':'身份:',
2935
- 'sharing.role.hub':'服务端(Hub 主机)',
2936
- 'sharing.role.client':'成员(连接到 Hub)',
2937
- 'sharing.clientConfiguredLabel':'客户端已配置:',
2938
- 'sharing.configuredHub':'配置的 Hub:',
2939
- 'sharing.connected':'已连接:',
2940
- 'sharing.yes':'是',
2941
- 'sharing.no':'否',
2942
- 'sharing.user':'用户:',
2943
- 'sharing.team':'团队:',
2944
- 'sharing.groups':'分组:',
2945
- 'sharing.loading':'加载中...',
2946
- 'sharing.loadingGroups':'正在加载分组...',
2947
- 'sharing.noGroupsYet':'暂无分组。',
2948
- 'search.localResults':'本地结果',
2949
- 'search.hubResults':'Hub 结果',
2950
- 'search.noLocal':'无本地结果。',
2951
- 'search.noHub':'无 Hub 结果。',
2952
- 'search.viewDetail':'查看详情',
2953
- 'search.sharedMemory':'共享记忆',
2954
- 'search.loadFailed':'加载共享记忆失败',
2955
- 'share.alreadyShared':'已共享',
2956
- 'share.shareBtn':'共享',
2957
- 'share.updateBtn':'更新共享',
2958
- 'share.unshareBtn':'取消共享',
2959
- 'toast.taskShared':'任务已共享',
2960
- 'toast.taskShareFail':'任务共享失败',
2961
- 'toast.taskUnshared':'任务已取消共享',
2962
- 'toast.taskUnshareFail':'取消共享失败',
2963
- 'toast.memoryShared':'记忆已共享',
2964
- 'toast.memoryShareFail':'记忆共享失败',
2965
- 'toast.memoryUnshared':'记忆已取消共享',
2966
- 'toast.memoryUnshareFail':'记忆取消共享失败',
2967
- 'toast.skillShared':'技能已共享',
2968
- 'toast.skillShareFail':'技能共享失败',
2969
- 'toast.skillUnshared':'技能已取消共享',
2970
- 'toast.skillUnshareFail':'技能取消共享失败',
2971
- 'share.memoryVisibilityPrompt':'共享可见性(public 或 group):',
2972
- 'share.memoryUnshareConfirm':'取消共享此记忆?',
2973
- 'share.group':'团队',
2974
- 'share.public':'公开',
2975
- 'toast.skillPulled':'技能已拉取到本地',
2976
- 'toast.skillPullFail':'技能拉取失败',
2977
1996
  'task.edit':'编辑',
2978
1997
  'task.delete':'删除',
2979
1998
  'task.save':'保存',
@@ -2998,37 +2017,8 @@ const I18N={
2998
2017
  'update.installing':'安装中...',
2999
2018
  'update.success':'更新完成',
3000
2019
  'update.failed':'更新失败',
3001
- 'update.restarting':'正在重启服务,页面将自动刷新...',
3002
- 'update.dismiss':'关闭',
3003
- 'sharing.disable.confirm.hub':'你即将关闭 Hub 服务。\\n\\n关闭后将会:\\n\\u2022 所有已连接的团队成员将断开连接\\n\\u2022 他们将无法继续同步记忆、任务和技能\\n\\u2022 已共享的数据会保留,重新开启后仍可使用\\n\\n确定要关闭吗?',
3004
- 'sharing.disable.confirm.client':'你即将断开与团队 Hub 的连接。\\n\\n断开后将会:\\n\\u2022 你将无法再接收团队共享的记忆、任务和技能\\n\\u2022 你的本地数据不受影响,会完整保留\\n\\u2022 之后可以随时重新开启共享来恢复连接\\n\\n确定要断开吗?',
3005
- 'sharing.disable.restartAlert':'共享已关闭。请重启 OpenClaw 网关使更改生效,重启后请刷新此页面。\\n\\n执行命令:openclaw gateway stop && openclaw gateway start',
3006
- 'admin.notEnabled.title':'团队共享尚未开启',
3007
- 'admin.notEnabled.desc':'管理面板用于管理团队成员、共享的记忆、任务和技能。使用此功能前,需要先开启 Hub 共享。',
3008
- 'admin.notEnabled.setupHub':'配置为 Hub 服务端',
3009
- 'admin.notEnabled.joinTeam':'加入已有团队',
3010
- 'admin.notEnabled.hint':'如果之前配置过共享,你的数据仍然保留。重新开启共享即可恢复访问所有共享内容。',
3011
- 'sharing.disconnected.hint':'无法连接到 Hub 服务器,Hub 可能已下线或网络不可用。',
3012
- 'sharing.retryConnection':'重试连接',
3013
- 'sharing.retryConnection.loading':'连接中...',
3014
- 'sharing.retryConnection.success':'连接成功!',
3015
- 'sharing.retryConnection.fail':'仍然无法连接,请检查 Hub 是否在线。',
3016
- 'guide.title':'开始团队协作',
3017
- 'guide.subtitle':'MemOS 支持团队记忆共享。选择以下方式之一开启协作,或继续使用纯本地模式。',
3018
- 'guide.join.title':'加入远程团队',
3019
- 'guide.join.desc':'你的团队已有 Hub 在运行?加入即可与团队成员共享记忆、任务和技能。',
3020
- 'guide.join.s1':'向 Hub 管理员索取 Hub 地址和 Team Token',
3021
- 'guide.join.s2':'前往「设置 → Hub & Team」,开启共享,选择「Client」模式',
3022
- 'guide.join.s3':'填写 Hub 地址和 Team Token,点击「测试连接」',
3023
- 'guide.join.s4':'保存设置,重启 OpenClaw 网关,然后刷新此页面',
3024
- 'guide.join.btn':'\u2192 配置 Client 模式',
3025
- 'guide.hub.title':'自建 Hub 服务',
3026
- 'guide.hub.desc':'将本机作为团队服务端,让其他成员连接过来共享记忆。',
3027
- 'guide.hub.s1':'前往「设置 → Hub & Team」,开启共享,选择「Hub」模式',
3028
- 'guide.hub.s2':'设置团队名称,保存设置后重启网关,然后刷新此页面',
3029
- 'guide.hub.s3':'将 Hub 地址和 Team Token 分享给团队成员',
3030
- 'guide.hub.s4':'在管理面板中审批加入请求',
3031
- 'guide.hub.btn':'\u2192 配置 Hub 模式'
2020
+ 'update.restarting':'正在重启服务...',
2021
+ 'update.dismiss':'关闭'
3032
2022
  }
3033
2023
  };
3034
2024
  const LANG_KEY='memos-viewer-lang';
@@ -3130,112 +2120,11 @@ async function doReset(){
3130
2120
  else{err.textContent=d.error||t('reset.err.fail')}
3131
2121
  }
3132
2122
 
3133
- var _sharingRole='client';
3134
- function _genToken(len){
3135
- var a=new Uint8Array(len||18);crypto.getRandomValues(a);
3136
- return btoa(String.fromCharCode.apply(null,a)).replace(/\\+/g,'-').replace(/\\//g,'_').replace(/=+$/,'');
3137
- }
3138
- function onSharingToggle(){
3139
- var on=document.getElementById('cfgSharingEnabled').checked;
3140
- document.getElementById('sharingConfigPanel').style.display=on?'block':'none';
3141
- if(on) selectSharingRole(_sharingRole);
3142
- }
3143
- function selectSharingRole(role){
3144
- _sharingRole=role;
3145
- document.getElementById('btnRoleHub').className='btn btn-sm'+(role==='hub'?' btn-primary':'');
3146
- document.getElementById('btnRoleClient').className='btn btn-sm'+(role==='client'?' btn-primary':'');
3147
- document.getElementById('hubModeConfig').style.display=role==='hub'?'block':'none';
3148
- document.getElementById('clientModeConfig').style.display=role==='client'?'block':'none';
3149
- var sp=document.getElementById('sharingStatusPanel');
3150
- var tp=document.getElementById('sharingTeamPanel');
3151
- var ap=document.getElementById('sharingAdminPanel');
3152
- if(role==='client'){
3153
- if(sp) sp.style.display='none';
3154
- if(tp) tp.style.display='none';
3155
- if(ap) ap.style.display='none';
3156
- }else{
3157
- if(sp) sp.style.display='';
3158
- if(tp) tp.style.display='';
3159
- if(ap) ap.style.display='';
3160
- }
3161
- if(role==='hub'){
3162
- var tk=document.getElementById('cfgHubTeamToken');
3163
- if(!tk.value.trim()) tk.value=_genToken(18);
3164
- var tn=document.getElementById('cfgHubTeamName');
3165
- if(!tn.value.trim()) tn.value='My Team';
3166
- }
3167
- }
3168
- var _cachedLocalIP='';
3169
- function updateHubShareInfo(){
3170
- var panel=document.getElementById('hubShareInfo');
3171
- var content=document.getElementById('hubShareInfoContent');
3172
- if(!panel||!content) return;
3173
- var tokenEl=document.getElementById('cfgHubTeamToken');
3174
- var token=tokenEl?tokenEl.value.trim():'';
3175
- var port=document.getElementById('cfgHubPort').value.trim()||'18800';
3176
- if(!token||_sharingRole!=='hub'){panel.style.display='none';return;}
3177
- panel.style.display='block';
3178
- var cpStyle='cursor:pointer;background:rgba(99,102,241,.06);border:1px solid rgba(99,102,241,.15);border-radius:6px;padding:4px 10px;font-family:var(--mono);font-size:12px;color:var(--text);transition:all .15s;user-select:all';
3179
- var renderShare=function(ip){
3180
- var addr=ip?(ip+':'+esc(port)):('&lt;'+t('settings.hub.shareInfo.yourIP')+'&gt;:'+esc(port));
3181
- var tip=t('settings.hub.shareInfo.clickCopy');
3182
- content.innerHTML=
3183
- '<span style="font-size:11px;color:var(--text-muted);font-weight:500">Hub '+t('settings.hub.hubAddress')+'</span>'+
3184
- '<span style="'+cpStyle+'" onclick="navigator.clipboard.writeText(this.textContent);toast(&#39;Copied!&#39;,&#39;success&#39;)" title="'+tip+'">'+addr+'</span>'+
3185
- '<span style="font-size:11px;color:var(--text-muted);font-weight:500">Team Token</span>'+
3186
- '<span style="'+cpStyle+'" onclick="navigator.clipboard.writeText(this.textContent);toast(&#39;Copied!&#39;,&#39;success&#39;)" title="'+tip+'">'+esc(token)+'</span>';
3187
- };
3188
- if(_cachedLocalIP){renderShare(_cachedLocalIP);return;}
3189
- renderShare('');
3190
- fetch('/api/local-ips').then(function(r){return r.json()}).then(function(d){
3191
- if(d.ips&&d.ips.length>0){_cachedLocalIP=d.ips[0];renderShare(_cachedLocalIP);}
3192
- }).catch(function(){});
3193
- }
3194
- async function testHubConnection(){
3195
- var btn=document.getElementById('btnTestHubConn');
3196
- var result=document.getElementById('hubConnTestResult');
3197
- var addr=document.getElementById('cfgClientHubAddress').value.trim();
3198
- if(!addr){result.innerHTML='<span style="color:var(--rose)">\u274C '+t('settings.hub.test.noAddr')+'</span>';return;}
3199
- btn.disabled=true;result.innerHTML=t('settings.hub.test.testing');
3200
- try{
3201
- var ipsData=await fetch('/api/local-ips').then(function(r){return r.json();});
3202
- var localAddrs=['127.0.0.1','localhost','0.0.0.0'].concat(ipsData.ips||[]);
3203
- var parsed=new URL(addr.indexOf('://')>-1?addr:'http://'+addr);
3204
- if(localAddrs.indexOf(parsed.hostname)>=0){
3205
- result.innerHTML='<span style="color:var(--rose)">\u274C '+t('sharing.cannotJoinSelf')+'</span>';
3206
- btn.disabled=false;return;
3207
- }
3208
- }catch(e){}
3209
- try{
3210
- var url=addr.match(/^https?:\\/\\//)?addr:'http://'+addr;
3211
- url=url.replace(/\\/+$/,'');
3212
- var r=await fetch('/api/sharing/test-hub',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({hubUrl:url})});
3213
- var d=await r.json();
3214
- if(d.ok){
3215
- result.innerHTML='<span style="color:var(--green)">\u2705 '+t('settings.hub.test.ok')+(d.teamName?' — '+esc(d.teamName):'')+'</span>';
3216
- }else{
3217
- var errMsg=d.error==='cannot_join_self'?t('sharing.cannotJoinSelf'):(d.error||t('settings.hub.test.fail'));
3218
- result.innerHTML='<span style="color:var(--rose)">\u274C '+errMsg+'</span>';
3219
- }
3220
- }catch(e){
3221
- result.innerHTML='<span style="color:var(--rose)">\u274C '+esc(String(e))+'</span>';
3222
- }finally{btn.disabled=false;}
3223
- }
3224
-
3225
2123
  function enterApp(){
3226
2124
  document.getElementById('app').style.display='flex';
3227
2125
  loadAll();
3228
2126
  }
3229
2127
 
3230
- function switchSettingsTab(tab,btn){
3231
- document.querySelectorAll('.settings-tab-btn').forEach(function(b){b.classList.remove('active')});
3232
- if(btn){btn.classList.add('active')}
3233
- else{var b=document.querySelector('.settings-tab-btn[data-tab="'+tab+'"]');if(b)b.classList.add('active')}
3234
- document.querySelectorAll('.settings-card[data-stab]').forEach(function(c){
3235
- if(c.dataset.stab===tab){c.classList.add('stab-active')}else{c.classList.remove('stab-active')}
3236
- });
3237
- }
3238
-
3239
2128
  function switchView(view){
3240
2129
  document.querySelectorAll('.nav-tabs .tab').forEach(t=>t.classList.toggle('active',t.dataset.view===view));
3241
2130
  const feedWrap=document.getElementById('feedWrap');
@@ -3245,7 +2134,6 @@ function switchView(view){
3245
2134
  const logsView=document.getElementById('logsView');
3246
2135
  const settingsView=document.getElementById('settingsView');
3247
2136
  const migrateView=document.getElementById('migrateView');
3248
- const adminView=document.getElementById('adminView');
3249
2137
  const sidebar=document.getElementById('sidebar');
3250
2138
  feedWrap.classList.add('hide');
3251
2139
  analyticsView.classList.remove('show');
@@ -3254,17 +2142,19 @@ function switchView(view){
3254
2142
  logsView.classList.remove('show');
3255
2143
  settingsView.classList.remove('show');
3256
2144
  migrateView.classList.remove('show');
3257
- if(adminView) adminView.classList.remove('show');
3258
- var sessionSection=document.getElementById('sidebarSessionSection');
2145
+ const sessionSection=document.getElementById('sidebarSessionSection');
3259
2146
  if(view==='memories'){
3260
2147
  feedWrap.classList.remove('hide');
3261
- if(sessionSection){sessionSection.style.visibility='';sessionSection.style.pointerEvents='';}
2148
+ sessionSection.style.visibility='';
2149
+ sessionSection.style.pointerEvents='';
3262
2150
  } else if(view==='tasks'||view==='skills'){
3263
- if(sessionSection){sessionSection.style.visibility='hidden';sessionSection.style.pointerEvents='none';}
2151
+ sessionSection.style.visibility='hidden';
2152
+ sessionSection.style.pointerEvents='none';
3264
2153
  if(view==='tasks'){tasksView.classList.add('show');loadTasks();}
3265
2154
  else{skillsView.classList.add('show');loadSkills();}
3266
2155
  } else {
3267
- if(sessionSection){sessionSection.style.visibility='hidden';sessionSection.style.pointerEvents='none';}
2156
+ sessionSection.style.visibility='hidden';
2157
+ sessionSection.style.pointerEvents='none';
3268
2158
  if(view==='analytics'){
3269
2159
  analyticsView.classList.add('show');
3270
2160
  loadMetrics();
@@ -3278,1142 +2168,104 @@ function switchView(view){
3278
2168
  } else if(view==='import'){
3279
2169
  migrateView.classList.add('show');
3280
2170
  if(!window._migrateRunning) migrateScan(false);
3281
- } else if(view==='admin'){
3282
- if(adminView){adminView.classList.add('show');loadAdminData();}
3283
2171
  }
3284
2172
  }
3285
2173
  }
3286
2174
 
3287
- function onMemoryScopeChange(){
3288
- memorySearchScope=document.getElementById('memorySearchScope')?.value||'local';
3289
- if(document.getElementById('searchInput').value.trim()) doSearch(document.getElementById('searchInput').value);
3290
- else if(memorySearchScope!=='local') { document.getElementById('sharingSearchMeta').textContent=''; loadHubMemories(); }
3291
- else { document.getElementById('sharingSearchMeta').textContent=''; loadMemories(); }
3292
- }
3293
-
3294
- function onSkillScopeChange(){
3295
- skillSearchScope=document.getElementById('skillSearchScope')?.value||'local';
3296
- loadSkills();
3297
- }
3298
-
3299
- function onTaskScopeChange(){
3300
- taskSearchScope=document.getElementById('taskSearchScope')?.value||'local';
3301
- tasksPage=0;
3302
- loadTasks();
3303
- }
3304
-
3305
- async function loadSharingStatus(forcePending){
2175
+ // ─── Logs ───
2176
+ let logAutoTimer=null;
2177
+ let logPage=1;
2178
+ const LOG_PAGE_SIZE=20;
2179
+ async function loadLogs(page){
2180
+ if(typeof page==='number') logPage=page;
3306
2181
  try{
3307
- const r=await fetch('/api/sharing/status');
3308
- const d=await r.json();
3309
- sharingStatusCache=d;
3310
- renderSharingSidebar(d);
3311
- renderSharingSettings(d);
3312
- updateTeamGuide(d);
3313
- if(forcePending && d && d.admin && d.admin.canManageUsers) loadSharingPendingUsers();
3314
- }catch(e){
3315
- renderSharingSidebar(null);
3316
- renderSharingSettings(null);
3317
- updateTeamGuide(null);
3318
- }
2182
+ const toolFilter=document.getElementById('logToolFilter').value;
2183
+ const offset=(logPage-1)*LOG_PAGE_SIZE;
2184
+ const url='/api/logs?limit='+LOG_PAGE_SIZE+'&offset='+offset+(toolFilter?'&tool='+encodeURIComponent(toolFilter):'');
2185
+ const [logsRes,toolsRes]=await Promise.all([fetch(url),fetch('/api/log-tools')]);
2186
+ if(!logsRes.ok) return;
2187
+ const logsData=await logsRes.json();
2188
+ const toolsData=await toolsRes.json();
2189
+ renderLogToolFilter(toolsData.tools||[],toolFilter);
2190
+ renderLogs(logsData.logs||[]);
2191
+ renderLogPagination(logsData.page||1,logsData.totalPages||1,logsData.total||0);
2192
+ startLogAutoRefresh();
2193
+ }catch(e){console.error('loadLogs',e)}
3319
2194
  }
3320
-
3321
- function renderSharingSidebar(data){
3322
- var section=document.getElementById('sidebarSharingSection');
3323
- var statusEl=document.getElementById('sharingSidebarStatus');
3324
- var hintEl=document.getElementById('sharingSidebarHint');
3325
- var badgeEl=document.getElementById('sharingSidebarConnBadge');
3326
- if(!statusEl||!hintEl) return;
3327
- if(!data||!data.enabled){
3328
- if(section) section.style.display='none';
3329
- window._isHubAdmin=false;
3330
- return;
3331
- }
3332
- if(section) section.style.display='block';
3333
- var conn=data.connection||{};
3334
- function setBadge(color,text,glow){
3335
- if(!badgeEl)return;
3336
- badgeEl.innerHTML='<span style="display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:9999px;background:'+color+'15;color:'+color+'"><span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:'+color+(glow?';box-shadow:0 0 4px '+color:'')+'"></span>'+esc(text)+'</span>';
3337
- }
3338
- if(data.role==='hub'){
3339
- setBadge('#34d399',t('sharing.sidebar.connected'),true);
3340
- statusEl.innerHTML='';
3341
- hintEl.textContent='';
3342
- }else if(conn.pendingApproval&&conn.user){
3343
- setBadge('#fbbf24',t('sharing.sidebar.pending'),false);
3344
- var html='<div class="info-grid">';
3345
- html+='<span class="label">'+t('sharing.sidebar.identity')+'</span><span class="value">'+esc(conn.user.username)+'</span>';
3346
- if(conn.teamName) html+='<span class="label">'+t('sharing.team')+'</span><span class="value">'+esc(conn.teamName)+'</span>';
3347
- html+='</div>';
3348
- statusEl.innerHTML=html;
3349
- hintEl.textContent=t('sharing.pendingApproval.hint');
3350
- }else if(conn.rejected&&conn.user){
3351
- setBadge('#ef4444',t('sharing.sidebar.rejected'),false);
3352
- var html='<div class="info-grid">';
3353
- html+='<span class="label">'+t('sharing.sidebar.identity')+'</span><span class="value">'+esc(conn.user.username||'-')+'</span>';
3354
- if(conn.teamName) html+='<span class="label">'+t('sharing.team')+'</span><span class="value">'+esc(conn.teamName)+'</span>';
3355
- html+='</div>';
3356
- statusEl.innerHTML=html;
3357
- hintEl.textContent=t('sharing.rejected.hint');
3358
- }else if(conn.connected&&conn.user){
3359
- var isAdmin=conn.user.role==='admin';
3360
- setBadge('#34d399',t('sharing.sidebar.connected'),true);
3361
- var html='<div class="info-grid">';
3362
- html+='<span class="label">'+t('sharing.sidebar.identity')+'</span><span class="value">'+esc(conn.user.username)+(isAdmin?' <span class="role-badge admin">'+t('sharing.sidebar.admin')+'</span>':'')+'</span>';
3363
- html+='<span class="label">'+t('sharing.team')+'</span><span class="value">'+esc(conn.teamName||'-')+'</span>';
3364
- html+='</div>';
3365
- statusEl.innerHTML=html;
3366
- hintEl.innerHTML='';
3367
- }else if(data.clientConfigured){
3368
- setBadge('#ef4444',t('sharing.sidebar.disconnected'),false);
3369
- statusEl.innerHTML='<div style="font-size:11px;color:var(--text-muted)">'+t('sharing.sidebar.targetHub')+' '+esc(data.hubUrl||'')+'</div>';
3370
- hintEl.innerHTML=esc(t('sharing.clientDisconnected.hint'))+'<br><a href="#" onclick="retryConnection();return false;" style="color:var(--pri);font-size:11px;text-decoration:none">'+t('sharing.retryConnection')+'</a>';
3371
- }else{
3372
- setBadge('#888',t('sharing.sidebar.notConfigured'),false);
3373
- statusEl.innerHTML='';
3374
- hintEl.textContent=t('sharing.clientNotConfigured.hint');
2195
+ function onLogFilterChange(){logPage=1;loadLogs(1);}
2196
+ function renderLogPagination(page,totalPages,total){
2197
+ const el=document.getElementById('logsPagination');
2198
+ if(!el||totalPages<=1){if(el)el.innerHTML='';return;}
2199
+ const pages=[];
2200
+ const range=2;
2201
+ for(let i=1;i<=totalPages;i++){
2202
+ if(i===1||i===totalPages||Math.abs(i-page)<=range){
2203
+ pages.push(i);
2204
+ }else if(pages[pages.length-1]!=='...'){
2205
+ pages.push('...');
2206
+ }
3375
2207
  }
2208
+ let html='<div class="logs-pagination">';
2209
+ html+='<button class="btn btn-sm btn-ghost" '+(page<=1?'disabled':'')+' onclick="loadLogs('+(page-1)+')">\u2039</button>';
2210
+ pages.forEach(p=>{
2211
+ if(p==='...'){html+='<span class="page-ellipsis">\u2026</span>';}
2212
+ else{html+='<button class="btn btn-sm '+(p===page?'btn-primary':'btn-ghost')+'" onclick="loadLogs('+p+')">'+p+'</button>';}
2213
+ });
2214
+ html+='<button class="btn btn-sm btn-ghost" '+(page>=totalPages?'disabled':'')+' onclick="loadLogs('+(page+1)+')">\u203A</button>';
2215
+ html+='<span class="page-total">'+total+' total</span>';
2216
+ html+='</div>';
2217
+ el.innerHTML=html;
3376
2218
  }
3377
2219
 
3378
- function renderSharingSettings(data){
3379
- var statusEl=document.getElementById('sharingStatusPanel');
3380
- var teamEl=document.getElementById('sharingTeamPanel');
3381
- var adminEl=document.getElementById('sharingAdminPanel');
3382
- var panelsWrap=document.getElementById('sharingPanelsWrap');
3383
- if(!statusEl||!teamEl||!adminEl) return;
3384
- if(!data||!data.enabled){
3385
- statusEl.innerHTML='';teamEl.innerHTML='';adminEl.innerHTML='';
3386
- if(panelsWrap) panelsWrap.style.display='none';
3387
- return;
3388
- }
3389
- if(panelsWrap) panelsWrap.style.display='';
3390
- var conn=data.connection||{};
3391
- var user=conn.user||{};
3392
- var actualRole=data.role||_sharingRole||'client';
3393
- if(data.role) _sharingRole=data.role;
3394
- var isAdmin=(data.admin&&data.admin.canManageUsers)||(conn.connected&&user.role==='admin')||(actualRole==='hub');
3395
- window._isHubAdmin=isAdmin;
3396
- var hubAdminBtn=document.getElementById('hubAdminEntryBtn');
3397
-
3398
- if(actualRole==='hub'){
3399
- statusEl.innerHTML='';teamEl.innerHTML='';adminEl.innerHTML='';
3400
- if(hubAdminBtn) hubAdminBtn.style.display=isAdmin?'':'none';
3401
- return;
3402
- }
3403
-
3404
- if(actualRole==='client'){
3405
- statusEl.style.display='none';teamEl.style.display='none';adminEl.style.display='none';
3406
- if(hubAdminBtn) hubAdminBtn.style.display='none';
3407
-
3408
- var connBadge;
3409
- if(conn.pendingApproval){
3410
- connBadge='<span class="hic-badge pending"><span class="hic-dot amber"></span>'+t('sharing.sidebar.pending')+'</span>';
3411
- }else if(conn.rejected){
3412
- connBadge='<span class="hic-badge disconnected"><span class="hic-dot red"></span>'+t('sharing.sidebar.rejected')+'</span>';
3413
- }else if(conn.connected){
3414
- connBadge='<span class="hic-badge connected"><span class="hic-dot green"></span>'+t('sharing.sidebar.connected')+'</span>';
3415
- }else{
3416
- connBadge='<span class="hic-badge disconnected"><span class="hic-dot red"></span>'+t('sharing.sidebar.disconnected')+'</span>';
3417
- }
3418
- statusEl.style.display='';
3419
- var sh='<div class="hub-info-card hic-status"><div class="hic-title"><span class="hic-icon">\u{1F517}</span>'+t('settings.hub.connection')+' '+connBadge+'</div><div class="hic-grid">';
3420
- if(conn.pendingApproval&&user.username){
3421
- sh+='<span class="hic-label">'+t('sharing.user')+'</span><span class="hic-value">'+esc(user.username)+'</span>';
3422
- sh+='</div><div class="hic-empty" style="color:#f59e0b">'+t('sharing.pendingApproval.hint')+'</div></div>';
3423
- }else if(conn.rejected){
3424
- if(user.username) sh+='<span class="hic-label">'+t('sharing.user')+'</span><span class="hic-value">'+esc(user.username)+'</span>';
3425
- sh+='</div><div class="hic-empty" style="color:#ef4444">'+t('sharing.rejected.hint')+'</div>'+
3426
- '<div style="margin-top:10px;padding:0 16px 14px"><button class="btn btn-sm btn-primary" onclick="retryHubJoin()">'+t('sharing.retryJoin')+'</button>'+
3427
- '<span style="font-size:11px;color:var(--text-muted);margin-left:8px">'+t('sharing.retryJoin.hint')+'</span></div></div>';
3428
- }else if(conn.connected&&user.username){
3429
- sh+='<span class="hic-label">'+t('sharing.user')+'</span><span class="hic-value" style="display:flex;align-items:center;gap:6px">'+
3430
- '<input type="text" id="hubUsernameInput" value="'+esc(user.username)+'" style="border:1px solid var(--border);border-radius:6px;padding:3px 8px;font-size:12px;background:var(--bg);color:var(--text);width:120px;font-family:inherit" />'+
3431
- '<button class="btn btn-sm" onclick="updateHubUsername()" style="padding:2px 10px;font-size:11px">'+t('sharing.saveUsername')+'</button>'+
3432
- '</span>';
3433
- sh+='<span class="hic-label">'+t('sharing.team')+'</span><span class="hic-value">'+esc(conn.teamName||'-')+'</span>';
3434
- sh+='</div></div>';
3435
- }else{
3436
- sh+='</div><div class="hic-empty" style="color:var(--text-muted)">'+t('sharing.disconnected.hint')+'</div>'+
3437
- '<div style="margin-top:10px;padding:0 16px 14px"><button class="btn btn-sm btn-primary" id="btnRetryConn" onclick="retryConnection()">'+t('sharing.retryConnection')+'</button>'+
3438
- '<span id="retryConnResult" style="margin-left:10px;font-size:11px"></span></div></div>';
3439
- }
3440
- statusEl.innerHTML=sh;
3441
- teamEl.innerHTML='';adminEl.innerHTML='';
3442
- return;
3443
- }
2220
+ function renderLogToolFilter(tools,current){
2221
+ const sel=document.getElementById('logToolFilter');
2222
+ const opts=['<option value="">'+t('logs.allTools')+'</option>'];
2223
+ tools.forEach(tn=>{
2224
+ opts.push('<option value="'+tn+'"'+(tn===current?' selected':'')+'>'+tn+'</option>');
2225
+ });
2226
+ sel.innerHTML=opts.join('');
2227
+ }
3444
2228
 
3445
- statusEl.innerHTML='';teamEl.innerHTML='';adminEl.innerHTML='';
2229
+ function formatLogTime(ts){
2230
+ const d=new Date(ts);
2231
+ const time=d.toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit',second:'2-digit',hour12:false});
2232
+ const y=d.getFullYear();
2233
+ const m=String(d.getMonth()+1).padStart(2,'0');
2234
+ const day=String(d.getDate()).padStart(2,'0');
2235
+ return y+'-'+m+'-'+day+' '+time;
3446
2236
  }
3447
2237
 
3448
- async function retryConnection(){
3449
- var btn=document.getElementById('btnRetryConn');
3450
- var result=document.getElementById('retryConnResult');
3451
- if(btn){btn.disabled=true;btn.textContent=t('sharing.retryConnection.loading');}
3452
- if(result) result.innerHTML='<span style="color:var(--text-muted)">'+t('sharing.retryConnection.loading')+'</span>';
3453
- try{
3454
- await loadSharingStatus(false);
3455
- var d=sharingStatusCache;
3456
- if(d&&d.connection&&d.connection.connected){
3457
- toast(t('sharing.retryConnection.success'),'success');
3458
- if(result) result.innerHTML='<span style="color:#22c55e">\\u2705 '+t('sharing.retryConnection.success')+'</span>';
3459
- }else{
3460
- if(result) result.innerHTML='<span style="color:#ef4444">'+t('sharing.retryConnection.fail')+'</span>';
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:''});
3461
2266
  }
3462
- }catch(e){
3463
- if(result) result.innerHTML='<span style="color:#ef4444">'+t('sharing.retryConnection.fail')+'</span>';
3464
2267
  }
3465
- if(btn){btn.disabled=false;btn.textContent=t('sharing.retryConnection');}
3466
- }
3467
-
3468
- async function retryHubJoin(){
3469
- if(!confirm(t('sharing.retryJoin.confirm'))) return;
3470
- try{
3471
- var r=await fetch('/api/sharing/retry-join',{method:'POST',headers:{'Content-Type':'application/json'}});
3472
- var d=await r.json();
3473
- if(d.ok){
3474
- toast(t('sharing.retryJoin.success'),'success');
3475
- setTimeout(function(){location.reload();},1500);
3476
- }else{
3477
- toast(d.error||t('sharing.retryJoin.fail'),'error');
3478
- }
3479
- }catch(e){toast(t('sharing.retryJoin.fail')+': '+e.message,'error');}
3480
- }
3481
-
3482
- async function updateHubUsername(){
3483
- var input=document.getElementById('hubUsernameInput');
3484
- if(!input) return;
3485
- var newName=input.value.trim();
3486
- if(!newName||newName.length<2||newName.length>32){
3487
- toast(t('sharing.username.invalid'),'error');
3488
- return;
3489
- }
3490
- try{
3491
- var r=await fetch('/api/sharing/update-username',{
3492
- method:'POST',
3493
- headers:{'Content-Type':'application/json'},
3494
- body:JSON.stringify({username:newName})
3495
- });
3496
- var d=await r.json();
3497
- if(d.error==='username_taken'){
3498
- toast(t('sharing.username.taken'),'error');
3499
- return;
3500
- }
3501
- if(d.error){
3502
- toast(d.error,'error');
3503
- return;
3504
- }
3505
- toast(t('sharing.username.updated'),'success');
3506
- loadSharingStatus(false);
3507
- }catch(e){
3508
- toast(t('sharing.username.error'),'error');
3509
- }
3510
- }
3511
-
3512
- async function loadSharingPendingUsers(){
3513
- if(_sharingRole==='client') return;
3514
- var el=document.getElementById('sharingAdminPanel');
3515
- if(!el) return;
3516
- el.innerHTML=t('sharing.loading');
3517
- try{
3518
- const r=await fetch('/api/sharing/pending-users');
3519
- const d=await r.json();
3520
- const users=Array.isArray(d.users)?d.users:[];
3521
- renderSharingPendingUsers(users, d.error, sharingStatusCache&&sharingStatusCache.admin?sharingStatusCache.admin.rejectSupported:false);
3522
- }catch(e){
3523
- el.innerHTML='<div class="line">'+t('sharing.pendingLoadFail')+esc(String(e))+'</div>';
3524
- }
3525
- }
3526
-
3527
- function renderSharingPendingUsers(users, error, rejectSupported){
3528
- var el=document.getElementById('sharingAdminPanel');
3529
- if(!el) return;
3530
- var wrap='<div class="hub-info-card hic-pending"><div class="hic-title"><span class="hic-icon">\u{1F6E1}</span>'+t('settings.hub.adminPending')+' <span class="hic-badge pending"><span class="hic-dot amber"></span>'+(users?users.length:0)+'</span></div>';
3531
- if(error){
3532
- el.innerHTML=wrap+'<div class="hic-empty">'+esc(error)+'</div></div>';
3533
- return;
3534
- }
3535
- if(!users||users.length===0){
3536
- el.innerHTML=wrap+'<div class="hic-empty">'+t('sharing.noPending')+'</div></div>';
3537
- return;
3538
- }
3539
- el.innerHTML=wrap+users.map(function(user){
3540
- return '<div style="display:flex;align-items:center;justify-content:space-between;padding:8px 0;border-top:1px solid var(--border)">'+
3541
- '<div><div style="font-size:13px;font-weight:600;color:var(--text)">'+esc(user.username||user.id||'')+'</div>'+
3542
- '<div style="font-size:11px;color:var(--text-muted)">'+t('admin.device')+esc(user.deviceName||'unknown')+'</div></div>'+
3543
- '<div class="hic-actions" style="margin:0">'+
3544
- '<button class="btn btn-sm btn-primary" onclick="approveSharingUser(&quot;'+escAttr(user.id)+'&quot;,&quot;'+escAttr(user.username||'')+'&quot;)">'+t('admin.approve')+'</button>'+
3545
- (rejectSupported?'<button class="btn btn-sm btn-ghost" style="color:var(--rose)" onclick="rejectSharingUser(&quot;'+escAttr(user.id)+'&quot;,&quot;'+escAttr(user.username||'')+'&quot;)">'+t('admin.reject')+'</button>':'')+
3546
- '</div></div>';
3547
- }).join('')+'</div>';
3548
- }
3549
-
3550
- async function approveSharingUser(userId,username){
3551
- try{
3552
- const r=await fetch('/api/sharing/approve-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,username:username})});
3553
- const d=await r.json();
3554
- if(d.ok){toast(t('toast.userApproved'),'success');loadSharingPendingUsers();loadSharingStatus(true);} else {toast(d.error||t('toast.approveFail'),'error');}
3555
- }catch(e){toast(t('toast.approveFail')+': '+e.message,'error');}
3556
- }
3557
-
3558
- async function rejectSharingUser(userId,username){
3559
- try{
3560
- const r=await fetch('/api/sharing/reject-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,username:username})});
3561
- const d=await r.json();
3562
- if(d.ok){toast(t('toast.userRejected'),'success');loadSharingPendingUsers();} else {toast(d.error||t('toast.rejectFail'),'error');}
3563
- }catch(e){toast(t('toast.rejectFail')+': '+e.message,'error');}
3564
- }
3565
-
3566
- /* ─── Team Setup Guide ─── */
3567
- var TEAM_GUIDE_DISMISSED_KEY='memos-team-guide-dismissed';
3568
- function updateTeamGuide(sharingData){
3569
- var el=document.getElementById('teamSetupGuide');
3570
- if(!el) return;
3571
- if(localStorage.getItem(TEAM_GUIDE_DISMISSED_KEY)==='1'){el.style.display='none';return;}
3572
- var isConfigured=sharingData&&sharingData.enabled;
3573
- el.style.display=isConfigured?'none':'block';
3574
- }
3575
- function dismissTeamGuide(){
3576
- localStorage.setItem(TEAM_GUIDE_DISMISSED_KEY,'1');
3577
- var el=document.getElementById('teamSetupGuide');
3578
- if(el) el.style.display='none';
3579
- }
3580
- function guideGoToHub(role){
3581
- switchSettingsTab('hub',document.querySelector('.settings-tab-btn[data-tab="hub"]'));
3582
- var chk=document.getElementById('cfgSharingEnabled');
3583
- if(chk&&!chk.checked){chk.checked=true;onSharingToggle();}
3584
- selectSharingRole(role);
3585
- var card=document.getElementById('settingsSharingConfig');
3586
- if(card) card.scrollIntoView({behavior:'smooth',block:'start'});
3587
- }
3588
-
3589
- /* ─── Group Manager ─── */
3590
- var groupManagerUsers=[];
3591
- async function loadGroupManager(){
3592
- var panel=document.getElementById('groupManagerPanel');
3593
- if(!panel) return;
3594
- panel.style.display='block';
3595
- panel.innerHTML=t('sharing.loadingGroups');
3596
- try{
3597
- var [gr,ur]=await Promise.all([
3598
- fetch('/api/sharing/groups').then(function(r){return r.json();}),
3599
- fetch('/api/sharing/users').then(function(r){return r.json();})
3600
- ]);
3601
- var groups=Array.isArray(gr.groups)?gr.groups:[];
3602
- groupManagerUsers=Array.isArray(ur.users)?ur.users:[];
3603
- renderGroupManager(panel,groups);
3604
- }catch(e){panel.innerHTML=t('admin.groupsFailed')+esc(String(e));}
3605
- }
3606
- function renderGroupManager(panel,groups){
3607
- var html='<div style="margin-bottom:8px;display:flex;gap:8px;align-items:center;font-size:12px">'+
3608
- '<strong>'+t('admin.groups')+' ('+groups.length+')</strong>'+
3609
- '<button class="btn btn-sm" onclick="showCreateGroupForm()" style="font-size:11px">'+t('admin.newGroup')+'</button>'+
3610
- '</div>';
3611
- html+='<div id="createGroupForm" style="display:none;margin-bottom:10px;padding:10px;background:var(--bg);border:1px solid var(--border);border-radius:8px;font-size:12px">'+
3612
- '<input id="newGroupName" type="text" placeholder="'+t('admin.groupName')+'" style="width:60%;padding:4px 8px;border:1px solid var(--border);border-radius:6px;font-size:12px;margin-right:6px;background:var(--bg);color:var(--text)">'+
3613
- '<input id="newGroupDesc" type="text" placeholder="'+t('admin.groupDesc')+'" style="width:60%;padding:4px 8px;border:1px solid var(--border);border-radius:6px;font-size:12px;margin-right:6px;margin-top:6px;background:var(--bg);color:var(--text)">'+
3614
- '<div style="margin-top:6px"><button class="btn btn-sm btn-primary" onclick="createGroup()" style="font-size:11px">'+t('admin.create')+'</button> '+
3615
- '<button class="btn btn-sm btn-ghost" onclick="hideCreateGroupForm()" style="font-size:11px">'+t('admin.cancel')+'</button></div>'+
3616
- '</div>';
3617
- if(groups.length===0){
3618
- html+='<div style="font-size:12px;color:var(--text-muted);padding:6px 0">'+t('sharing.noGroupsYet')+'</div>';
3619
- }else{
3620
- html+='<div style="display:flex;flex-direction:column;gap:6px">';
3621
- for(var i=0;i<groups.length;i++){
3622
- var g=groups[i];
3623
- html+='<div style="padding:8px 12px;background:var(--bg);border:1px solid var(--border);border-radius:8px;font-size:12px">'+
3624
- '<div style="display:flex;justify-content:space-between;align-items:center">'+
3625
- '<div><strong style="font-size:12px">'+esc(g.name)+'</strong>'+(g.description?' — <span style="color:var(--text-sec);font-size:11px">'+esc(g.description)+'</span>':'')+'</div>'+
3626
- '<div style="display:flex;gap:6px">'+
3627
- '<button class="btn btn-sm" onclick="toggleGroupMembers(&quot;'+escAttr(g.id)+'&quot;)" style="font-size:11px;padding:2px 8px">'+t('admin.members')+'</button>'+
3628
- '<button class="btn btn-sm btn-ghost" onclick="deleteGroup(&quot;'+escAttr(g.id)+'&quot;,&quot;'+escAttr(g.name)+'&quot;)" style="color:#ef4444;font-size:11px;padding:2px 8px">'+t('admin.delete')+'</button>'+
3629
- '</div>'+
3630
- '</div>'+
3631
- '<div id="groupMembers_'+escAttr(g.id)+'" style="display:none;margin-top:6px;font-size:12px"></div>'+
3632
- '</div>';
3633
- }
3634
- html+='</div>';
3635
- }
3636
- panel.innerHTML=html;
3637
- }
3638
- function showCreateGroupForm(){var f=document.getElementById('createGroupForm');if(f)f.style.display='block';}
3639
- function hideCreateGroupForm(){var f=document.getElementById('createGroupForm');if(f)f.style.display='none';}
3640
- async function createGroup(){
3641
- var name=(document.getElementById('newGroupName')).value.trim();
3642
- var desc=(document.getElementById('newGroupDesc')).value.trim();
3643
- if(!name){toast(t('toast.groupNameRequired'),'error');return;}
3644
- try{
3645
- var r=await fetch('/api/sharing/groups',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:name,description:desc})});
3646
- var d=await r.json();
3647
- if(d.ok){toast(t('toast.groupCreated'),'success');hideCreateGroupForm();loadGroupManager();}else{toast(d.error||t('toast.createFail'),'error');}
3648
- }catch(e){toast(t('toast.createFail')+': '+e.message,'error');}
3649
- }
3650
- async function deleteGroup(groupId,groupName){
3651
- if(!confirm(t('confirm.deleteGroup').replace('{name}',groupName))) return;
3652
- try{
3653
- var r=await fetch('/api/sharing/groups/'+encodeURIComponent(groupId),{method:'DELETE'});
3654
- var d=await r.json();
3655
- if(d.ok){toast(t('toast.groupDeleted'),'success');loadGroupManager();}else{toast(d.error||t('toast.deleteFail'),'error');}
3656
- }catch(e){toast(t('toast.deleteFail')+': '+e.message,'error');}
3657
- }
3658
- async function toggleGroupMembers(groupId){
3659
- var el=document.getElementById('groupMembers_'+groupId);
3660
- if(!el) return;
3661
- if(el.style.display!=='none'){el.style.display='none';return;}
3662
- el.style.display='block';
3663
- el.innerHTML=t('sharing.loading');
3664
- try{
3665
- var r=await fetch('/api/sharing/groups/'+encodeURIComponent(groupId)+'/members');
3666
- var d=await r.json();
3667
- var members=Array.isArray(d.members)?d.members:[];
3668
- renderGroupMembers(el,groupId,members);
3669
- }catch(e){el.innerHTML=t('admin.groupsFailed')+esc(String(e));}
3670
- }
3671
- function renderGroupMembers(el,groupId,members){
3672
- var html='<div style="font-size:12px;margin-bottom:6px;color:var(--text-sec)">'+t('admin.membersCount').replace('{n}',members.length)+'</div>';
3673
- if(members.length>0){
3674
- html+='<div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px">';
3675
- for(var i=0;i<members.length;i++){
3676
- var m=members[i];
3677
- html+='<span style="display:inline-flex;align-items:center;gap:4px;padding:3px 10px;background:var(--bg-card);border:1px solid var(--border);border-radius:12px;font-size:12px">'+
3678
- esc(m.username||m.userId)+
3679
- ' <button style="background:none;border:none;color:#ef4444;cursor:pointer;font-size:11px;padding:0 2px" onclick="removeGroupMember(&quot;'+escAttr(groupId)+'&quot;,&quot;'+escAttr(m.userId)+'&quot;)">&times;</button>'+
3680
- '</span>';
3681
- }
3682
- html+='</div>';
3683
- }else{
3684
- html+='<div style="font-size:12px;color:var(--text-muted);margin-bottom:8px">'+t('admin.noMembersYet')+'</div>';
3685
- }
3686
- var memberIds=new Set(members.map(function(m){return m.userId;}));
3687
- var available=groupManagerUsers.filter(function(u){return !memberIds.has(u.id);});
3688
- if(available.length>0){
3689
- html+='<div style="display:flex;gap:6px;align-items:center">'+
3690
- '<select id="addMemberSelect_'+escAttr(groupId)+'" style="padding:4px 8px;border:1px solid var(--border);border-radius:6px;font-size:12px">';
3691
- for(var j=0;j<available.length;j++){
3692
- html+='<option value="'+escAttr(available[j].id)+'">'+esc(available[j].username)+'</option>';
3693
- }
3694
- html+='</select>'+
3695
- '<button class="btn btn-sm" onclick="addGroupMember(&quot;'+escAttr(groupId)+'&quot;)">'+t('admin.add')+'</button>'+
3696
- '</div>';
3697
- }
3698
- el.innerHTML=html;
3699
- }
3700
- async function addGroupMember(groupId){
3701
- var sel=document.getElementById('addMemberSelect_'+groupId);
3702
- if(!sel) return;
3703
- var userId=sel.value;
3704
- if(!userId) return;
3705
- try{
3706
- var r=await fetch('/api/sharing/groups/'+encodeURIComponent(groupId)+'/members',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId})});
3707
- var d=await r.json();
3708
- if(d.ok){toast(t('toast.memberAdded'),'success');reloadGroupMembers(groupId);}else{toast(d.error||t('toast.addFail'),'error');}
3709
- }catch(e){toast(t('toast.addFail')+': '+e.message,'error');}
3710
- }
3711
- async function removeGroupMember(groupId,userId){
3712
- if(!confirm(t('confirm.removeGroupMember'))) return;
3713
- try{
3714
- var r=await fetch('/api/sharing/groups/'+encodeURIComponent(groupId)+'/members',{method:'DELETE',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId})});
3715
- var d=await r.json();
3716
- if(d.ok){toast(t('toast.memberRemoved'),'success');reloadGroupMembers(groupId);}else{toast(d.error||t('toast.removeFail'),'error');}
3717
- }catch(e){toast(t('toast.removeFail')+': '+e.message,'error');}
3718
- }
3719
- async function reloadGroupMembers(groupId){
3720
- var el=document.getElementById('groupMembers_'+groupId);
3721
- if(!el) return;
3722
- el.style.display='block';
3723
- el.innerHTML=t('sharing.loading');
3724
- try{
3725
- var r=await fetch('/api/sharing/groups/'+encodeURIComponent(groupId)+'/members');
3726
- var d=await r.json();
3727
- var members=Array.isArray(d.members)?d.members:[];
3728
- renderGroupMembers(el,groupId,members);
3729
- }catch(e){el.innerHTML=t('admin.groupsFailed')+esc(String(e));}
3730
- }
3731
-
3732
- /* ─── Hub Admin Panel ─── */
3733
- var adminDataCache={users:[],groups:[],tasks:[],skills:[],memories:[]};
3734
-
3735
- function switchAdminTab(tab,btn){
3736
- document.querySelectorAll('.admin-tabs .admin-tab').forEach(function(t){t.classList.remove('active');});
3737
- btn.classList.add('active');
3738
- document.querySelectorAll('.admin-panel').forEach(function(p){p.classList.remove('active');});
3739
- var panel=document.getElementById('admin'+tab.charAt(0).toUpperCase()+tab.slice(1)+'Panel');
3740
- if(panel) panel.classList.add('active');
3741
- }
3742
-
3743
- function adminGoSetup(role){
3744
- switchView('settings');
3745
- setTimeout(function(){guideGoToHub(role);},200);
3746
- }
3747
-
3748
- async function loadAdminData(){
3749
- var notEnabledEl=document.getElementById('adminNotEnabled');
3750
- var mainEl=document.getElementById('adminMainContent');
3751
- var sharingOn=sharingStatusCache&&sharingStatusCache.enabled;
3752
- if(!sharingOn){
3753
- if(mainEl) mainEl.style.display='none';
3754
- if(notEnabledEl){
3755
- notEnabledEl.style.display='block';
3756
- notEnabledEl.innerHTML=
3757
- '<div style="text-align:center;padding:60px 32px;max-width:520px;margin:0 auto">'+
3758
- '<div style="font-size:48px;margin-bottom:16px">\u{1F6E1}</div>'+
3759
- '<div style="font-size:18px;font-weight:700;color:var(--text);margin-bottom:8px">'+t('admin.notEnabled.title')+'</div>'+
3760
- '<div style="font-size:13px;color:var(--text-sec);line-height:1.7;margin-bottom:24px">'+t('admin.notEnabled.desc')+'</div>'+
3761
- '<div style="display:flex;gap:12px;justify-content:center;flex-wrap:wrap">'+
3762
- '<button class="btn btn-primary" style="padding:8px 20px;font-size:13px" onclick="adminGoSetup(&quot;hub&quot;)">'+t('admin.notEnabled.setupHub')+'</button>'+
3763
- '<button class="btn btn-ghost" style="padding:8px 20px;font-size:13px" onclick="adminGoSetup(&quot;client&quot;)">'+t('admin.notEnabled.joinTeam')+'</button>'+
3764
- '</div>'+
3765
- '<div style="font-size:11px;color:var(--text-muted);margin-top:20px;line-height:1.6">'+t('admin.notEnabled.hint')+'</div>'+
3766
- '</div>';
3767
- }
3768
- return;
3769
- }
3770
- if(notEnabledEl) notEnabledEl.style.display='none';
3771
- if(mainEl) mainEl.style.display='';
3772
- if(!window._isHubAdmin){
3773
- var statsEl=document.getElementById('adminStats');
3774
- if(statsEl) statsEl.innerHTML='<div class="admin-empty">'+t('admin.noPermission')+'</div>';
3775
- return;
3776
- }
3777
- try{
3778
- var [usersR,tasksR,skillsR,pendingR,memoriesR]=await Promise.all([
3779
- fetch('/api/sharing/users').then(function(r){return r.json();}),
3780
- fetch('/api/admin/shared-tasks').then(function(r){return r.json();}),
3781
- fetch('/api/admin/shared-skills').then(function(r){return r.json();}),
3782
- fetch('/api/sharing/pending-users').then(function(r){return r.json();}),
3783
- fetch('/api/admin/shared-memories').then(function(r){return r.json();})
3784
- ]);
3785
- adminDataCache.users=Array.isArray(usersR.users)?usersR.users:[];
3786
- adminDataCache.groups=[];
3787
- adminDataCache.tasks=Array.isArray(tasksR.tasks)?tasksR.tasks:[];
3788
- adminDataCache.skills=Array.isArray(skillsR.skills)?skillsR.skills:[];
3789
- adminDataCache.memories=Array.isArray(memoriesR.memories)?memoriesR.memories:[];
3790
- var pending=Array.isArray(pendingR.users)?pendingR.users:[];
3791
- renderAdminStats(pending.length);
3792
- renderAdminUsers(adminDataCache.users, pending);
3793
- renderAdminMemories(adminDataCache.tasks);
3794
- renderAdminSkills(adminDataCache.skills);
3795
- renderAdminSharedMemories(adminDataCache.memories);
3796
- }catch(e){
3797
- var statsEl=document.getElementById('adminStats');
3798
- if(statsEl) statsEl.innerHTML='<div class="admin-empty">'+t('admin.loadFailed')+esc(String(e))+'</div>';
3799
- }
3800
- }
3801
-
3802
- function renderAdminStats(pendingCount){
3803
- var el=document.getElementById('adminStats');
3804
- if(!el) return;
3805
- el.innerHTML=
3806
- '<div class="admin-stat-box"><span class="as-icon">\u{1F465}</span><div class="val">'+adminDataCache.users.length+'</div><div class="lbl">'+t('admin.stat.activeUsers')+'</div></div>'+
3807
- '<div class="admin-stat-box"><span class="as-icon">\u{23F3}</span><div class="val">'+pendingCount+'</div><div class="lbl">'+t('admin.stat.pending')+'</div></div>'+
3808
- '<div class="admin-stat-box"><span class="as-icon">\u{1F4CB}</span><div class="val">'+adminDataCache.tasks.length+'</div><div class="lbl">'+t('admin.stat.sharedTasks')+'</div></div>'+
3809
- '<div class="admin-stat-box"><span class="as-icon">\u{1F9E0}</span><div class="val">'+adminDataCache.skills.length+'</div><div class="lbl">'+t('admin.stat.sharedSkills')+'</div></div>'+
3810
- '<div class="admin-stat-box"><span class="as-icon">\u{1F4AD}</span><div class="val">'+(adminDataCache.memories||[]).length+'</div><div class="lbl">'+t('admin.stat.sharedMemories')+'</div></div>';
3811
- var tc=document.getElementById('adminTabCountUsers');if(tc)tc.textContent=adminDataCache.users.length+pendingCount;
3812
- tc=document.getElementById('adminTabCountMemories');if(tc)tc.textContent=(adminDataCache.memories||[]).length;
3813
- tc=document.getElementById('adminTabCountTasks');if(tc)tc.textContent=adminDataCache.tasks.length;
3814
- tc=document.getElementById('adminTabCountSkills');if(tc)tc.textContent=adminDataCache.skills.length;
3815
- }
3816
-
3817
- function renderAdminUsers(users,pending){
3818
- var el=document.getElementById('adminUsersPanel');
3819
- if(!el) return;
3820
- var html='';
3821
- if(pending&&pending.length>0){
3822
- html+='<div style="margin-bottom:16px"><h3 style="font-size:14px;font-weight:600;color:var(--amber);margin-bottom:10px">'+t('admin.pendingApproval')+' ('+pending.length+')</h3>';
3823
- for(var p=0;p<pending.length;p++){
3824
- var pu=pending[p];
3825
- html+='<div class="admin-card"><div class="admin-card-header"><div class="admin-card-title">'+esc(pu.username||pu.id||'Unknown')+'</div><span class="admin-badge pending">pending</span></div>'+
3826
- '<div class="admin-card-meta">'+t('admin.device')+esc(pu.deviceName||'unknown')+'</div>'+
3827
- '<div class="admin-card-actions">'+
3828
- '<button class="btn btn-sm btn-primary" onclick="adminApproveUser(&quot;'+escAttr(pu.id)+'&quot;,&quot;'+escAttr(pu.username||'')+'&quot;)">'+t('admin.approve')+'</button>'+
3829
- '<button class="btn btn-sm btn-ghost" onclick="adminRejectUser(&quot;'+escAttr(pu.id)+'&quot;)" style="color:var(--rose)">'+t('admin.reject')+'</button>'+
3830
- '</div></div>';
3831
- }
3832
- html+='</div>';
3833
- }
3834
- html+='<h3 style="font-size:14px;font-weight:600;color:var(--text);margin-bottom:10px">'+t('admin.activeUsers')+' ('+users.length+')</h3>';
3835
- if(users.length===0){
3836
- html+='<div class="admin-empty"><span class="ae-icon">\u{1F465}</span>'+t('admin.noActiveUsers')+'</div>';
3837
- }else{
3838
- for(var i=0;i<users.length;i++){
3839
- var u=users[i];
3840
- html+='<div class="admin-card"><div class="admin-card-header"><div class="admin-card-title">'+esc(u.username||u.id)+'</div>'+
3841
- '<span class="admin-badge '+(u.role==='admin'?'admin':'member')+'">'+esc(u.role||'member')+'</span></div>'+
3842
- '<div class="admin-card-meta">ID: '+esc(u.id)+(u.status?' \u00B7 Status: '+esc(u.status):'')+'</div></div>';
3843
- }
3844
- }
3845
- el.innerHTML=html;
3846
- }
3847
-
3848
- async function adminApproveUser(userId,username){
3849
- try{
3850
- var r=await fetch('/api/sharing/approve-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,username:username})});
3851
- var d=await r.json();
3852
- if(d.ok){toast(t('toast.userApproved'),'success');loadAdminData();}else{toast(d.error||t('toast.approveFail'),'error');}
3853
- }catch(e){toast(t('toast.approveFail')+': '+e.message,'error');}
3854
- }
3855
- async function adminRejectUser(userId){
3856
- if(!confirm(t('confirm.rejectUser'))) return;
3857
- try{
3858
- var r=await fetch('/api/sharing/reject-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId})});
3859
- var d=await r.json();
3860
- if(d.ok){toast(t('toast.userRejected'),'success');loadAdminData();}else{toast(d.error||t('toast.rejectFail'),'error');}
3861
- }catch(e){toast(t('toast.rejectFail')+': '+e.message,'error');}
3862
- }
3863
-
3864
- function renderAdminGroups(groups){
3865
- var el=document.getElementById('adminGroupsPanel');
3866
- if(!el) return;
3867
- var html='<div style="margin-bottom:12px;display:flex;justify-content:space-between;align-items:center">'+
3868
- '<h3 style="font-size:14px;font-weight:600;color:var(--text)">'+t('admin.groups')+' ('+groups.length+')</h3>'+
3869
- '<button class="btn btn-sm btn-primary" onclick="showAdminCreateGroup()">'+t('admin.newGroup')+'</button></div>';
3870
- html+='<div id="adminCreateGroupForm" style="display:none;margin-bottom:14px;padding:14px;background:var(--bg);border:1px solid var(--border);border-radius:10px">'+
3871
- '<div style="display:flex;flex-direction:column;gap:8px">'+
3872
- '<input id="adminNewGroupName" type="text" placeholder="'+t('admin.groupName')+'" style="padding:8px 12px;border:1px solid var(--border);border-radius:8px;font-size:13px;background:var(--bg-card);color:var(--text)">'+
3873
- '<input id="adminNewGroupDesc" type="text" placeholder="'+t('admin.groupDesc')+'" style="padding:8px 12px;border:1px solid var(--border);border-radius:8px;font-size:13px;background:var(--bg-card);color:var(--text)">'+
3874
- '<div style="display:flex;gap:8px"><button class="btn btn-sm btn-primary" onclick="adminCreateGroup()">'+t('admin.create')+'</button>'+
3875
- '<button class="btn btn-sm btn-ghost" onclick="hideAdminCreateGroup()">'+t('admin.cancel')+'</button></div></div></div>';
3876
- if(groups.length===0){
3877
- html+='<div class="admin-empty"><span class="ae-icon">\u{1F4C2}</span>'+t('admin.noGroups')+'</div>';
3878
- }else{
3879
- for(var i=0;i<groups.length;i++){
3880
- var g=groups[i];
3881
- html+='<div class="admin-card"><div class="admin-card-header"><div class="admin-card-title">'+esc(g.name)+'</div>'+
3882
- '<button class="btn btn-sm btn-ghost" onclick="adminDeleteGroup(&quot;'+escAttr(g.id)+'&quot;,&quot;'+escAttr(g.name)+'&quot;)" style="color:var(--rose);font-size:11px">'+t('admin.delete')+'</button></div>'+
3883
- (g.description?'<div class="admin-card-meta">'+esc(g.description)+'</div>':'')+
3884
- '<div id="adminGroupMembers_'+escAttr(g.id)+'" style="margin-top:10px"></div>'+
3885
- '</div>';
3886
- }
3887
- }
3888
- el.innerHTML=html;
3889
- for(var i=0;i<groups.length;i++){adminLoadGroupMembers(groups[i].id);}
3890
- }
3891
- function showAdminCreateGroup(){var f=document.getElementById('adminCreateGroupForm');if(f)f.style.display='block';}
3892
- function hideAdminCreateGroup(){var f=document.getElementById('adminCreateGroupForm');if(f)f.style.display='none';}
3893
- async function adminCreateGroup(){
3894
- var name=(document.getElementById('adminNewGroupName')).value.trim();
3895
- var desc=(document.getElementById('adminNewGroupDesc')).value.trim();
3896
- if(!name){toast(t('toast.groupNameRequired'),'error');return;}
3897
- try{
3898
- var r=await fetch('/api/sharing/groups',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:name,description:desc})});
3899
- var d=await r.json();
3900
- if(d.ok||d.id){toast(t('toast.groupCreated'),'success');hideAdminCreateGroup();loadAdminData();}else{toast(d.error||t('toast.createFail'),'error');}
3901
- }catch(e){toast(t('toast.createFail')+': '+e.message,'error');}
3902
- }
3903
- async function adminDeleteGroup(groupId,groupName){
3904
- if(!confirm(t('confirm.deleteGroupShort').replace('{name}',groupName))) return;
3905
- try{
3906
- var r=await fetch('/api/sharing/groups/'+encodeURIComponent(groupId),{method:'DELETE'});
3907
- var d=await r.json();
3908
- if(d.ok){toast(t('toast.groupDeleted'),'success');loadAdminData();}else{toast(d.error||t('toast.deleteFail'),'error');}
3909
- }catch(e){toast(t('toast.deleteFail')+': '+e.message,'error');}
3910
- }
3911
- async function adminLoadGroupMembers(groupId){
3912
- var el=document.getElementById('adminGroupMembers_'+groupId);
3913
- if(!el) return;
3914
- el.innerHTML=t('sharing.loading');
3915
- try{
3916
- var r=await fetch('/api/sharing/groups/'+encodeURIComponent(groupId)+'/members');
3917
- var d=await r.json();
3918
- var members=Array.isArray(d.members)?d.members:[];
3919
- var html='<div style="font-size:12px;margin-bottom:6px;color:var(--text-sec)">'+t('admin.membersCount').replace('{n}',members.length)+'</div>';
3920
- if(members.length>0){
3921
- html+='<div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px">';
3922
- for(var i=0;i<members.length;i++){
3923
- var m=members[i];
3924
- html+='<span style="display:inline-flex;align-items:center;gap:4px;padding:3px 10px;background:var(--bg-card);border:1px solid var(--border);border-radius:12px;font-size:12px">'+
3925
- esc(m.username||m.userId)+
3926
- ' <button style="background:none;border:none;color:#ef4444;cursor:pointer;font-size:11px;padding:0 2px" onclick="adminRemoveGroupMember(&quot;'+escAttr(groupId)+'&quot;,&quot;'+escAttr(m.userId)+'&quot;)">&times;</button></span>';
3927
- }
3928
- html+='</div>';
3929
- }else{
3930
- html+='<div style="font-size:12px;color:var(--text-muted);margin-bottom:8px">'+t('admin.noMembers')+'</div>';
3931
- }
3932
- var memberIds=new Set(members.map(function(m){return m.userId;}));
3933
- var available=adminDataCache.users.filter(function(u){return !memberIds.has(u.id);});
3934
- if(available.length>0){
3935
- html+='<div style="display:flex;gap:6px;align-items:center">'+
3936
- '<select id="adminAddMember_'+escAttr(groupId)+'" style="padding:4px 8px;border:1px solid var(--border);border-radius:6px;font-size:12px;background:var(--bg-card);color:var(--text)">';
3937
- for(var j=0;j<available.length;j++){
3938
- html+='<option value="'+escAttr(available[j].id)+'">'+esc(available[j].username)+'</option>';
3939
- }
3940
- html+='</select><button class="btn btn-sm" onclick="adminAddGroupMember(&quot;'+escAttr(groupId)+'&quot;)">'+t('admin.add')+'</button></div>';
3941
- }
3942
- el.innerHTML=html;
3943
- }catch(e){el.innerHTML=t('admin.groupsFailed')+esc(String(e));}
3944
- }
3945
- async function adminAddGroupMember(groupId){
3946
- var sel=document.getElementById('adminAddMember_'+groupId);
3947
- if(!sel) return;
3948
- try{
3949
- var r=await fetch('/api/sharing/groups/'+encodeURIComponent(groupId)+'/members',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:sel.value})});
3950
- var d=await r.json();
3951
- if(d.ok){toast(t('toast.memberAdded'),'success');adminLoadGroupMembers(groupId);}else{toast(d.error||t('toast.addFail'),'error');}
3952
- }catch(e){toast(t('toast.addFail')+': '+e.message,'error');}
3953
- }
3954
- async function adminRemoveGroupMember(groupId,userId){
3955
- if(!confirm(t('confirm.removeMember'))) return;
3956
- try{
3957
- var r=await fetch('/api/sharing/groups/'+encodeURIComponent(groupId)+'/members',{method:'DELETE',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId})});
3958
- var d=await r.json();
3959
- if(d.ok){toast(t('toast.memberRemoved'),'success');adminLoadGroupMembers(groupId);}else{toast(d.error||t('toast.removeFail'),'error');}
3960
- }catch(e){toast(t('toast.removeFail')+': '+e.message,'error');}
3961
- }
3962
-
3963
- function renderAdminMemories(tasks){
3964
- var el=document.getElementById('adminMemoriesPanel');
3965
- if(!el) return;
3966
- adminTasksCache=tasks;
3967
- var html='<h3 style="font-size:14px;font-weight:600;color:var(--text);margin-bottom:10px">'+t('admin.sharedTasks')+' ('+tasks.length+')</h3>';
3968
- if(tasks.length===0){
3969
- html+='<div class="admin-empty"><span class="ae-icon">\u{1F4CB}</span>'+t('admin.noSharedTasks')+'</div>';
3970
- }else{
3971
- for(var i=0;i<tasks.length;i++){
3972
- var tk=tasks[i];
3973
- html+='<div class="admin-card" onclick="openHubTaskDetailFromCache(\\\'admin\\\','+i+')" style="cursor:pointer"><div class="admin-card-header"><div class="admin-card-title">'+esc(tk.title||tk.id)+'</div>'+
3974
- '</div>'+
3975
- '<div class="admin-card-meta">'+
3976
- t('admin.owner')+esc(tk.ownerName||tk.sourceUserId||'unknown')+
3977
- (tk.chunkCount!=null?' \u00B7 '+t('admin.chunks')+tk.chunkCount:'')+
3978
- ' \u00B7 '+t('admin.updated')+new Date(tk.updatedAt||tk.createdAt).toLocaleDateString()+
3979
- '</div>'+
3980
- '<div class="admin-card-actions">'+
3981
- '<button class="btn btn-sm btn-ghost" onclick="event.stopPropagation();adminDeleteTask(&quot;'+escAttr(tk.id)+'&quot;,&quot;'+escAttr(tk.title||tk.id)+'&quot;)" style="color:var(--rose)">'+t('admin.remove')+'</button>'+
3982
- '</div></div>';
3983
- }
3984
- }
3985
- el.innerHTML=html;
3986
- }
3987
-
3988
- async function adminDeleteTask(taskId,taskTitle){
3989
- if(!confirm(t('confirm.removeTask').replace('{name}',taskTitle))) return;
3990
- try{
3991
- var r=await fetch('/api/admin/shared-tasks/'+encodeURIComponent(taskId),{method:'DELETE'});
3992
- var d=await r.json();
3993
- if(d.ok){toast(t('toast.taskRemoved'),'success');loadAdminData();}else{toast(d.error||t('toast.removeFail'),'error');}
3994
- }catch(e){toast(t('toast.removeFail')+': '+e.message,'error');}
3995
- }
3996
-
3997
- function renderAdminSkills(skills){
3998
- var el=document.getElementById('adminSkillsPanel');
3999
- if(!el) return;
4000
- adminSkillsCache=skills;
4001
- var html='<h3 style="font-size:14px;font-weight:600;color:var(--text);margin-bottom:10px">'+t('admin.sharedSkills')+' ('+skills.length+')</h3>';
4002
- if(skills.length===0){
4003
- html+='<div class="admin-empty"><span class="ae-icon">\u{1F9E0}</span>'+t('admin.noSharedSkills')+'</div>';
4004
- }else{
4005
- for(var i=0;i<skills.length;i++){
4006
- var s=skills[i];
4007
- html+='<div class="admin-card" onclick="openHubSkillDetailFromCache(\\\'admin\\\','+i+')" style="cursor:pointer"><div class="admin-card-header"><div class="admin-card-title">'+esc(s.name||s.id)+'</div>'+
4008
- '</div>'+
4009
- '<div class="admin-card-meta">'+
4010
- (s.description?esc(s.description)+'<br>':'')+
4011
- t('admin.owner')+esc(s.ownerName||s.sourceUserId||'unknown')+
4012
- (s.version!=null?' \u00B7 '+t('admin.version')+s.version:'')+
4013
- (s.qualityScore!=null?' \u00B7 '+t('admin.quality')+s.qualityScore:'')+
4014
- '</div>'+
4015
- '<div class="admin-card-actions">'+
4016
- '<button class="btn btn-sm btn-ghost" onclick="event.stopPropagation();adminDeleteSkill(&quot;'+escAttr(s.id)+'&quot;,&quot;'+escAttr(s.name||s.id)+'&quot;)" style="color:var(--rose)">'+t('admin.remove')+'</button>'+
4017
- '</div></div>';
4018
- }
4019
- }
4020
- el.innerHTML=html;
4021
- }
4022
-
4023
- async function adminDeleteSkill(skillId,skillName){
4024
- if(!confirm(t('confirm.removeSkill').replace('{name}',skillName))) return;
4025
- try{
4026
- var r=await fetch('/api/admin/shared-skills/'+encodeURIComponent(skillId),{method:'DELETE'});
4027
- var d=await r.json();
4028
- if(d.ok){toast(t('toast.skillRemoved'),'success');loadAdminData();}else{toast(d.error||t('toast.removeFail'),'error');}
4029
- }catch(e){toast(t('toast.removeFail')+': '+e.message,'error');}
4030
- }
4031
-
4032
- function renderAdminSharedMemories(memories){
4033
- var el=document.getElementById('adminSharedMemoriesPanel');
4034
- if(!el) return;
4035
- adminMemoriesCache=memories||[];
4036
- var html='<h3 style="font-size:14px;font-weight:600;color:var(--text);margin-bottom:10px">'+t('admin.sharedMemories')+' ('+(memories||[]).length+')</h3>';
4037
- if(!memories||memories.length===0){
4038
- html+='<div class="admin-empty"><span class="ae-icon">\u{1F4AD}</span>'+t('admin.noSharedMemories')+'</div>';
4039
- }else{
4040
- for(var i=0;i<memories.length;i++){
4041
- var m=memories[i];
4042
- html+='<div class="admin-card" onclick="openHubMemoryDetail(\\\'admin\\\','+i+')" style="cursor:pointer"><div class="admin-card-header"><div class="admin-card-title">'+esc(m.summary||m.content?.slice(0,80)||m.id)+'</div>'+
4043
- '</div>'+
4044
- '<div class="admin-card-meta">'+
4045
- t('admin.owner')+esc(m.ownerName||m.sourceUserId||'unknown')+
4046
- (m.kind?' \u00B7 Kind: '+esc(m.kind):'')+
4047
- (m.role?' \u00B7 Role: '+esc(m.role):'')+
4048
- ' \u00B7 '+t('admin.updated')+new Date(m.updatedAt||m.createdAt).toLocaleDateString()+
4049
- '</div>'+
4050
- '<div class="admin-card-actions">'+
4051
- '<button class="btn btn-sm btn-ghost" onclick="event.stopPropagation();adminDeleteMemory(&quot;'+escAttr(m.id)+'&quot;,&quot;'+escAttr(m.summary||m.id)+'&quot;)" style="color:var(--rose)">'+t('admin.remove')+'</button>'+
4052
- '</div></div>';
4053
- }
4054
- }
4055
- el.innerHTML=html;
4056
- }
4057
-
4058
- async function adminDeleteMemory(memoryId,memoryTitle){
4059
- if(!confirm(t('confirm.removeMemory').replace('{name}',memoryTitle))) return;
4060
- try{
4061
- var r=await fetch('/api/admin/shared-memories/'+encodeURIComponent(memoryId),{method:'DELETE'});
4062
- var d=await r.json();
4063
- if(d.ok){toast(t('toast.memoryRemoved'),'success');loadAdminData();}else{toast(d.error||t('toast.removeFail'),'error');}
4064
- }catch(e){toast(t('toast.removeFail')+': '+e.message,'error');}
4065
- }
4066
-
4067
- function renderSharingMemorySearchResults(data,query){
4068
- const list=document.getElementById('memoryList');
4069
- const localHits=(data&&data.local&&Array.isArray(data.local.hits))?data.local.hits:[];
4070
- const hubHits=(data&&data.hub&&Array.isArray(data.hub.hits))?data.hub.hits:[];
4071
- document.getElementById('searchMeta').textContent='Search results for "'+query+'"';
4072
- document.getElementById('sharingSearchMeta').textContent='Local '+localHits.length+' · Hub '+hubHits.length;
4073
- document.getElementById('pagination').innerHTML='';
4074
- list.innerHTML=''+
4075
- '<div class="result-section">'+
4076
- '<div class="result-section-header"><div class="result-section-title">'+t('search.localResults')+'</div><div class="result-section-sub">'+localHits.length+' hit(s)</div></div>'+
4077
- '<div class="search-hit-list">'+(localHits.length?localHits.map(function(hit,idx){
4078
- return '<div class="search-hit-card">'+
4079
- '<div class="summary">'+(idx+1)+'. '+esc(hit.summary||'(no summary)')+'</div>'+
4080
- '<div class="excerpt">'+esc(hit.excerpt||'')+'</div>'+
4081
- '<div class="search-hit-meta">'+
4082
- '<span class="meta-chip">role: '+esc(hit.role||'unknown')+'</span>'+
4083
- (hit.score!=null?'<span class="meta-chip">score: '+Math.round(hit.score*100)+'%</span>':'')+
4084
- (hit.taskId?'<span class="meta-chip">task: '+esc(hit.taskId)+'</span>':'')+
4085
- '</div>'+
4086
- '</div>';
4087
- }).join(''):'<div class="search-hit-card"><div class="excerpt">'+t('search.noLocal')+'</div></div>')+'</div>'+
4088
- '</div>'+
4089
- '<div class="result-section">'+
4090
- '<div class="result-section-header"><div class="result-section-title">'+t('search.hubResults')+'</div><div class="result-section-sub">'+hubHits.length+' hit(s)</div></div>'+
4091
- '<div class="search-hit-list">'+(hubHits.length?hubHits.map(function(hit,idx){
4092
- return '<div class="hub-hit-card">'+
4093
- '<div class="summary">'+(idx+1)+'. '+esc(hit.summary||'(no summary)')+'</div>'+
4094
- '<div class="excerpt">'+esc(hit.excerpt||'')+'</div>'+
4095
- '<div class="hub-hit-meta">'+
4096
- '<span class="meta-chip">owner: '+esc(hit.ownerName||'unknown')+'</span>'+
4097
- (hit.groupName?'<span class="meta-chip">group: '+esc(hit.groupName)+'</span>':'')+
4098
- '<span class="meta-chip">visibility: '+esc(hit.visibility||'hub')+'</span>'+
4099
- '</div>'+
4100
- '<div class="hub-hit-actions">'+
4101
- '<button class="btn btn-sm" onclick="openSharedMemoryDetail(&quot;'+escAttr(hit.remoteHitId)+'&quot;,&quot;'+escAttr(hit.summary||t('search.sharedMemory'))+'&quot;,&quot;'+escAttr(hit.ownerName||'')+'&quot;,&quot;'+escAttr(hit.groupName||'')+'&quot;)">'+t('search.viewDetail')+'</button>'+
4102
- '</div>'+
4103
- '</div>';
4104
- }).join(''):'<div class="hub-hit-card"><div class="excerpt">'+t('search.noHub')+'</div></div>')+'</div>'+
4105
- '</div>';
4106
- }
4107
-
4108
- async function openSharedMemoryDetail(remoteHitId,title,owner,groupName){
4109
- currentSharedMemoryHitId=remoteHitId;
4110
- document.getElementById('sharedMemoryOverlay').classList.add('show');
4111
- document.getElementById('sharedMemoryTitle').textContent=title||t('search.sharedMemory');
4112
- document.getElementById('sharedMemoryMeta').innerHTML='<span class="meta-item">Hub</span>'+(owner?'<span class="meta-item">'+t('admin.owner')+esc(owner)+'</span>':'')+(groupName?'<span class="meta-item">'+t('admin.group')+esc(groupName)+'</span>':'');
4113
- document.getElementById('sharedMemorySummary').textContent=t('sharing.loading');
4114
- document.getElementById('sharedMemoryContent').textContent='';
4115
- try{
4116
- const r=await fetch('/api/sharing/memory-detail',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({remoteHitId:remoteHitId})});
4117
- const d=await r.json();
4118
- if(d.error) throw new Error(d.error);
4119
- document.getElementById('sharedMemorySummary').textContent=d.summary||'';
4120
- document.getElementById('sharedMemoryContent').textContent=d.content||'';
4121
- }catch(e){
4122
- document.getElementById('sharedMemorySummary').textContent=t('search.loadFailed');
4123
- document.getElementById('sharedMemoryContent').textContent=String(e.message||e);
4124
- }
4125
- }
4126
-
4127
- function closeSharedMemoryDetail(event){
4128
- if(event && event.target!==document.getElementById('sharedMemoryOverlay')) return;
4129
- document.getElementById('sharedMemoryOverlay').classList.remove('show');
4130
- }
4131
-
4132
- async function openHubMemoryDetail(cacheKey,idx){
4133
- var arr=cacheKey==='admin'?adminMemoriesCache:hubMemoriesCache;
4134
- var m=arr[idx];
4135
- if(!m) return;
4136
- var overlay=document.getElementById('sharedMemoryOverlay');
4137
- overlay.classList.add('show');
4138
- document.getElementById('sharedMemoryTitle').textContent=m.summary||m.content?.slice(0,80)||'(no summary)';
4139
- var metaHtml='<span class="meta-item">\\u{1F310} Hub</span>'+
4140
- (m.ownerName?'<span class="meta-item">'+t('admin.owner')+esc(m.ownerName)+'</span>':'')+
4141
- (m.groupName?'<span class="meta-item">'+t('admin.group')+esc(m.groupName)+'</span>':'')+
4142
- (m.kind?'<span class="meta-item">Kind: '+esc(m.kind)+'</span>':'')+
4143
- (m.role?'<span class="meta-item">Role: '+esc(m.role)+'</span>':'')+
4144
- '<span class="meta-item">visibility: '+esc(m.visibility||'hub')+'</span>'+
4145
- '<span class="meta-item">'+new Date(m.updatedAt||m.createdAt||0).toLocaleString(dateLoc())+'</span>';
4146
- document.getElementById('sharedMemoryMeta').innerHTML=metaHtml;
4147
- document.getElementById('sharedMemorySummary').textContent=m.summary||'';
4148
- document.getElementById('sharedMemoryContent').textContent=m.content||t('sharing.loading');
4149
- // try to fetch full content from Hub API
4150
- var remoteId=m.remoteHitId||m.id;
4151
- if(remoteId){
4152
- try{
4153
- var r=await fetch('/api/sharing/memory-detail',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({remoteHitId:remoteId})});
4154
- var d=await r.json();
4155
- if(!d.error&&(d.content||d.summary)){
4156
- if(d.summary) document.getElementById('sharedMemorySummary').textContent=d.summary;
4157
- document.getElementById('sharedMemoryContent').textContent=d.content||m.content||'';
4158
- }
4159
- }catch(e){}
4160
- }
4161
- }
4162
-
4163
- function openHubTaskDetailFromCache(cacheKey,idx){
4164
- var arr=cacheKey==='admin'?adminTasksCache:hubTasksCache;
4165
- var task=arr[idx];
4166
- if(!task) return;
4167
- var overlay=document.getElementById('taskDetailOverlay');
4168
- overlay.classList.add('show');
4169
- document.getElementById('taskDetailTitle').textContent=task.title||'(no title)';
4170
- document.getElementById('taskShareActions').innerHTML='';
4171
- var meta=[
4172
- '<span class="meta-item">\\u{1F310} Hub</span>',
4173
- task.status?'<span class="meta-item"><span class="task-status-badge '+task.status+'">'+esc(task.status)+'</span></span>':'',
4174
- '<span class="meta-item">'+t('admin.owner')+esc(task.ownerName||'unknown')+'</span>',
4175
- task.groupName?'<span class="meta-item">'+t('admin.group')+esc(task.groupName)+'</span>':'',
4176
- '<span class="meta-item">visibility: '+esc(task.visibility||'hub')+'</span>',
4177
- task.chunkCount!=null?'<span class="meta-item">\\u{1F4DD} '+esc(String(task.chunkCount))+' '+t('tasks.chunks.label')+'</span>':'',
4178
- task.startedAt?'<span class="meta-item">\\u{1F4C5} '+formatTime(task.startedAt)+'</span>':'',
4179
- task.endedAt?'<span class="meta-item">\\u2192 '+formatTime(task.endedAt)+'</span>':'',
4180
- (task.updatedAt||task.createdAt)?'<span class="meta-item">'+t('admin.updated')+new Date(task.updatedAt||task.createdAt).toLocaleString(dateLoc())+'</span>':'',
4181
- task.sourceTaskId?'<div style="width:100%;margin-top:4px"><span class="meta-item" style="width:100%">'+t('tasks.taskid')+'<span class="task-id-full">'+esc(task.sourceTaskId)+'</span></span></div>':'',
4182
- ].filter(Boolean);
4183
- document.getElementById('taskDetailMeta').innerHTML=meta.join('');
4184
- document.getElementById('taskSkillSection').innerHTML='';
4185
- document.getElementById('taskSkillSection').className='task-skill-section';
4186
- document.getElementById('taskDetailSummary').innerHTML=task.summary?renderSummaryHtml(task.summary):'<div style="color:var(--text-muted);font-size:13px">'+t('tasks.nochunks')+'</div>';
4187
- document.getElementById('taskDetailChunks').innerHTML='<div style="color:var(--text-muted);padding:12px;font-size:13px">'+t('tasks.nochunks')+'</div>';
4188
- }
4189
-
4190
- function openHubSkillDetailFromCache(cacheKey,idx){
4191
- var arr=cacheKey==='admin'?adminSkillsCache:hubSkillsCache;
4192
- var skill=arr[idx];
4193
- if(!skill) return;
4194
- var overlay=document.getElementById('skillDetailOverlay');
4195
- overlay.classList.add('show');
4196
- document.getElementById('skillDetailTitle').textContent='\\u{1F9E0} '+(skill.name||'(no name)');
4197
- var qs=skill.qualityScore;
4198
- var qsBadge=(qs!==null&&qs!==undefined)?'<span class="meta-item"><span class="skill-badge quality '+(qs>=7?'high':qs>=5?'mid':'low')+'">\\u2605 '+(+qs).toFixed(1)+'/10</span></span>':'';
4199
- var meta=[
4200
- '<span class="meta-item">\\u{1F310} Hub</span>',
4201
- skill.version!=null?'<span class="meta-item"><span class="skill-badge version">v'+skill.version+'</span></span>':'',
4202
- skill.status?'<span class="meta-item"><span class="skill-badge status-'+skill.status+'">'+esc(skill.status)+'</span></span>':'',
4203
- '<span class="meta-item">visibility: '+esc(skill.visibility||'hub')+'</span>',
4204
- qsBadge,
4205
- '<span class="meta-item">'+t('admin.owner')+esc(skill.ownerName||'unknown')+'</span>',
4206
- skill.groupName?'<span class="meta-item">'+t('admin.group')+esc(skill.groupName)+'</span>':'',
4207
- (skill.updatedAt||skill.createdAt)?'<span class="meta-item">'+t('admin.updated')+new Date(skill.updatedAt||skill.createdAt).toLocaleString(dateLoc())+'</span>':'',
4208
- ].filter(Boolean);
4209
- document.getElementById('skillDetailMeta').innerHTML=meta.join('');
4210
- document.getElementById('skillDetailDesc').textContent=skill.description||'';
4211
- document.getElementById('skillFilesList').innerHTML='';
4212
- document.getElementById('skillDetailContent').innerHTML=skill.content?'<pre>'+esc(skill.content)+'</pre>':'';
4213
- document.getElementById('skillVersionsList').innerHTML='';
4214
- document.getElementById('skillRelatedTasks').innerHTML='';
4215
- var visBtn=document.getElementById('skillVisibilityBtn');
4216
- if(visBtn) visBtn.style.display='none';
4217
- var dlBtn=document.getElementById('skillDownloadBtn');
4218
- if(dlBtn) dlBtn.style.display='none';
4219
- var shareBtn=document.getElementById('skillShareActions');
4220
- if(shareBtn) shareBtn.innerHTML='';
4221
- }
4222
-
4223
- function escAttr(s){return String(s||'').replace(/&/g,'&amp;').replace(/'/g,'&#39;').replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
4224
-
4225
- function renderTaskShareActions(task){
4226
- currentTaskDetail=task||null;
4227
- const el=document.getElementById('taskShareActions');
4228
- if(!el){return;}
4229
- if(!task||!task.id){el.innerHTML='';return;}
4230
- const current=(task.sharingVisibility||task.visibility||null);
4231
- const isShared=!!current;
4232
- var statusHtml='';
4233
- if(isShared){
4234
- statusHtml='<span style="display:inline-flex;align-items:center;gap:4px;padding:3px 10px;background:#22c55e22;border:1px solid #22c55e44;border-radius:12px;font-size:12px;color:#22c55e">\u2713 '+t('share.alreadyShared')+'</span>';
4235
- }
4236
- el.innerHTML=statusHtml+
4237
- '<button class="btn btn-sm" onclick="shareCurrentTask()">'+(isShared?t('share.updateBtn'):t('share.shareBtn'))+'</button>'+
4238
- (isShared?'<button class="btn btn-sm btn-ghost" onclick="unshareCurrentTask()">'+t('share.unshareBtn')+'</button>':'');
4239
- }
4240
-
4241
- async function shareCurrentTask(){
4242
- if(!currentTaskDetail) return;
4243
- const visibility='public';
4244
- try{
4245
- const r=await fetch('/api/sharing/tasks/share',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({taskId:currentTaskDetail.id,visibility:visibility})});
4246
- const d=await r.json();
4247
- if(d.ok||d.shared){toast(t('toast.taskShared'),'success');currentTaskDetail.sharingVisibility=visibility;renderTaskShareActions(currentTaskDetail);} else {toast(d.error||t('toast.taskShareFail'),'error');}
4248
- }catch(e){toast(t('toast.taskShareFail')+': '+e.message,'error');}
4249
- }
4250
-
4251
- async function unshareCurrentTask(){
4252
- if(!currentTaskDetail) return;
4253
- try{
4254
- const r=await fetch('/api/sharing/tasks/unshare',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({taskId:currentTaskDetail.id})});
4255
- const d=await r.json();
4256
- if(d.ok||d.unshared){toast(t('toast.taskUnshared'),'success');currentTaskDetail.sharingVisibility=null;renderTaskShareActions(currentTaskDetail);} else {toast(d.error||t('toast.taskUnshareFail'),'error');}
4257
- }catch(e){toast(t('toast.taskUnshareFail')+': '+e.message,'error');}
4258
- }
4259
-
4260
- function renderSkillShareActions(skill){
4261
- const el=document.getElementById('skillShareActions');
4262
- if(!el){return;}
4263
- if(!skill||!skill.id){el.innerHTML='';return;}
4264
- const current=(skill.sharingVisibility||null);
4265
- const isShared=!!current;
4266
- var statusHtml='';
4267
- if(isShared){
4268
- statusHtml='<span style="display:inline-flex;align-items:center;gap:4px;padding:3px 10px;background:#22c55e22;border:1px solid #22c55e44;border-radius:12px;font-size:12px;color:#22c55e">\u2713 '+t('share.alreadyShared')+'</span>';
4269
- }
4270
- el.innerHTML=statusHtml+
4271
- '<button class="btn btn-sm" onclick="shareCurrentSkill()">'+(isShared?t('share.updateBtn'):t('share.shareBtn'))+'</button>'+
4272
- (isShared?'<button class="btn btn-sm btn-ghost" onclick="unshareCurrentSkill()">'+t('share.unshareBtn')+'</button>':'');
4273
- }
4274
-
4275
- async function shareCurrentSkill(){
4276
- if(!currentSkillDetail) return;
4277
- const visibility='public';
4278
- try{
4279
- const r=await fetch('/api/sharing/skills/share',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({skillId:currentSkillDetail.id,visibility:visibility})});
4280
- const d=await r.json();
4281
- if(d.ok){toast(t('toast.skillShared'),'success');currentSkillDetail.sharingVisibility=visibility;renderSkillShareActions(currentSkillDetail);} else {toast(d.error||t('toast.skillShareFail'),'error');}
4282
- }catch(e){toast(t('toast.skillShareFail')+': '+e.message,'error');}
4283
- }
4284
-
4285
- async function unshareCurrentSkill(){
4286
- if(!currentSkillDetail) return;
4287
- try{
4288
- const r=await fetch('/api/sharing/skills/unshare',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({skillId:currentSkillDetail.id})});
4289
- const d=await r.json();
4290
- if(d.ok){toast(t('toast.skillUnshared'),'success');currentSkillDetail.sharingVisibility=null;renderSkillShareActions(currentSkillDetail);} else {toast(d.error||t('toast.skillUnshareFail'),'error');}
4291
- }catch(e){toast(t('toast.skillUnshareFail')+': '+e.message,'error');}
4292
- }
4293
-
4294
- async function shareMemoryPrompt(chunkId){
4295
- try{
4296
- const r=await fetch('/api/sharing/memories/share',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({chunkId:chunkId,visibility:'public'})});
4297
- const d=await r.json();
4298
- if(d.ok){toast(t('toast.memoryShared'),'success');loadMemories();} else {toast(d.error||t('toast.memoryShareFail'),'error');}
4299
- }catch(e){toast(t('toast.memoryShareFail')+': '+e.message,'error');}
4300
- }
4301
-
4302
- async function unshareMemory(chunkId){
4303
- try{
4304
- const r=await fetch('/api/sharing/memories/unshare',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({chunkId:chunkId})});
4305
- const d=await r.json();
4306
- if(d.ok){toast(t('toast.memoryUnshared'),'success');loadMemories();} else {toast(d.error||t('toast.memoryUnshareFail'),'error');}
4307
- }catch(e){toast(t('toast.memoryUnshareFail')+': '+e.message,'error');}
4308
- }
4309
-
4310
- function debounceSkillSearch(){
4311
- clearTimeout(skillSearchTimer);
4312
- skillSearchTimer=setTimeout(function(){loadSkills();},300);
4313
- }
4314
-
4315
- async function pullHubSkill(skillId){
4316
- try{
4317
- const r=await fetch('/api/sharing/skills/pull',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({skillId:skillId})});
4318
- const d=await r.json();
4319
- if(d.ok||d.pulled||d.details){toast(t('toast.skillPulled'),'success');loadSkills();} else {toast(d.error||t('toast.skillPullFail'),'error');}
4320
- }catch(e){toast(t('toast.skillPullFail')+': '+e.message,'error');}
4321
- }
4322
-
4323
- // ─── Logs ───
4324
- let logAutoTimer=null;
4325
- let logPage=1;
4326
- const LOG_PAGE_SIZE=20;
4327
- async function loadLogs(page){
4328
- if(typeof page==='number') logPage=page;
4329
- try{
4330
- const toolFilter=document.getElementById('logToolFilter').value;
4331
- const offset=(logPage-1)*LOG_PAGE_SIZE;
4332
- const url='/api/logs?limit='+LOG_PAGE_SIZE+'&offset='+offset+(toolFilter?'&tool='+encodeURIComponent(toolFilter):'');
4333
- const [logsRes,toolsRes]=await Promise.all([fetch(url),fetch('/api/log-tools')]);
4334
- if(!logsRes.ok) return;
4335
- const logsData=await logsRes.json();
4336
- const toolsData=await toolsRes.json();
4337
- renderLogToolFilter(toolsData.tools||[],toolFilter);
4338
- renderLogs(logsData.logs||[]);
4339
- renderLogPagination(logsData.page||1,logsData.totalPages||1,logsData.total||0);
4340
- startLogAutoRefresh();
4341
- }catch(e){console.error('loadLogs',e)}
4342
- }
4343
- function onLogFilterChange(){logPage=1;loadLogs(1);}
4344
- function renderLogPagination(page,totalPages,total){
4345
- const el=document.getElementById('logsPagination');
4346
- if(!el||totalPages<=1){if(el)el.innerHTML='';return;}
4347
- const pages=[];
4348
- const range=2;
4349
- for(let i=1;i<=totalPages;i++){
4350
- if(i===1||i===totalPages||Math.abs(i-page)<=range){
4351
- pages.push(i);
4352
- }else if(pages[pages.length-1]!=='...'){
4353
- pages.push('...');
4354
- }
4355
- }
4356
- let html='<div class="logs-pagination">';
4357
- html+='<button class="btn btn-sm btn-ghost" '+(page<=1?'disabled':'')+' onclick="loadLogs('+(page-1)+')">\u2039</button>';
4358
- pages.forEach(p=>{
4359
- if(p==='...'){html+='<span class="page-ellipsis">\u2026</span>';}
4360
- else{html+='<button class="btn btn-sm '+(p===page?'btn-primary':'btn-ghost')+'" onclick="loadLogs('+p+')">'+p+'</button>';}
4361
- });
4362
- html+='<button class="btn btn-sm btn-ghost" '+(page>=totalPages?'disabled':'')+' onclick="loadLogs('+(page+1)+')">\u203A</button>';
4363
- html+='<span class="page-total">'+total+' total</span>';
4364
- html+='</div>';
4365
- el.innerHTML=html;
4366
- }
4367
-
4368
- function renderLogToolFilter(tools,current){
4369
- const sel=document.getElementById('logToolFilter');
4370
- const opts=['<option value="">'+t('logs.allTools')+'</option>'];
4371
- tools.forEach(tn=>{
4372
- opts.push('<option value="'+tn+'"'+(tn===current?' selected':'')+'>'+tn+'</option>');
4373
- });
4374
- sel.innerHTML=opts.join('');
4375
- }
4376
-
4377
- function formatLogTime(ts){
4378
- const d=new Date(ts);
4379
- const time=d.toLocaleTimeString(dateLoc(),{hour:'2-digit',minute:'2-digit',second:'2-digit',hour12:false});
4380
- const y=d.getFullYear();
4381
- const m=String(d.getMonth()+1).padStart(2,'0');
4382
- const day=String(d.getDate()).padStart(2,'0');
4383
- return y+'-'+m+'-'+day+' '+time;
4384
- }
4385
-
4386
- function parseMemoryAddEntries(out){
4387
- var lines=out.split('\\n');
4388
- var results=[];
4389
- for(var i=0;i<lines.length;i++){
4390
- var line=lines[i].trim();
4391
- if(!line) continue;
4392
- if(line.startsWith('{')){
4393
- try{
4394
- var obj=JSON.parse(line);
4395
- if(obj.role&&obj.action){results.push({role:obj.role,action:obj.action,summary:obj.summary||'',content:obj.content||'',reason:obj.reason||''});continue;}
4396
- }catch(e){}
4397
- }
4398
- var rm=line.match(/^\\[(\\w+)\\]\\s*([^\u2192]+)\u2192/);
4399
- if(rm){
4400
- var role=rm[1],actionRaw=rm[2].trim();
4401
- var action='stored';
4402
- if(actionRaw.indexOf('exact-dup')>=0||actionRaw.indexOf('\u23ED')>=0) action='exact-dup';
4403
- else if(actionRaw.indexOf('dedup')>=0||actionRaw.indexOf('\uD83D\uDD01')>=0) action='dedup';
4404
- else if(actionRaw.indexOf('merged')>=0||actionRaw.indexOf('\uD83D\uDD00')>=0) action='merged';
4405
- else if(actionRaw.indexOf('error')>=0||actionRaw.indexOf('\u274C')>=0) action='error';
4406
- var afterArrow=line.replace(/^\\[\\w+\\]\\s*[^\u2192]+\u2192\\s*/,'');
4407
- var contentLines=[afterArrow];
4408
- while(i+1<lines.length&&!lines[i+1].trim().startsWith('[')&&!lines[i+1].trim().startsWith('{')){
4409
- i++;
4410
- if(lines[i].trim()) contentLines.push(lines[i]);
4411
- else contentLines.push('');
4412
- }
4413
- results.push({role:role,action:action,summary:'',content:contentLines.join('\\n'),reason:''});
4414
- }
4415
- }
4416
- return results;
2268
+ return results;
4417
2269
  }
4418
2270
 
4419
2271
  function buildLogSummary(lg){
@@ -4637,7 +2489,6 @@ function setMetricsDays(d){
4637
2489
  }
4638
2490
 
4639
2491
  async function loadMetrics(){
4640
- try{
4641
2492
  const r=await fetch('/api/metrics?days='+metricsDays);
4642
2493
  const d=await r.json();
4643
2494
  document.getElementById('mTotal').textContent=formatNum(d.totals.memories);
@@ -4646,11 +2497,9 @@ async function loadMetrics(){
4646
2497
  document.getElementById('mEmbeddings').textContent=formatNum(d.totals.embeddings);
4647
2498
  renderChartWrites(d.writesPerDay);
4648
2499
  loadToolMetrics();
4649
- }catch(e){console.error('loadMetrics',e)}
4650
2500
  }
4651
2501
 
4652
2502
  function formatNum(n){return n>=1e6?(n/1e6).toFixed(1)+'M':n>=1e3?(n/1e3).toFixed(1)+'k':String(n);}
4653
- function dateLoc(){return curLang==='zh'?'zh-CN':'en-US';}
4654
2503
 
4655
2504
  /* ─── Tasks View Logic ─── */
4656
2505
  let tasksStatusFilter='';
@@ -4666,24 +2515,29 @@ function setTaskStatusFilter(btn,status){
4666
2515
  }
4667
2516
 
4668
2517
  async function loadTasks(){
4669
- const scope=document.getElementById('taskSearchScope')?document.getElementById('taskSearchScope').value:taskSearchScope;
4670
- taskSearchScope=scope||'local';
4671
- if(taskSearchScope!=='local'){ return loadHubTasks(); }
4672
2518
  const list=document.getElementById('tasksList');
4673
2519
  list.innerHTML='<div class="spinner"></div>';
4674
2520
  try{
4675
2521
  const params=new URLSearchParams({limit:String(TASKS_PER_PAGE),offset:String(tasksPage*TASKS_PER_PAGE)});
4676
2522
  if(tasksStatusFilter) params.set('status',tasksStatusFilter);
4677
- const [data,allD,activeD,compD,skipD]=await Promise.all([
4678
- fetch('/api/tasks?'+params).then(r=>r.json()),
4679
- fetch('/api/tasks?limit=1&offset=0').then(r=>r.json()),
4680
- fetch('/api/tasks?status=active&limit=1&offset=0').then(r=>r.json()),
4681
- fetch('/api/tasks?status=completed&limit=1&offset=0').then(r=>r.json()),
4682
- fetch('/api/tasks?status=skipped&limit=1&offset=0').then(r=>r.json())
4683
- ]);
2523
+ const r=await fetch('/api/tasks?'+params);
2524
+ const data=await r.json();
2525
+
2526
+ // stats
2527
+ const allR=await fetch('/api/tasks?limit=1&offset=0');
2528
+ const allD=await allR.json();
4684
2529
  document.getElementById('tasksTotalCount').textContent=formatNum(allD.total);
2530
+
2531
+ const activeR=await fetch('/api/tasks?status=active&limit=1&offset=0');
2532
+ const activeD=await activeR.json();
4685
2533
  document.getElementById('tasksActiveCount').textContent=formatNum(activeD.total);
2534
+
2535
+ const compR=await fetch('/api/tasks?status=completed&limit=1&offset=0');
2536
+ const compD=await compR.json();
4686
2537
  document.getElementById('tasksCompletedCount').textContent=formatNum(compD.total);
2538
+
2539
+ const skipR=await fetch('/api/tasks?status=skipped&limit=1&offset=0');
2540
+ const skipD=await skipR.json();
4687
2541
  document.getElementById('tasksSkippedCount').textContent=formatNum(skipD.total);
4688
2542
 
4689
2543
  if(!data.tasks||data.tasks.length===0){
@@ -4749,16 +2603,13 @@ async function openTaskDetail(taskId){
4749
2603
  document.getElementById('taskSkillSection').className='task-skill-section';
4750
2604
  document.getElementById('taskDetailSummary').textContent='';
4751
2605
  document.getElementById('taskDetailChunks').innerHTML='<div class="spinner"></div>';
4752
- document.getElementById('taskShareActions').innerHTML='';
4753
2606
  document.getElementById('taskDetailActions').innerHTML='';
4754
2607
 
4755
2608
  try{
4756
2609
  const r=await fetch('/api/task/'+taskId);
4757
2610
  const task=await r.json();
4758
- currentTaskDetail=task;
4759
2611
 
4760
2612
  document.getElementById('taskDetailTitle').textContent=task.title||t('tasks.untitled');
4761
- renderTaskShareActions(task);
4762
2613
 
4763
2614
  const meta=[
4764
2615
  '<span class="meta-item"><span class="task-status-badge '+task.status+'">'+t('tasks.status.'+task.status)+'</span></span>',
@@ -4927,177 +2778,60 @@ function setSkillStatusFilter(btn,status){
4927
2778
 
4928
2779
  async function loadSkills(){
4929
2780
  const list=document.getElementById('skillsList');
4930
- const hubList=document.getElementById('hubSkillsList');
4931
2781
  list.innerHTML='<div class="spinner"></div>';
4932
- var hubSection=document.getElementById('hubSkillsSection');
4933
- if(hubList){
4934
- if(skillSearchScope==='local'){
4935
- if(hubSection) hubSection.style.display='none';
4936
- }else{
4937
- if(hubSection) hubSection.style.display='block';
4938
- hubList.innerHTML='<div class="spinner"></div>';
4939
- }
4940
- }
4941
-
4942
- const query=(document.getElementById('skillSearchInput')?.value||'').trim();
4943
- const scope=document.getElementById('skillSearchScope') ? document.getElementById('skillSearchScope').value : skillSearchScope;
4944
- skillSearchScope=scope||'local';
4945
-
4946
2782
  try{
4947
2783
  const params=new URLSearchParams();
4948
2784
  if(skillsStatusFilter) params.set('status',skillsStatusFilter);
4949
2785
  const visFilter=document.getElementById('skillVisibilityFilter')?.value;
4950
2786
  if(visFilter) params.set('visibility',visFilter);
2787
+ const r=await fetch('/api/skills?'+params);
2788
+ const data=await r.json();
4951
2789
 
4952
- const localRes=await fetch('/api/skills?'+params.toString());
4953
- const localData=await localRes.json();
4954
- let localSkills=Array.isArray(localData.skills)?localData.skills:[];
4955
- if(query){
4956
- const q=query.toLowerCase();
4957
- localSkills=localSkills.filter(skill=>{
4958
- const haystack=[skill.name,skill.description,skill.tags].filter(Boolean).join(' ').toLowerCase();
4959
- return haystack.includes(q);
4960
- });
4961
- }
2790
+ document.getElementById('skillsTotalCount').textContent=formatNum(data.skills.length);
2791
+ document.getElementById('skillsActiveCount').textContent=formatNum(data.skills.filter(s=>s.status==='active').length);
2792
+ document.getElementById('skillsDraftCount').textContent=formatNum(data.skills.filter(s=>s.status==='draft').length);
2793
+ document.getElementById('skillsInstalledCount').textContent=formatNum(data.skills.filter(s=>s.installed).length);
2794
+ document.getElementById('skillsPublicCount').textContent=formatNum(data.skills.filter(s=>s.visibility==='public').length);
4962
2795
 
4963
- const renderLocalCards=function(skills){
4964
- if(!skills||skills.length===0){
4965
- return '<div style="text-align:center;padding:48px;color:var(--text-muted);font-size:14px">'+(query?t('skills.search.noresult'):t('skills.empty'))+'</div>';
4966
- }
4967
- return skills.map(skill=>{
4968
- const timeStr=formatTime(skill.createdAt);
4969
- const tags=parseTags(skill.tags);
4970
- const installedClass=skill.installed?'installed':'';
4971
- const statusClass=skill.status==='archived'?'archived':(skill.status==='draft'?'draft':'');
4972
- const sourceLabel=tags.includes('hub-import')?'hub-import':skill.sourceType;
4973
- const qs=skill.qualityScore;
4974
- const qsBadge=qs!==null&&qs!==undefined?'<span class="skill-badge quality '+(qs>=7?'high':qs>=5?'mid':'low')+'">★ '+qs.toFixed(1)+'</span>':'';
4975
- const visBadge=skill.visibility==='public'?'<span class="skill-badge visibility-public">🌐 '+t('skills.visibility.public')+'</span>':'';
4976
- return '<div class="skill-card '+installedClass+' '+statusClass+'" onclick="openSkillDetail(&quot;'+escAttr(skill.id)+'&quot;)">'+
4977
- '<div class="skill-card-top">'+
4978
- '<div class="skill-card-name">🧠 '+esc(skill.name)+'</div>'+
4979
- '<div class="skill-card-badges">'+
4980
- qsBadge+
4981
- '<span class="skill-badge version">v'+skill.version+'</span>'+
4982
- visBadge+
4983
- (skill.installed?'<span class="skill-badge installed">'+t('skills.installed.badge')+'</span>':'')+
4984
- '<span class="skill-badge status-'+skill.status+'">'+t('skills.status.'+skill.status)+'</span>'+
4985
- '</div>'+
4986
- '</div>'+
4987
- '<div class="skill-card-desc">'+esc(skill.description)+'</div>'+
4988
- '<div class="skill-card-bottom">'+
4989
- '<span class="tag"><span class="icon">📅</span> '+timeStr+'</span>'+
4990
- '<span class="tag"><span class="icon">📦</span> '+sourceLabel+'</span>'+
4991
- (tags.length>0?'<div class="skill-card-tags">'+tags.map(t=>'<span class="skill-tag">'+esc(t)+'</span>').join('')+'</div>':'')+
4992
- '</div>'+
4993
- '</div>';
4994
- }).join('');
4995
- };
4996
-
4997
- list.innerHTML=renderLocalCards(localSkills);
4998
-
4999
- if(skillSearchScope==='local'){
5000
- if(hubSection) hubSection.style.display='none';
5001
- document.getElementById('skillSearchMeta').textContent=query?(t('skills.search.local')+' '+localSkills.length):'';
5002
- document.getElementById('skillsTotalCount').textContent=formatNum(localSkills.length);
5003
- document.getElementById('skillsActiveCount').textContent=formatNum(localSkills.filter(s=>s.status==='active').length);
5004
- document.getElementById('skillsDraftCount').textContent=formatNum(localSkills.filter(s=>s.status==='draft').length);
5005
- document.getElementById('skillsInstalledCount').textContent=formatNum(localSkills.filter(s=>s.installed).length);
5006
- document.getElementById('skillsPublicCount').textContent=formatNum(localSkills.filter(s=>s.visibility==='public').length);
5007
- return;
5008
- }
5009
-
5010
- if(!query){
5011
- if(hubSection) hubSection.style.display='block';
5012
- if(hubList){ loadHubSkills(hubList); }
5013
- document.getElementById('skillSearchMeta').textContent=t('skills.search.local')+' '+localSkills.length;
5014
- document.getElementById('skillsTotalCount').textContent=formatNum(localSkills.length);
5015
- document.getElementById('skillsActiveCount').textContent=formatNum(localSkills.filter(s=>s.status==='active').length);
5016
- document.getElementById('skillsDraftCount').textContent=formatNum(localSkills.filter(s=>s.status==='draft').length);
5017
- document.getElementById('skillsInstalledCount').textContent=formatNum(localSkills.filter(s=>s.installed).length);
5018
- document.getElementById('skillsPublicCount').textContent=formatNum(localSkills.filter(s=>s.visibility==='public').length);
2796
+ if(!data.skills||data.skills.length===0){
2797
+ list.innerHTML='<div style="text-align:center;padding:48px;color:var(--text-muted);font-size:14px">'+t('skills.empty')+'</div>';
5019
2798
  return;
5020
2799
  }
5021
2800
 
5022
- const sharingParams=new URLSearchParams();
5023
- sharingParams.set('query',query);
5024
- sharingParams.set('scope',skillSearchScope);
5025
- sharingParams.set('maxResults','20');
5026
- const r=await fetch('/api/sharing/search/skills?'+sharingParams.toString());
5027
- const data=await r.json();
5028
- const localHits=(data.local&&Array.isArray(data.local.hits))?data.local.hits:[];
5029
- const hubHits=(data.hub&&Array.isArray(data.hub.hits))?data.hub.hits:[];
5030
-
5031
- list.innerHTML=localHits.length?localHits.map(function(skill){
5032
- return '<div class="hub-skill-card" onclick="openSkillDetail(&quot;'+escAttr(skill.skillId)+'&quot;)">'+
5033
- '<div class="summary">'+esc(skill.name)+'</div>'+
5034
- '<div class="excerpt">'+esc(skill.description||'')+'</div>'+
5035
- '<div class="hub-skill-meta"><span class="meta-chip">visibility: '+esc(skill.visibility||'private')+'</span><span class="meta-chip">owner: '+esc(skill.owner||'agent:main')+'</span></div>'+
5036
- '</div>';
5037
- }).join(''):'<div style="text-align:center;padding:48px;color:var(--text-muted);font-size:14px">'+t('skills.search.noresult')+'</div>';
5038
-
5039
- if(hubList){
5040
- if(hubSection) hubSection.style.display=hubHits.length?'block':'none';
5041
- hubList.innerHTML=hubHits.length?hubHits.map(function(skill){
5042
- return '<div class="hub-skill-card">'+
5043
- '<div class="summary">'+esc(skill.name)+'</div>'+
5044
- '<div class="excerpt">'+esc(skill.description||'')+'</div>'+
5045
- '<div class="hub-skill-meta">'+
5046
- '<span class="meta-chip">owner: '+esc(skill.ownerName||'unknown')+'</span>'+
5047
- (skill.groupName?'<span class="meta-chip">group: '+esc(skill.groupName)+'</span>':'')+
5048
- '<span class="meta-chip">visibility: '+esc(skill.visibility||'hub')+'</span>'+
5049
- (skill.version!=null?'<span class="meta-chip">v'+skill.version+'</span>':'')+
2801
+ list.innerHTML=data.skills.map(skill=>{
2802
+ const timeStr=formatTime(skill.createdAt);
2803
+ const tags=parseTags(skill.tags);
2804
+ const installedClass=skill.installed?'installed':'';
2805
+ const statusClass=skill.status==='archived'?'archived':(skill.status==='draft'?'draft':'');
2806
+ const qs=skill.qualityScore;
2807
+ const qsBadge=qs!==null&&qs!==undefined?'<span class="skill-badge quality '+(qs>=7?'high':qs>=5?'mid':'low')+'">\\u2605 '+qs.toFixed(1)+'</span>':'';
2808
+ const visBadge=skill.visibility==='public'?'<span class="skill-badge visibility-public">\\u{1F310} '+t('skills.visibility.public')+'</span>':'';
2809
+ return '<div class="skill-card '+installedClass+' '+statusClass+'" onclick="openSkillDetail(\\''+skill.id+'\\')">'+
2810
+ '<div class="skill-card-top">'+
2811
+ '<div class="skill-card-name">\\u{1F9E0} '+esc(skill.name)+'</div>'+
2812
+ '<div class="skill-card-badges">'+
2813
+ qsBadge+
2814
+ '<span class="skill-badge version">v'+skill.version+'</span>'+
2815
+ visBadge+
2816
+ (skill.installed?'<span class="skill-badge installed">'+t('skills.installed.badge')+'</span>':'')+
2817
+ '<span class="skill-badge status-'+skill.status+'">'+t('skills.status.'+skill.status)+'</span>'+
5050
2818
  '</div>'+
5051
- '<div class="hub-skill-actions"><button class="btn btn-sm" onclick="event.stopPropagation();pullHubSkill(&quot;'+escAttr(skill.skillId)+'&quot;)">Pull to Local</button></div>'+
5052
- '</div>';
5053
- }).join(''):'';
5054
- }
5055
-
5056
- document.getElementById('skillSearchMeta').textContent=t('skills.search.local')+' '+localHits.length+(hubHits.length?' · Hub '+hubHits.length:'');
5057
- document.getElementById('skillsTotalCount').textContent=formatNum(localHits.length+hubHits.length);
5058
- document.getElementById('skillsActiveCount').textContent=formatNum(localHits.length);
5059
- document.getElementById('skillsDraftCount').textContent='0';
5060
- document.getElementById('skillsInstalledCount').textContent='-';
5061
- document.getElementById('skillsPublicCount').textContent=formatNum(hubHits.filter(function(s){return s.visibility==='public';}).length);
5062
- }catch(e){
5063
- list.innerHTML='<div style="text-align:center;padding:24px;color:var(--rose)">'+t('skills.load.error')+': '+esc(String(e))+'</div>';
5064
- if(hubList){
5065
- hubList.innerHTML='<div style="text-align:center;padding:24px;color:var(--rose)">'+t('skills.load.error')+'</div>';
5066
- }
5067
- }
5068
- }
5069
-
5070
- async function loadHubSkills(hubList){
5071
- if(!hubList) hubList=document.getElementById('hubSkillsList');
5072
- if(!hubList) return;
5073
- var hubSection=document.getElementById('hubSkillsSection');
5074
- hubList.innerHTML='<div class="spinner"></div>';
5075
- try{
5076
- const r=await fetch('/api/sharing/skills/list?limit=40');
5077
- const d=await r.json();
5078
- const skills=Array.isArray(d.skills)?d.skills:[];
5079
- hubSkillsCache=skills;
5080
- if(!skills.length){
5081
- if(hubSection) hubSection.style.display='none';
5082
- return;
5083
- }
5084
- if(hubSection) hubSection.style.display='block';
5085
- hubList.innerHTML=skills.map(function(skill,idx){
5086
- return '<div class="hub-skill-card" onclick="openHubSkillDetailFromCache(\\\'hub\\\',' +idx+')" style="cursor:pointer">'+
5087
- '<div class="summary">'+esc(skill.name)+'</div>'+
5088
- '<div class="excerpt">'+esc(skill.description||'')+'</div>'+
5089
- '<div class="hub-skill-meta">'+
5090
- '<span class="meta-chip">owner: '+esc(skill.ownerName||'unknown')+'</span>'+
5091
- (skill.groupName?'<span class="meta-chip">group: '+esc(skill.groupName)+'</span>':'')+
5092
- '<span class="meta-chip">visibility: '+esc(skill.visibility||'hub')+'</span>'+
5093
- (skill.version!=null?'<span class="meta-chip">v'+skill.version+'</span>':'')+
5094
2819
  '</div>'+
5095
- '<div class="hub-skill-actions"><button class="btn btn-sm" onclick="event.stopPropagation();pullHubSkill(\\''+escAttr(skill.sourceSkillId)+'\\')">Pull to Local</button></div>'+
2820
+ '<div class="skill-card-desc">'+esc(skill.description)+'</div>'+
2821
+ '<div class="skill-card-bottom">'+
2822
+ '<span class="tag"><span class="icon">\\u{1F4C5}</span> '+timeStr+'</span>'+
2823
+ '<span class="tag"><span class="icon">\\u{1F4E6}</span> '+skill.sourceType+'</span>'+
2824
+ (tags.length>0?'<div class="skill-card-tags">'+tags.map(t=>'<span class="skill-tag">'+esc(t)+'</span>').join('')+'</div>':'')+
2825
+ '</div>'+
2826
+ '<div class="card-actions" onclick="event.stopPropagation()">'+
2827
+ '<button class="btn btn-sm btn-ghost" onclick="openSkillDetail(\\''+skill.id+'\\')">'+t('card.expand')+'</button>'+
2828
+ (skill.visibility==='public'?'<button class="btn btn-sm btn-ghost" onclick="toggleSkillPublic(\\''+skill.id+'\\',false)">\\u{1F512} '+t('skills.setPrivate')+'</button>':'<button class="btn btn-sm btn-ghost" onclick="toggleSkillPublic(\\''+skill.id+'\\',true)">\\u{1F310} '+t('skills.setPublic')+'</button>')+
2829
+ '<button class="btn btn-sm btn-ghost" style="color:var(--accent)" onclick="deleteSkill(\\''+skill.id+'\\')">'+t('skill.delete')+'</button>'+
2830
+ '</div>'+
5096
2831
  '</div>';
5097
2832
  }).join('');
5098
2833
  }catch(e){
5099
- if(hubSection) hubSection.style.display='none';
5100
- hubList.innerHTML='';
2834
+ list.innerHTML='<div style="text-align:center;padding:24px;color:var(--rose)">Failed to load skills: '+esc(String(e))+'</div>';
5101
2835
  }
5102
2836
  }
5103
2837
 
@@ -5106,7 +2840,6 @@ function parseTags(tagsStr){
5106
2840
  }
5107
2841
 
5108
2842
  let currentSkillId='';
5109
- let currentSkillDetail=null;
5110
2843
 
5111
2844
  async function openSkillDetail(skillId){
5112
2845
  currentSkillId=skillId;
@@ -5119,8 +2852,6 @@ async function openSkillDetail(skillId){
5119
2852
  document.getElementById('skillDetailContent').innerHTML='<div class="spinner"></div>';
5120
2853
  document.getElementById('skillVersionsList').innerHTML='<div class="spinner"></div>';
5121
2854
  document.getElementById('skillRelatedTasks').innerHTML='';
5122
- var vb=document.getElementById('skillVisibilityBtn');if(vb)vb.style.display='';
5123
- var db=document.getElementById('skillDownloadBtn');if(db)db.style.display='';
5124
2855
  document.getElementById('skillDetailActions').innerHTML='';
5125
2856
 
5126
2857
  try{
@@ -5166,8 +2897,6 @@ async function openSkillDetail(skillId){
5166
2897
  }
5167
2898
 
5168
2899
  document.getElementById('skillDetailDesc').textContent=skill.description;
5169
- currentSkillDetail=skill;
5170
- renderSkillShareActions(skill);
5171
2900
 
5172
2901
  if(files.length>0){
5173
2902
  const fileIcons={'skill':'\\u{1F4D6}','script':'\\u{2699}','reference':'\\u{1F4CE}','file':'\\u{1F4C4}'};
@@ -5375,9 +3104,6 @@ function timeAgo(ts){
5375
3104
  }
5376
3105
 
5377
3106
  /* ─── Settings / Config ─── */
5378
- function syncHostToggles(){}
5379
- function onProviderChange(){}
5380
-
5381
3107
  async function loadConfig(){
5382
3108
  try{
5383
3109
  const r=await fetch('/api/config');
@@ -5412,26 +3138,6 @@ async function loadConfig(){
5412
3138
 
5413
3139
  const tel=cfg.telemetry||{};
5414
3140
  document.getElementById('cfgTelemetryEnabled').checked=tel.enabled!==false;
5415
-
5416
- const sharing=cfg.sharing||{};
5417
- const caps=sharing.capabilities||{};
5418
- const embProv=(cfg.embedding||{}).provider;
5419
- const sumProv=(cfg.summarizer||{}).provider;
5420
- const skProv=((cfg.skillEvolution||{}).summarizer||{}).provider;
5421
-
5422
-
5423
- document.getElementById('cfgSharingEnabled').checked=!!sharing.enabled;
5424
- _sharingRole=sharing.role||'client';
5425
- var hub=sharing.hub||{};
5426
- var client=sharing.client||{};
5427
- document.getElementById('cfgHubPort').value=hub.port||18800;
5428
- document.getElementById('cfgHubTeamName').value=hub.teamName||'';
5429
- document.getElementById('cfgHubTeamToken').value=hub.teamToken||'';
5430
- document.getElementById('cfgClientHubAddress').value=client.hubAddress||'';
5431
- document.getElementById('cfgClientTeamToken').value=client.teamToken||'';
5432
- document.getElementById('cfgClientUserToken').value=client.userToken||'';
5433
- onSharingToggle();
5434
- updateHubShareInfo();
5435
3141
  }catch(e){
5436
3142
  console.error('loadConfig error',e);
5437
3143
  }
@@ -5464,36 +3170,9 @@ function onProviderChange(section){
5464
3170
  if(m[2]==='chat'&&def.chatModel&&!mdEl.value.trim()) mdEl.value=def.chatModel;
5465
3171
  }
5466
3172
 
5467
- function flashSaved(id){
5468
- var el=document.getElementById(id);
5469
- if(!el)return;
5470
- el.classList.add('show');
5471
- setTimeout(function(){el.classList.remove('show');},2500);
5472
- }
5473
-
5474
- async function doSaveConfig(cfg, btnEl, savedId){
5475
- btnEl.disabled=true;btnEl.textContent=t('settings.test.loading');
5476
- function done(){btnEl.disabled=false;btnEl.textContent=t('settings.save');}
5477
- try{
5478
- const r=await fetch('/api/config',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(cfg)});
5479
- if(r.status===401){done();toast(t('settings.session.expired'),'error');return false;}
5480
- if(!r.ok) throw new Error(await r.text());
5481
- flashSaved(savedId);
5482
- toast(t('settings.saved'),'success');
5483
- done();
5484
- return true;
5485
- }catch(e){
5486
- toast(t('settings.save.fail')+': '+e.message,'error');
5487
- done();
5488
- return false;
5489
- }
5490
- }
5491
-
5492
- async function saveModelsConfig(){
5493
- var card=document.querySelector('.card-models');
5494
- var saveBtn=card.querySelector('.settings-actions .btn-primary');
3173
+ async function saveConfig(){
3174
+ var saveBtn=document.querySelector('.settings-actions .btn-primary');
5495
3175
  saveBtn.disabled=true;saveBtn.textContent=t('settings.test.loading');
5496
- function done(){saveBtn.disabled=false;saveBtn.textContent=t('settings.save');}
5497
3176
 
5498
3177
  const cfg={};
5499
3178
  const embP=document.getElementById('cfgEmbProvider').value;
@@ -5534,8 +3213,16 @@ async function saveModelsConfig(){
5534
3213
  if(skApiKey) cfg.skillEvolution.summarizer.apiKey=skApiKey;
5535
3214
  }
5536
3215
 
3216
+ const vp=document.getElementById('cfgViewerPort').value.trim();
3217
+ if(vp) cfg.viewerPort=Number(vp);
3218
+ cfg.telemetry={enabled:document.getElementById('cfgTelemetryEnabled').checked};
3219
+
3220
+ function done(){saveBtn.disabled=false;saveBtn.textContent=t('settings.save');}
3221
+
3222
+ // 1) Embedding model is required
5537
3223
  if(!embP||embP===''){done();toast(t('settings.save.emb.required'),'error');return;}
5538
3224
 
3225
+ // 2) Test embedding
5539
3226
  try{
5540
3227
  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||''})});
5541
3228
  if(er.status===401){done();toast(t('settings.session.expired'),'error');return;}
@@ -5544,6 +3231,7 @@ async function saveModelsConfig(){
5544
3231
  document.getElementById('testEmbResult').className='test-result ok';document.getElementById('testEmbResult').innerHTML='\\u2705 '+t('settings.test.ok');
5545
3232
  }catch(e){done();toast(t('settings.save.emb.fail')+': '+e.message,'error');return;}
5546
3233
 
3234
+ // 3) Test summarizer if user filled it
5547
3235
  if(hasSumConfig&&cfg.summarizer){
5548
3236
  try{
5549
3237
  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||''})});
@@ -5554,6 +3242,7 @@ async function saveModelsConfig(){
5554
3242
  }catch(e){done();toast(t('settings.save.sum.fail')+': '+e.message,'error');return;}
5555
3243
  }
5556
3244
 
3245
+ // 4) Test skill model if user filled it
5557
3246
  if(hasSkillConfig&&cfg.skillEvolution.summarizer){
5558
3247
  try{
5559
3248
  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||''})});
@@ -5564,6 +3253,7 @@ async function saveModelsConfig(){
5564
3253
  }catch(e){done();toast(t('settings.save.skill.fail')+': '+e.message,'error');return;}
5565
3254
  }
5566
3255
 
3256
+ // 5) If summarizer or skill model not configured, check OpenClaw fallback and confirm
5567
3257
  if(!hasSumConfig||!hasSkillConfig){
5568
3258
  try{
5569
3259
  var fr=await fetch('/api/fallback-model');
@@ -5577,86 +3267,17 @@ async function saveModelsConfig(){
5577
3267
  }catch(e){}
5578
3268
  }
5579
3269
 
5580
- await doSaveConfig(cfg, saveBtn, 'modelsSaved');
5581
- }
5582
-
5583
- async function saveHubConfig(){
5584
- var card=document.getElementById('settingsSharingConfig');
5585
- var saveBtn=card.querySelector('.settings-actions .btn-primary');
5586
- saveBtn.disabled=true;saveBtn.textContent=t('settings.test.loading');
5587
- function done(){saveBtn.disabled=false;saveBtn.textContent=t('settings.save');}
5588
-
5589
- const cfg={};
5590
- var sharingEnabled=document.getElementById('cfgSharingEnabled').checked;
5591
- cfg.sharing={
5592
- enabled:sharingEnabled,
5593
- role:_sharingRole,
5594
- capabilities:{}
5595
- };
5596
- if(sharingEnabled&&_sharingRole==='hub'){
5597
- var hubPort=document.getElementById('cfgHubPort').value.trim();
5598
- var hubTeamName=document.getElementById('cfgHubTeamName').value.trim();
5599
- var hubTeamToken=document.getElementById('cfgHubTeamToken').value.trim();
5600
- cfg.sharing.hub={port:hubPort?Number(hubPort):18800};
5601
- if(hubTeamName) cfg.sharing.hub.teamName=hubTeamName;
5602
- if(hubTeamToken) cfg.sharing.hub.teamToken=hubTeamToken;
5603
- cfg.sharing.client={hubAddress:'',userToken:'',teamToken:''};
5604
- }
5605
- if(sharingEnabled&&_sharingRole==='client'){
5606
- var clientAddr=document.getElementById('cfgClientHubAddress').value.trim();
5607
- var clientTeamToken=document.getElementById('cfgClientTeamToken').value.trim();
5608
- var clientUserToken=document.getElementById('cfgClientUserToken').value.trim();
5609
- cfg.sharing.client={};
5610
- if(clientAddr) cfg.sharing.client.hubAddress=clientAddr;
5611
- if(clientTeamToken) cfg.sharing.client.teamToken=clientTeamToken;
5612
- if(clientUserToken) cfg.sharing.client.userToken=clientUserToken;
5613
- cfg.sharing.hub={port:18800,teamName:'',teamToken:''};
5614
- if(clientAddr){
5615
- try{
5616
- var ips=await fetch('/api/local-ips').then(function(r){return r.json();});
5617
- var localAddrs=['127.0.0.1','localhost','0.0.0.0'].concat(ips.ips||[]);
5618
- var parsed=new URL(clientAddr.indexOf('://')>-1?clientAddr:'http://'+clientAddr);
5619
- if(localAddrs.indexOf(parsed.hostname)>=0){
5620
- done();toast(t('sharing.cannotJoinSelf'),'error');return;
5621
- }
5622
- }catch(e){}
5623
- }
5624
- }
5625
-
5626
- var prevSharingEnabled=sharingStatusCache&&sharingStatusCache.enabled;
5627
- var prevRole=sharingStatusCache&&sharingStatusCache.role;
5628
- if(prevSharingEnabled&&!sharingEnabled){
5629
- var confirmMsg=prevRole==='hub'?t('sharing.disable.confirm.hub'):t('sharing.disable.confirm.client');
5630
- if(!confirm(confirmMsg)){done();return;}
5631
- }
5632
-
5633
- var ok=await doSaveConfig(cfg, saveBtn, 'hubSaved');
5634
- if(ok){
5635
- loadSharingStatus(false);
5636
- if(sharingEnabled){
5637
- updateHubShareInfo();
5638
- setTimeout(function(){alert(t('settings.hub.restartAlert'));},300);
5639
- }
5640
- if(prevSharingEnabled&&!sharingEnabled){
5641
- setTimeout(function(){alert(t('sharing.disable.restartAlert'));},300);
5642
- }
5643
- }
5644
- }
5645
-
5646
- async function saveGeneralConfig(){
5647
- var card=document.querySelector('.card-general');
5648
- var saveBtn=card.querySelector('.settings-actions .btn-primary');
5649
-
5650
- const cfg={};
5651
- const vp=document.getElementById('cfgViewerPort').value.trim();
5652
- if(vp) cfg.viewerPort=Number(vp);
5653
- cfg.telemetry={enabled:document.getElementById('cfgTelemetryEnabled').checked};
5654
-
5655
- await doSaveConfig(cfg, saveBtn, 'generalSaved');
5656
- }
5657
-
5658
- async function saveConfig(){
5659
- await saveModelsConfig();
3270
+ // 6) All tests passed, save
3271
+ try{
3272
+ const r=await fetch('/api/config',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(cfg)});
3273
+ if(!r.ok) throw new Error(await r.text());
3274
+ const el=document.getElementById('settingsSaved');
3275
+ el.classList.add('show');
3276
+ setTimeout(()=>el.classList.remove('show'),2500);
3277
+ toast(t('settings.saved'),'success');
3278
+ }catch(e){
3279
+ toast(t('settings.save.fail')+': '+e.message,'error');
3280
+ }finally{done();}
5660
3281
  }
5661
3282
 
5662
3283
  async function testModel(type){
@@ -5757,7 +3378,7 @@ function formatDuration(ms){
5757
3378
 
5758
3379
  function formatTime(ts){
5759
3380
  if(!ts) return '-';
5760
- return new Date(ts).toLocaleString(dateLoc(),{month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'});
3381
+ return new Date(ts).toLocaleString('zh-CN',{month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'});
5761
3382
  }
5762
3383
 
5763
3384
  function fillDays(rows,days){
@@ -5769,7 +3390,7 @@ function fillDays(rows,days){
5769
3390
  const row=map.get(dateStr)||{};
5770
3391
  out.push({date:dateStr,count:row.count??0,list:row.list??0,search:row.search??0,total:(row.list??0)+(row.search??0)});
5771
3392
  }
5772
- if(days>60){
3393
+ if(days>21){
5773
3394
  const weeks=[];let i=0;
5774
3395
  while(i<out.length){
5775
3396
  const chunk=out.slice(i,i+7);
@@ -5787,23 +3408,19 @@ function fillDays(rows,days){
5787
3408
  }
5788
3409
 
5789
3410
  function renderBars(el,data,valueKey,H){
5790
- var vals=data.map(function(d){return d[valueKey]??0;});
5791
- if(vals.every(function(v){return v===0;})){el.innerHTML='<div style="color:var(--text-muted);font-size:13px;padding:40px;text-align:center">'+t('chart.nodata')+'</div>';return;}
5792
- var max=Math.max(1,Math.max.apply(null,vals));
5793
- var n=data.length;
5794
- var labelStep=n<=7?1:(n<=14?2:5);
5795
- el.innerHTML=data.map(function(r,idx){
5796
- var v=r[valueKey]??0;
5797
- var rawDate=r.date.includes('~')?r.date:(r.date.length>5?r.date.slice(5):'');
5798
- var showLabel=(idx%labelStep===0)||(idx===n-1);
5799
- var label=showLabel?rawDate:'';
5800
- var tipDate=r.date.length>=10?r.date.slice(5,10):'';
5801
- var tipText=tipDate?(tipDate+': '+v):(''+v);
3411
+ const vals=data.map(d=>d[valueKey]??0);
3412
+ if(vals.every(v=>v===0)){el.innerHTML='<div style="color:var(--text-muted);font-size:13px;padding:20px;text-align:center">'+t('chart.nodata')+'</div>';return;}
3413
+ const max=Math.max(1,...vals);
3414
+ const nonZero=vals.filter(v=>v>0).length;
3415
+ const barStyle=data.length<=7?'min-width:40px;max-width:120px':'';
3416
+ el.innerHTML=data.map(r=>{
3417
+ const v=r[valueKey]??0;
3418
+ const label=r.date.includes('~')?r.date:(r.date.length>5?r.date.slice(5):r.date);
5802
3419
  if(v===0){
5803
- return '<div class="chart-bar-wrap"><div class="chart-tip">'+tipText+'</div><div class="chart-bar-col"><div class="chart-bar zero" style="height:3px"></div></div><div class="chart-bar-label">'+label+'</div></div>';
3420
+ return '<div class="chart-bar-wrap" style="'+barStyle+'"><div class="chart-tip">0</div><div class="chart-bar-col"><div class="chart-bar zero" style="height:2px"></div></div><div class="chart-bar-label">'+label+'</div></div>';
5804
3421
  }
5805
- var h=Math.max(8,Math.round((v/max)*H));
5806
- return '<div class="chart-bar-wrap"><div class="chart-tip">'+tipText+'</div><div class="chart-bar-col"><div class="chart-bar" style="height:'+h+'px"></div></div><div class="chart-bar-label">'+label+'</div></div>';
3422
+ const h=Math.max(8,Math.round((v/max)*H));
3423
+ return '<div class="chart-bar-wrap" style="'+barStyle+'"><div class="chart-tip">'+v+'</div><div class="chart-bar-col"><div class="chart-bar" style="height:'+h+'px"></div></div><div class="chart-bar-label">'+label+'</div></div>';
5807
3424
  }).join('');
5808
3425
  }
5809
3426
 
@@ -5814,31 +3431,25 @@ function renderChartWrites(rows){
5814
3431
  }
5815
3432
 
5816
3433
  function renderChartCalls(rows){
5817
- var el=document.getElementById('chartCalls');
5818
- var filled=fillDays(rows?.map(function(r){return {date:r.date,list:r.list,search:r.search};}),metricsDays);
5819
- var vals=filled.map(function(f){return f.total;});
5820
- if(vals.every(function(v){return v===0;})){el.innerHTML='<div style="color:var(--text-muted);font-size:13px;padding:40px;text-align:center">'+t('chart.nocalls')+'</div>';return;}
5821
- var max=Math.max(1,Math.max.apply(null,vals));
5822
- var H=160;
5823
- var n=filled.length;
5824
- var labelStep=n<=7?1:(n<=14?2:5);
5825
- el.innerHTML=filled.map(function(r,idx){
5826
- var rawDate=r.date.includes('~')?r.date:(r.date.length>5?r.date.slice(5):'');
5827
- var showLabel=(idx%labelStep===0)||(idx===n-1);
5828
- var label=showLabel?rawDate:'';
5829
- var tipDate=r.date.length>=10?r.date.slice(5,10):'';
3434
+ const el=document.getElementById('chartCalls');
3435
+ const filled=fillDays(rows?.map(r=>({date:r.date,list:r.list,search:r.search})),metricsDays);
3436
+ const vals=filled.map(f=>f.total);
3437
+ if(vals.every(v=>v===0)){el.innerHTML='<div style="color:var(--text-muted);font-size:13px;padding:20px;text-align:center">'+t('chart.nocalls')+'</div>';return;}
3438
+ const max=Math.max(1,...vals);
3439
+ const H=160;
3440
+ el.innerHTML=filled.map(r=>{
3441
+ const label=r.date.includes('~')?r.date:(r.date.length>5?r.date.slice(5):r.date);
5830
3442
  if(r.total===0){
5831
- var tipZero=tipDate?(tipDate+': 0'):'0';
5832
- return '<div class="chart-bar-wrap"><div class="chart-tip">'+tipZero+'</div><div class="chart-bar-col"><div class="chart-bar zero" style="height:3px"></div></div><div class="chart-bar-label">'+label+'</div></div>';
3443
+ return '<div class="chart-bar-wrap"><div class="chart-tip">0</div><div class="chart-bar-col"><div class="chart-bar zero" style="height:2px"></div></div><div class="chart-bar-label">'+label+'</div></div>';
5833
3444
  }
5834
- var totalH=Math.max(8,Math.round((r.total/max)*H));
5835
- var listH=r.list?Math.max(4,Math.round((r.list/r.total)*totalH)):0;
5836
- var searchH=r.search?totalH-listH:0;
5837
- var tip=(tipDate?tipDate+' - ':'')+'List: '+r.list+', Search: '+r.search;
5838
- var bars='';
3445
+ const totalH=Math.max(8,Math.round((r.total/max)*H));
3446
+ const listH=r.list?Math.max(3,Math.round((r.list/r.total)*totalH)):0;
3447
+ const searchH=r.search?totalH-listH:0;
3448
+ const tip='List: '+r.list+', Search: '+r.search;
3449
+ let bars='';
5839
3450
  if(searchH>0) bars+='<div class="chart-bar violet" style="height:'+searchH+'px"></div>';
5840
3451
  if(listH>0) bars+='<div class="chart-bar" style="height:'+listH+'px"></div>';
5841
- return '<div class="chart-bar-wrap"><div class="chart-tip">'+tip+'</div><div class="chart-bar-col"><div style="display:flex;flex-direction:column;gap:1px;align-items:center;width:100%">'+bars+'</div></div><div class="chart-bar-label">'+label+'</div></div>';
3452
+ return '<div class="chart-bar-wrap"><div class="chart-tip">'+tip+'</div><div class="chart-bar-col"><div style="display:flex;flex-direction:column;gap:1px">'+bars+'</div></div><div class="chart-bar-label">'+label+'</div></div>';
5842
3453
  }).join('');
5843
3454
  }
5844
3455
 
@@ -6018,33 +3629,18 @@ function renderToolAgg(data){
6018
3629
  '</tbody></table>';
6019
3630
  }
6020
3631
 
6021
- /* ─── Sharing status polling ─── */
6022
- var _sharingPollTimer=null;
6023
- function startSharingPoll(){
6024
- stopSharingPoll();
6025
- _sharingPollTimer=setInterval(function(){
6026
- if(sharingStatusCache&&sharingStatusCache.enabled) loadSharingStatus(false);
6027
- },30000);
6028
- }
6029
- function stopSharingPoll(){
6030
- if(_sharingPollTimer){clearInterval(_sharingPollTimer);_sharingPollTimer=null;}
6031
- }
6032
-
6033
3632
  /* ─── Data loading ─── */
6034
3633
  async function loadAll(){
6035
- await Promise.all([loadStats(),loadMemories(),loadSharingStatus(false)]);
3634
+ await Promise.all([loadStats(),loadMemories()]);
6036
3635
  checkMigrateStatus();
6037
3636
  connectPPSSE();
6038
3637
  checkForUpdate();
6039
- startSharingPoll();
6040
3638
  }
6041
3639
 
6042
- async function loadStats(ownerFilter){
3640
+ async function loadStats(){
6043
3641
  let d;
6044
3642
  try{
6045
- var statsUrl='/api/stats';
6046
- if(ownerFilter) statsUrl+='?owner='+encodeURIComponent(ownerFilter);
6047
- const r=await fetch(statsUrl);
3643
+ const r=await fetch('/api/stats');
6048
3644
  d=await r.json();
6049
3645
  }catch(e){ d={}; }
6050
3646
  if(!d||typeof d!=='object') d={};
@@ -6100,17 +3696,6 @@ async function loadStats(ownerFilter){
6100
3696
  sl.innerHTML+='<div class="session-item'+(isActive?' active':'')+'" onclick="filterSession(\\''+s.session_key.replace(/'/g,"\\\\'")+'\\')"><span title="'+s.session_key+'">'+name+'</span><span class="count">'+s.count+'</span></div>';
6101
3697
  });
6102
3698
 
6103
- const fSel=document.getElementById('filterSession');
6104
- if(fSel){
6105
- const curVal=activeSession||'';
6106
- var sessionCount=(d.sessions||[]).length;
6107
- fSel.innerHTML='<option value="">'+t('filter.allsessions')+' ('+sessionCount+')</option>';
6108
- (d.sessions||[]).forEach(s=>{
6109
- const sName=s.session_key.length>30?s.session_key.slice(0,12)+'...'+s.session_key.slice(-10):s.session_key;
6110
- fSel.innerHTML+='<option value="'+s.session_key.replace(/"/g,'&quot;')+'"'+(s.session_key===curVal?' selected':'')+'>'+sName+' ('+s.count+')</option>';
6111
- });
6112
- }
6113
-
6114
3699
  const ownerSel=document.getElementById('filterOwner');
6115
3700
  if(ownerSel && d.owners && d.owners.length>0){
6116
3701
  const curVal=ownerSel.value;
@@ -6122,32 +3707,6 @@ async function loadStats(ownerFilter){
6122
3707
  }
6123
3708
  }
6124
3709
 
6125
- function onOwnerFilterChange(){
6126
- var owner=document.getElementById('filterOwner').value;
6127
- activeSession=null;
6128
- currentPage=1;
6129
- refreshSessionDropdown(owner);
6130
- applyFilters();
6131
- }
6132
-
6133
- async function refreshSessionDropdown(ownerFilter){
6134
- try{
6135
- var statsUrl='/api/stats';
6136
- if(ownerFilter) statsUrl+='?owner='+encodeURIComponent(ownerFilter);
6137
- var r=await fetch(statsUrl);
6138
- var d=await r.json();
6139
- var sessions=d.sessions||[];
6140
- var fSel=document.getElementById('filterSession');
6141
- if(fSel){
6142
- fSel.innerHTML='<option value="">'+t('filter.allsessions')+' ('+sessions.length+')</option>';
6143
- sessions.forEach(function(s){
6144
- var sName=s.session_key.length>30?s.session_key.slice(0,12)+'...'+s.session_key.slice(-10):s.session_key;
6145
- fSel.innerHTML+='<option value="'+s.session_key.replace(/"/g,'&quot;')+'">'+sName+' ('+s.count+')</option>';
6146
- });
6147
- }
6148
- }catch(e){}
6149
- }
6150
-
6151
3710
  function getFilterParams(){
6152
3711
  const p=new URLSearchParams();
6153
3712
  if(activeSession) p.set('session',activeSession);
@@ -6186,16 +3745,22 @@ async function loadMemories(page){
6186
3745
  }
6187
3746
  }
6188
3747
 
6189
- async function loadHubMemories(){
3748
+ async function doSearch(q){
3749
+ if(!q.trim()){currentPage=1;loadMemories();return}
6190
3750
  const list=document.getElementById('memoryList');
6191
3751
  list.innerHTML='<div class="spinner"></div>';
6192
3752
  try{
6193
- const r=await fetch('/api/sharing/memories/list?limit='+PAGE_SIZE);
3753
+ const p=getFilterParams();
3754
+ p.set('q',q);
3755
+ const r=await fetch('/api/search?'+p.toString());
6194
3756
  const d=await r.json();
6195
- const items=d.memories||[];
6196
- document.getElementById('searchMeta').textContent=items.length+t('search.meta.total');
6197
- document.getElementById('sharingSearchMeta').textContent='';
6198
- renderMemories(items);
3757
+ const total=d.total||0;
3758
+ const meta=[];
3759
+ if(d.vectorCount>0) meta.push(d.vectorCount+t('search.meta.semantic'));
3760
+ if(d.ftsCount>0) meta.push(d.ftsCount+t('search.meta.text'));
3761
+ meta.push(total+t('search.meta.results'));
3762
+ document.getElementById('searchMeta').textContent=meta.join(' \\u00B7 ');
3763
+ renderMemories(d.results||[]);
6199
3764
  document.getElementById('pagination').innerHTML='';
6200
3765
  }catch(e){
6201
3766
  document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
@@ -6204,51 +3769,6 @@ async function loadHubMemories(){
6204
3769
  }
6205
3770
  }
6206
3771
 
6207
- async function doSearch(query){
6208
- query=(query||'').trim();
6209
- if(!query){loadMemories();return;}
6210
- var scope=document.getElementById('memorySearchScope')?.value||memorySearchScope||'local';
6211
- var list=document.getElementById('memoryList');
6212
- list.innerHTML='<div class="spinner"></div>';
6213
- if(scope!=='local'){
6214
- try{
6215
- var r=await fetch('/api/sharing/search/memories',{
6216
- method:'POST',
6217
- headers:{'Content-Type':'application/json'},
6218
- body:JSON.stringify({query:query,scope:scope,maxResults:20,role:activeRole||undefined})
6219
- });
6220
- var data=await r.json();
6221
- renderSharingMemorySearchResults(data,query);
6222
- }catch(e){
6223
- document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
6224
- document.getElementById('sharingSearchMeta').textContent='';
6225
- renderMemories([]);
6226
- document.getElementById('pagination').innerHTML='';
6227
- }
6228
- } else {
6229
- try{
6230
- var p=getFilterParams();
6231
- p.set('q',query);
6232
- var r=await fetch('/api/search?'+p.toString());
6233
- var d=await r.json();
6234
- var total=d.total||0;
6235
- var meta=[];
6236
- if(d.vectorCount>0) meta.push(d.vectorCount+t('search.meta.semantic'));
6237
- if(d.ftsCount>0) meta.push(d.ftsCount+t('search.meta.text'));
6238
- meta.push(total+t('search.meta.results'));
6239
- document.getElementById('searchMeta').textContent=meta.join(' \u00B7 ');
6240
- document.getElementById('sharingSearchMeta').textContent='';
6241
- renderMemories(d.results||[]);
6242
- document.getElementById('pagination').innerHTML='';
6243
- }catch(e){
6244
- document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
6245
- document.getElementById('sharingSearchMeta').textContent='';
6246
- renderMemories([]);
6247
- document.getElementById('pagination').innerHTML='';
6248
- }
6249
- }
6250
- }
6251
-
6252
3772
  function debounceSearch(){
6253
3773
  clearTimeout(searchTimer);
6254
3774
  searchTimer=setTimeout(()=>doSearch(document.getElementById('searchInput').value),350);
@@ -6257,12 +3777,6 @@ function debounceSearch(){
6257
3777
  function filterSession(key){
6258
3778
  activeSession=key;
6259
3779
  currentPage=1;
6260
- var fSel=document.getElementById('filterSession');
6261
- if(fSel) fSel.value=key||'';
6262
- document.querySelectorAll('#sessionList .session-item').forEach(function(el,i){
6263
- if(i===0) el.classList.toggle('active',!key);
6264
- else el.classList.toggle('active',el.querySelector('span')?.title===key);
6265
- });
6266
3780
  loadAll();
6267
3781
  }
6268
3782
 
@@ -6298,7 +3812,7 @@ function renderMemories(items){
6298
3812
  }
6299
3813
  items.forEach(m=>{memoryCache[m.id]=m});
6300
3814
  list.innerHTML=items.map(m=>{
6301
- const time=m.created_at?new Date(typeof m.created_at==='number'?m.created_at:m.created_at).toLocaleString(dateLoc()):'';
3815
+ const time=m.created_at?new Date(typeof m.created_at==='number'?m.created_at:m.created_at).toLocaleString('zh-CN'):'';
6302
3816
  const role=m.role||'user';
6303
3817
  const rawSummary=m.summary||'';
6304
3818
  const rawContent=m.content||'';
@@ -6310,7 +3824,7 @@ function renderMemories(items){
6310
3824
  const mc=m.merge_count||0;
6311
3825
  const cardTitle=esc(rawSummary||rawContent||'');
6312
3826
  const mergeBadge=mc>0?'<span class="merge-badge">\\u{1F504} '+t('card.evolved')+' '+mc+t('card.times')+'</span>':'';
6313
- const updatedAt=(m.updated_at&&m.updated_at>m.created_at)?'<span class="card-updated">'+t('card.updated')+' '+new Date(m.updated_at).toLocaleString(dateLoc())+'</span>':'';
3827
+ 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>':'';
6314
3828
  const ds=m.dedup_status||'active';
6315
3829
  const isInactive=ds==='merged';
6316
3830
  const dedupBadge=ds==='duplicate'?'<span class="dedup-badge duplicate">'+t('card.dedupDuplicate')+'</span>':ds==='merged'?'<span class="dedup-badge merged">'+t('card.dedupMerged')+'</span>':'';
@@ -6319,8 +3833,6 @@ function renderMemories(items){
6319
3833
  const ownerVal=m.owner||'agent:main';
6320
3834
  const isPublicMem=ownerVal==='public';
6321
3835
  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>';
6322
- const memShared=m.sharingVisibility||null;
6323
- const memShareBadge=memShared?'<span style="display:inline-flex;align-items:center;gap:3px;padding:2px 8px;background:#22c55e22;border:1px solid #22c55e44;border-radius:10px;font-size:11px;color:#22c55e">\\u2713 '+(memShared==='group'?t('share.group'):t('share.public'))+'</span>':'';
6324
3836
  let dedupInfo='';
6325
3837
  if(ds==='duplicate'||ds==='merged'){
6326
3838
  const reason=m.dedup_reason?'<span style="font-size:11px;color:var(--text-muted)">'+t('card.dedupReason')+esc(m.dedup_reason)+'</span>':'';
@@ -6334,7 +3846,7 @@ function renderMemories(items){
6334
3846
  if(hist.length>0){
6335
3847
  historyHtml='<div class="merge-history" id="history-'+id+'" style="display:none"><div style="font-weight:600;margin-bottom:8px;font-size:12px">'+t('card.evolveHistory')+' ('+hist.length+')</div>';
6336
3848
  hist.forEach(function(h){
6337
- const ht=h.at?new Date(h.at).toLocaleString(dateLoc()):'';
3849
+ const ht=h.at?new Date(h.at).toLocaleString('zh-CN'):'';
6338
3850
  historyHtml+='<div class="merge-history-item"><span class="merge-action '+h.action+'">'+h.action+'</span> <span style="color:var(--text-muted)">'+ht+'</span><br>'+esc(h.reason||'');
6339
3851
  if(h.from) historyHtml+='<br><span style="opacity:.6">'+t('card.oldSummary')+':</span> '+esc(h.from);
6340
3852
  if(h.to) historyHtml+='<br><span style="opacity:.6">'+t('card.newSummary')+':</span> '+esc(h.to);
@@ -6370,7 +3882,6 @@ function renderMemories(items){
6370
3882
  (mc>0?'<button class="btn btn-sm btn-ghost" onclick="toggleHistory(\\''+id+'\\')">'+t('card.evolveHistory')+'</button>':'')+
6371
3883
  '<button class="btn btn-sm btn-ghost" onclick="openEditModal(\\''+id+'\\')">'+t('card.edit')+'</button>'+
6372
3884
  (isPublicMem?'<button class="btn btn-sm btn-ghost" onclick="toggleMemoryPublic(\\''+id+'\\',false)">\\u{1F512} '+t('skills.setPrivate')+'</button>':'<button class="btn btn-sm btn-ghost mem-public-btn" onclick="toggleMemoryPublic(\\''+id+'\\',true)">\\u{1F310} '+t('skills.setPublic')+'</button>')+
6373
- (memShared?'<button class="btn btn-sm btn-ghost" onclick="unshareMemory(\\''+id+'\\')">'+t('share.unshareBtn')+'</button>':'<button class="btn btn-sm btn-ghost" onclick="shareMemoryPrompt(\\''+id+'\\')">'+t('share.shareBtn')+'</button>')+
6374
3885
  '<button class="btn btn-sm btn-ghost" style="color:var(--accent)" onclick="deleteMemory(\\''+id+'\\')">'+t('card.delete')+'</button>'+
6375
3886
  vscore+
6376
3887
  '</div></div>';
@@ -6429,50 +3940,38 @@ function scrollToMemory(targetId){
6429
3940
  }
6430
3941
  showMemoryModal(targetId);
6431
3942
  }
6432
- function fmtModalDate(v){
6433
- if(!v) return '-';
6434
- var d=new Date(v);
6435
- if(isNaN(d.getTime())) return '-';
6436
- return d.toLocaleString(dateLoc());
6437
- }
6438
3943
  async function showMemoryModal(chunkId){
6439
- var overlay=document.getElementById('memoryModal');
6440
- var body=document.getElementById('memoryModalBody');
6441
- body.innerHTML='<div style="text-align:center;padding:56px;color:var(--text-sec)"><div class="spinner" style="margin:0 auto 14px"></div><div style="font-size:12px;letter-spacing:.04em">'+t('memory.detail.loading')+'</div></div>';
3944
+ const overlay=document.getElementById('memoryModal');
3945
+ const body=document.getElementById('memoryModalBody');
3946
+ body.innerHTML='<div style="text-align:center;padding:40px;color:var(--text-sec)">Loading...</div>';
6442
3947
  overlay.classList.add('show');
6443
3948
  try{
6444
- var res=await fetch('/api/memory/'+encodeURIComponent(chunkId));
6445
- if(!res.ok){body.innerHTML='<div style="text-align:center;padding:56px"><div style="font-size:32px;margin-bottom:12px">\u{1F50D}</div><div style="color:#f87171;font-size:13px">'+t('memory.detail.notFound')+'</div></div>';return;}
6446
- var data=await res.json();
6447
- var m=data.memory;
6448
- var role=(m.role||'unknown').toUpperCase();
6449
- var roleCls=(m.role||'').toLowerCase();
6450
- var ds=m.dedup_status||'active';
6451
- var h='<div class="mm-hero">';
6452
- h+='<div class="mm-hero-row">';
6453
- h+='<span class="mm-role-chip '+roleCls+'">'+role+'</span>';
6454
- if(ds!=='active') h+='<span class="mm-dedup-chip '+(ds==='duplicate'?'duplicate':'merged')+'">'+(ds==='duplicate'?'\u274C':'\u{1F504}')+' '+ds+'</span>';
6455
- h+='<span class="mm-id" onclick="navigator.clipboard.writeText(\\''+esc(m.id)+'\\');toast(\\'ID copied\\',\\'success\\')" title="'+t('memory.detail.copyId')+'">'+esc(m.id.slice(0,12))+'</span>';
6456
- h+='</div>';
6457
- if(m.summary) h+='<div class="mm-summary">'+esc(m.summary)+'</div>';
6458
- h+='</div>';
6459
- if(m.content){
6460
- h+='<div class="mm-section"><div class="mm-section-label">Content</div><pre class="mm-content">'+esc(m.content)+'</pre></div>';
6461
- }
6462
- h+='<div class="mm-meta">';
6463
- if(m.session_key) h+='<div class="mm-meta-chip"><strong>Session</strong><span>'+esc(m.session_key.slice(0,12))+'</span></div>';
6464
- h+='<div class="mm-meta-chip"><strong>'+t('memory.detail.created')+'</strong><span>'+fmtModalDate(m.created_at)+'</span></div>';
6465
- if(m.updated_at) h+='<div class="mm-meta-chip"><strong>'+t('memory.detail.updated')+'</strong><span>'+fmtModalDate(m.updated_at)+'</span></div>';
6466
- if(m.kind) h+='<div class="mm-meta-chip"><strong>Kind</strong><span>'+esc(m.kind)+'</span></div>';
6467
- h+='</div>';
6468
- if(m.dedup_reason){
6469
- h+='<div class="mm-dedup"><div class="mm-dedup-box">'+esc(m.dedup_reason)+'</div></div>';
6470
- }
6471
- if(m.dedup_target&&m.dedup_target!==chunkId){
6472
- h+='<div class="mm-footer"><span class="dedup-target-link" onclick="closeMemoryModal();scrollToMemory(\\''+m.dedup_target+'\\')">'+t('memory.detail.viewTarget')+' '+m.dedup_target.slice(0,8)+'...</span></div>';
6473
- }
6474
- body.innerHTML=h;
6475
- }catch(e){body.innerHTML='<div style="text-align:center;padding:56px"><div style="font-size:32px;margin-bottom:12px">\u26A0\uFE0F</div><div style="color:#f87171;font-size:13px">'+esc(String(e))+'</div></div>';}
3949
+ const res=await fetch('/api/memory/'+encodeURIComponent(chunkId));
3950
+ if(!res.ok){body.innerHTML='<div style="text-align:center;padding:40px;color:#f87171">Memory not found</div>';return;}
3951
+ const data=await res.json();
3952
+ const m=data.memory;
3953
+ const role=(m.role||'unknown').toUpperCase();
3954
+ const roleCls=(m.role||'').toLowerCase();
3955
+ const ds=m.dedup_status||'active';
3956
+ const time=new Date(m.created_at).toLocaleString('zh-CN');
3957
+ const updated=m.updated_at?new Date(m.updated_at).toLocaleString('zh-CN'):'';
3958
+ let html='<div class="modal-memory-card">';
3959
+ html+='<div class="modal-header-row"><span class="role-tag '+roleCls+'">'+role+'</span>';
3960
+ if(ds!=='active') html+='<span class="dedup-badge '+(ds==='duplicate'?'duplicate':'merged')+'">'+ds+'</span>';
3961
+ html+='</div>';
3962
+ 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>';
3963
+ html+='<div class="modal-field"><div class="modal-field-label">Summary</div><div class="modal-field-val" style="font-size:14px;font-weight:600">'+esc(m.summary||'')+'</div></div>';
3964
+ html+='<div class="modal-field"><div class="modal-field-label">Content</div><pre class="modal-field-content">'+esc(m.content||'')+'</pre></div>';
3965
+ html+='<div class="modal-meta-row">';
3966
+ html+='<span><strong>Session:</strong> '+esc(m.session_key||'')+'</span>';
3967
+ html+='<span><strong>Created:</strong> '+time+'</span>';
3968
+ if(updated) html+='<span><strong>Updated:</strong> '+updated+'</span>';
3969
+ html+='</div>';
3970
+ if(m.dedup_reason) html+='<div class="modal-field"><div class="modal-field-label">Dedup Reason</div><div class="modal-field-val">'+esc(m.dedup_reason)+'</div></div>';
3971
+ if(m.dedup_target&&m.dedup_target!==chunkId) html+='<div class="modal-field"><span class="dedup-target-link" onclick="closeMemoryModal();scrollToMemory(\\''+m.dedup_target+'\\')">View target: '+m.dedup_target.slice(0,8)+'...</span></div>';
3972
+ html+='</div>';
3973
+ body.innerHTML=html;
3974
+ }catch(e){body.innerHTML='<div style="text-align:center;padding:40px;color:#f87171">Error: '+esc(String(e))+'</div>';}
6476
3975
  }
6477
3976
  function closeMemoryModal(){document.getElementById('memoryModal').classList.remove('show');}
6478
3977
 
@@ -7190,12 +4689,10 @@ initViewerTheme();
7190
4689
  /* ─── Update check ─── */
7191
4690
  function waitForGatewayAndReload(maxAttempts,attempt){
7192
4691
  attempt=attempt||0;
7193
- function forceReload(){window.location.href=window.location.pathname+'?_t='+Date.now();}
7194
- if(attempt>=maxAttempts){forceReload();return;}
4692
+ if(attempt>=maxAttempts){window.location.reload();return;}
7195
4693
  setTimeout(function(){
7196
- fetch('/api/auth/status').then(function(r){
7197
- if(r.ok||r.status===401||r.status===403) forceReload();
7198
- else waitForGatewayAndReload(maxAttempts,attempt+1);
4694
+ fetch('/api/auth/status').then(function(){
4695
+ window.location.reload();
7199
4696
  }).catch(function(){waitForGatewayAndReload(maxAttempts,attempt+1);});
7200
4697
  },3000);
7201
4698
  }
@@ -7207,7 +4704,7 @@ function doUpdateInstall(packageSpec,btnEl,statusEl){
7207
4704
  .then(function(r){return r.json()})
7208
4705
  .then(function(d){
7209
4706
  if(d.ok){
7210
- btnEl.textContent=t('update.success')+(d.version?' v'+d.version:'');
4707
+ btnEl.textContent=t('update.success');
7211
4708
  btnEl.style.cssText='background:rgba(34,197,94,.15);color:#22c55e;border:1px solid rgba(34,197,94,.3);border-radius:6px;padding:4px 14px;font-size:12px;font-weight:600;cursor:default;white-space:nowrap';
7212
4709
  if(statusEl)statusEl.textContent=t('update.restarting');
7213
4710
  waitForGatewayAndReload(40);
@@ -7272,8 +4769,8 @@ checkAuth();
7272
4769
  <div class="memory-modal-overlay" id="memoryModal" onclick="if(event.target===this)closeMemoryModal()">
7273
4770
  <div class="memory-modal">
7274
4771
  <div class="memory-modal-title">
7275
- <div class="mm-tl"><div class="mm-tl-icon">\u{1F9E0}</div><span data-i18n="memory.detail.title">Memory Detail</span></div>
7276
- <button class="mm-close" onclick="closeMemoryModal()" title="Close">&times;</button>
4772
+ <span>Memory Detail</span>
4773
+ <button class="btn btn-sm btn-ghost" onclick="closeMemoryModal()" style="font-size:16px;padding:2px 8px">&times;</button>
7277
4774
  </div>
7278
4775
  <div class="memory-modal-body" id="memoryModalBody"></div>
7279
4776
  </div>