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