aimeat 1.16.2 → 1.18.0

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 (281) hide show
  1. package/dist/.env.example +6 -1
  2. package/dist/locales/en.json +166 -4
  3. package/dist/locales/fi.json +166 -4
  4. package/dist/public/components/Card.js +17 -3
  5. package/dist/public/components/Collapsible.js +39 -0
  6. package/dist/public/components/CopyButton.js +55 -24
  7. package/dist/public/components/DataTable.js +57 -0
  8. package/dist/public/components/EmptyState.js +28 -0
  9. package/dist/public/components/KeyValueRow.js +37 -0
  10. package/dist/public/components/Modal.js +22 -5
  11. package/dist/public/components/Pagination.js +70 -0
  12. package/dist/public/components/SearchBar.js +44 -0
  13. package/dist/public/components/StatusDot.js +29 -0
  14. package/dist/public/components/TagList.js +53 -0
  15. package/dist/public/components/ToggleSwitch.js +28 -0
  16. package/dist/public/components/index.js +41 -1
  17. package/dist/public/css/components/tags.css +24 -0
  18. package/dist/public/css/style-testbed.css +135 -0
  19. package/dist/public/css/theme.css +556 -34
  20. package/dist/public/css/views/admin-agent-integration.css +2 -33
  21. package/dist/public/css/views/admin.css +730 -686
  22. package/dist/public/css/views/agents-detail.css +402 -14
  23. package/dist/public/css/views/help.css +2 -1
  24. package/dist/public/css/views/hobbies.css +53 -33
  25. package/dist/public/css/views/marketplace.css +73 -60
  26. package/dist/public/css/views/openclaw.css +12 -6
  27. package/dist/public/css/views/portal-classic.css +34 -26
  28. package/dist/public/css/views/portal-dev.css +41 -32
  29. package/dist/public/css/views/portal.css +14 -18
  30. package/dist/public/css/views/profile.css +252 -164
  31. package/dist/public/css/views/public-knowledge-viewer.css +18 -6
  32. package/dist/public/css/views/scheduler.css +100 -0
  33. package/dist/public/js/format.js +41 -0
  34. package/dist/public/js/services/agent-tasks.js +14 -1
  35. package/dist/public/js/services/generator-spec-validate.js +5 -1
  36. package/dist/public/js/services/schedules.js +53 -0
  37. package/dist/public/js/style-testbed.js +170 -0
  38. package/dist/public/js/theme.js +114 -0
  39. package/dist/public/spa.html +74 -16
  40. package/dist/public/style-testbed.html +420 -0
  41. package/dist/public/views/admin/agent-integration-tab.js +23 -38
  42. package/dist/public/views/admin/agent-tasks-tab.js +14 -21
  43. package/dist/public/views/admin/chat-instances-tab.js +1 -1
  44. package/dist/public/views/admin/config-tab.js +7 -2
  45. package/dist/public/views/admin/cortex-tab.js +20 -14
  46. package/dist/public/views/admin/csm-tab.js +22 -18
  47. package/dist/public/views/admin/directory-tab.js +3 -3
  48. package/dist/public/views/admin/email-tab.js +23 -28
  49. package/dist/public/views/admin/federation-tab.js +15 -5
  50. package/dist/public/views/admin/generator-debug-tab.js +10 -8
  51. package/dist/public/views/admin/genesis-tab.js +4 -4
  52. package/dist/public/views/admin/ghii-tab.js +3 -3
  53. package/dist/public/views/admin/knowledge-tab.js +15 -5
  54. package/dist/public/views/admin/matching-tab.js +3 -3
  55. package/dist/public/views/admin/memory-tab.js +16 -20
  56. package/dist/public/views/admin/msm-tab.js +30 -17
  57. package/dist/public/views/admin/overview-tab.js +7 -7
  58. package/dist/public/views/admin/packages-tab.js +10 -9
  59. package/dist/public/views/admin/portal-tab.js +14 -4
  60. package/dist/public/views/admin/prompts-tab.js +5 -5
  61. package/dist/public/views/admin/push-tab.js +15 -4
  62. package/dist/public/views/admin/realtime-tab.js +3 -3
  63. package/dist/public/views/admin/services-tab.js +18 -27
  64. package/dist/public/views/admin/shared.js +48 -58
  65. package/dist/public/views/admin/sharing-groups-tab.js +5 -10
  66. package/dist/public/views/admin/stats-tab.js +3 -3
  67. package/dist/public/views/guides.js +9 -13
  68. package/dist/public/views/help.js +3 -24
  69. package/dist/public/views/hobbies.js +47 -41
  70. package/dist/public/views/marketplace.js +42 -34
  71. package/dist/public/views/openclaw.js +0 -1
  72. package/dist/public/views/portal-classic.js +4 -15
  73. package/dist/public/views/portal-dev.js +28 -30
  74. package/dist/public/views/portal.js +16 -21
  75. package/dist/public/views/portfolio.js +11 -12
  76. package/dist/public/views/profile/access-tab.js +250 -1
  77. package/dist/public/views/profile/agents/agent-card.js +75 -4
  78. package/dist/public/views/profile/agents/tab-activity.js +15 -13
  79. package/dist/public/views/profile/agents/tab-agent-config.js +60 -6
  80. package/dist/public/views/profile/agents/tab-integration.js +37 -60
  81. package/dist/public/views/profile/agents/tab-messages.js +10 -1
  82. package/dist/public/views/profile/agents/tab-quality.js +25 -19
  83. package/dist/public/views/profile/agents/tab-schedules.js +110 -0
  84. package/dist/public/views/profile/agents-activity-subtab.js +4 -4
  85. package/dist/public/views/profile/agents-services-subtab.js +5 -4
  86. package/dist/public/views/profile/agents-tab.js +141 -37
  87. package/dist/public/views/profile/agents-tasks-subtab.js +221 -26
  88. package/dist/public/views/profile/calibrator-batch.js +5 -11
  89. package/dist/public/views/profile/calibrator-tab.js +4 -9
  90. package/dist/public/views/profile/chat-sessions-tab.js +9 -5
  91. package/dist/public/views/profile/email-tab.js +2 -2
  92. package/dist/public/views/profile/extensions-tab.js +27 -22
  93. package/dist/public/views/profile/federation-tab.js +4 -1
  94. package/dist/public/views/profile/foundry-detail.js +22 -23
  95. package/dist/public/views/profile/foundry-tab.js +12 -13
  96. package/dist/public/views/profile/generator-dashboard/DebugPanel.js +4 -7
  97. package/dist/public/views/profile/generator-dashboard/use-dashboard-core.js +4 -1
  98. package/dist/public/views/profile/generator-dashboard/use-edit-mode.js +3 -2
  99. package/dist/public/views/profile/generator-detail.js +26 -30
  100. package/dist/public/views/profile/generator-tab.js +5 -5
  101. package/dist/public/views/profile/knowledge-tab.js +12 -12
  102. package/dist/public/views/profile/landing-page.js +100 -149
  103. package/dist/public/views/profile/memory-tab.js +15 -11
  104. package/dist/public/views/profile/node-stats-tab.js +3 -3
  105. package/dist/public/views/profile/nodes-tab.js +10 -3
  106. package/dist/public/views/profile/notifications-tab.js +3 -3
  107. package/dist/public/views/profile/packages-tab.js +14 -14
  108. package/dist/public/views/profile/scheduler-tab.js +340 -0
  109. package/dist/public/views/profile/services-tab.js +1 -1
  110. package/dist/public/views/profile/shared.js +16 -12
  111. package/dist/public/views/profile/wallet-tab.js +27 -50
  112. package/dist/public/views/profile/work-tab.js +29 -31
  113. package/dist/public/views/profile.js +24 -0
  114. package/dist/public/views/public-knowledge-viewer.js +9 -18
  115. package/dist/src/auth/middleware.d.ts +3 -2
  116. package/dist/src/auth/middleware.d.ts.map +1 -1
  117. package/dist/src/auth/middleware.js +78 -3
  118. package/dist/src/auth/middleware.js.map +1 -1
  119. package/dist/src/cli/init-wizard.d.ts.map +1 -1
  120. package/dist/src/cli/init-wizard.js +13 -1
  121. package/dist/src/cli/init-wizard.js.map +1 -1
  122. package/dist/src/config.d.ts +6 -0
  123. package/dist/src/config.d.ts.map +1 -1
  124. package/dist/src/config.js +12 -1
  125. package/dist/src/config.js.map +1 -1
  126. package/dist/src/generated/api-types.d.ts +708 -8
  127. package/dist/src/generated/api-types.d.ts.map +1 -1
  128. package/dist/src/mcp/agent-schedules.d.ts +23 -0
  129. package/dist/src/mcp/agent-schedules.d.ts.map +1 -0
  130. package/dist/src/mcp/agent-schedules.js +175 -0
  131. package/dist/src/mcp/agent-schedules.js.map +1 -0
  132. package/dist/src/mcp/annotations.d.ts.map +1 -1
  133. package/dist/src/mcp/annotations.js +6 -0
  134. package/dist/src/mcp/annotations.js.map +1 -1
  135. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  136. package/dist/src/mcp/catalog/definitions.js +55 -0
  137. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  138. package/dist/src/mcp/catalog/scopes.d.ts.map +1 -1
  139. package/dist/src/mcp/catalog/scopes.js +12 -0
  140. package/dist/src/mcp/catalog/scopes.js.map +1 -1
  141. package/dist/src/mcp/catalog/surfaces.d.ts.map +1 -1
  142. package/dist/src/mcp/catalog/surfaces.js +2 -0
  143. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  144. package/dist/src/mcp/index.d.ts.map +1 -1
  145. package/dist/src/mcp/index.js +2 -0
  146. package/dist/src/mcp/index.js.map +1 -1
  147. package/dist/src/models/agent-task-schemas.d.ts +6 -0
  148. package/dist/src/models/agent-task-schemas.d.ts.map +1 -1
  149. package/dist/src/models/agent-task-schemas.js +6 -0
  150. package/dist/src/models/agent-task-schemas.js.map +1 -1
  151. package/dist/src/models/schemas.d.ts +1 -0
  152. package/dist/src/models/schemas.d.ts.map +1 -1
  153. package/dist/src/models/schemas.js +4 -0
  154. package/dist/src/models/schemas.js.map +1 -1
  155. package/dist/src/routes/access-tokens.d.ts +17 -0
  156. package/dist/src/routes/access-tokens.d.ts.map +1 -0
  157. package/dist/src/routes/access-tokens.js +164 -0
  158. package/dist/src/routes/access-tokens.js.map +1 -0
  159. package/dist/src/routes/agent-tasks.d.ts +2 -0
  160. package/dist/src/routes/agent-tasks.d.ts.map +1 -1
  161. package/dist/src/routes/agent-tasks.js +118 -23
  162. package/dist/src/routes/agent-tasks.js.map +1 -1
  163. package/dist/src/routes/agents.d.ts.map +1 -1
  164. package/dist/src/routes/agents.js +46 -0
  165. package/dist/src/routes/agents.js.map +1 -1
  166. package/dist/src/routes/ai.d.ts +6 -9
  167. package/dist/src/routes/ai.d.ts.map +1 -1
  168. package/dist/src/routes/ai.js +26 -175
  169. package/dist/src/routes/ai.js.map +1 -1
  170. package/dist/src/routes/apps.d.ts.map +1 -1
  171. package/dist/src/routes/apps.js +104 -34
  172. package/dist/src/routes/apps.js.map +1 -1
  173. package/dist/src/routes/auth.d.ts.map +1 -1
  174. package/dist/src/routes/auth.js +77 -4
  175. package/dist/src/routes/auth.js.map +1 -1
  176. package/dist/src/routes/cortex.d.ts +2 -1
  177. package/dist/src/routes/cortex.d.ts.map +1 -1
  178. package/dist/src/routes/cortex.js +13 -8
  179. package/dist/src/routes/cortex.js.map +1 -1
  180. package/dist/src/routes/extensions.d.ts.map +1 -1
  181. package/dist/src/routes/extensions.js +85 -51
  182. package/dist/src/routes/extensions.js.map +1 -1
  183. package/dist/src/routes/generator.d.ts.map +1 -1
  184. package/dist/src/routes/generator.js +109 -43
  185. package/dist/src/routes/generator.js.map +1 -1
  186. package/dist/src/routes/ghii.d.ts.map +1 -1
  187. package/dist/src/routes/ghii.js +34 -13
  188. package/dist/src/routes/ghii.js.map +1 -1
  189. package/dist/src/routes/libs.d.ts +15 -0
  190. package/dist/src/routes/libs.d.ts.map +1 -1
  191. package/dist/src/routes/libs.js +178 -55
  192. package/dist/src/routes/libs.js.map +1 -1
  193. package/dist/src/routes/portal.d.ts.map +1 -1
  194. package/dist/src/routes/portal.js +1 -0
  195. package/dist/src/routes/portal.js.map +1 -1
  196. package/dist/src/routes/schedules.d.ts +33 -0
  197. package/dist/src/routes/schedules.d.ts.map +1 -0
  198. package/dist/src/routes/schedules.js +380 -0
  199. package/dist/src/routes/schedules.js.map +1 -0
  200. package/dist/src/server-bootstrap/config-init.js +2 -2
  201. package/dist/src/server-bootstrap/config-init.js.map +1 -1
  202. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  203. package/dist/src/server-bootstrap/routes-loader.js +7 -0
  204. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  205. package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
  206. package/dist/src/server-bootstrap/service-init.js +2 -1
  207. package/dist/src/server-bootstrap/service-init.js.map +1 -1
  208. package/dist/src/services/access-token.d.ts +28 -0
  209. package/dist/src/services/access-token.d.ts.map +1 -0
  210. package/dist/src/services/access-token.js +37 -0
  211. package/dist/src/services/access-token.js.map +1 -0
  212. package/dist/src/services/ai-completion.d.ts +83 -0
  213. package/dist/src/services/ai-completion.d.ts.map +1 -0
  214. package/dist/src/services/ai-completion.js +187 -0
  215. package/dist/src/services/ai-completion.js.map +1 -0
  216. package/dist/src/services/config-schema.d.ts.map +1 -1
  217. package/dist/src/services/config-schema.js +3 -0
  218. package/dist/src/services/config-schema.js.map +1 -1
  219. package/dist/src/services/generator-prompt-seeds.d.ts.map +1 -1
  220. package/dist/src/services/generator-prompt-seeds.js +6 -3
  221. package/dist/src/services/generator-prompt-seeds.js.map +1 -1
  222. package/dist/src/services/generator-prompts/spec-validate.d.ts.map +1 -1
  223. package/dist/src/services/generator-prompts/spec-validate.js +6 -2
  224. package/dist/src/services/generator-prompts/spec-validate.js.map +1 -1
  225. package/dist/src/services/generator-registration.d.ts +12 -4
  226. package/dist/src/services/generator-registration.d.ts.map +1 -1
  227. package/dist/src/services/generator-registration.js +71 -0
  228. package/dist/src/services/generator-registration.js.map +1 -1
  229. package/dist/src/services/generator-validate.d.ts +10 -0
  230. package/dist/src/services/generator-validate.d.ts.map +1 -1
  231. package/dist/src/services/generator-validate.js +180 -36
  232. package/dist/src/services/generator-validate.js.map +1 -1
  233. package/dist/src/services/owner-session.d.ts +43 -0
  234. package/dist/src/services/owner-session.d.ts.map +1 -0
  235. package/dist/src/services/owner-session.js +175 -0
  236. package/dist/src/services/owner-session.js.map +1 -0
  237. package/dist/src/services/schedule-constraints.d.ts +65 -0
  238. package/dist/src/services/schedule-constraints.d.ts.map +1 -0
  239. package/dist/src/services/schedule-constraints.js +106 -0
  240. package/dist/src/services/schedule-constraints.js.map +1 -0
  241. package/dist/src/services/scheduler.d.ts +34 -0
  242. package/dist/src/services/scheduler.d.ts.map +1 -1
  243. package/dist/src/services/scheduler.js +266 -22
  244. package/dist/src/services/scheduler.js.map +1 -1
  245. package/dist/src/services/skill-bundle/generator.js +19 -0
  246. package/dist/src/services/skill-bundle/generator.js.map +1 -1
  247. package/dist/src/storage/interface.d.ts +48 -4
  248. package/dist/src/storage/interface.d.ts.map +1 -1
  249. package/dist/src/storage/interface.js.map +1 -1
  250. package/dist/src/storage/providers/mongodb/index.d.ts +31 -0
  251. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  252. package/dist/src/storage/providers/mongodb/index.js +187 -9
  253. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  254. package/dist/src/storage/providers/sqlite/index.d.ts +31 -0
  255. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  256. package/dist/src/storage/providers/sqlite/index.js +131 -8
  257. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  258. package/dist/src/storage/providers/sqlite/repos/agent-task.d.ts.map +1 -1
  259. package/dist/src/storage/providers/sqlite/repos/agent-task.js +6 -4
  260. package/dist/src/storage/providers/sqlite/repos/agent-task.js.map +1 -1
  261. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  262. package/dist/src/storage/providers/sqlite/schema.js +79 -8
  263. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  264. package/dist/src/storage/repositories/pat.repository.d.ts +40 -0
  265. package/dist/src/storage/repositories/pat.repository.d.ts.map +1 -0
  266. package/dist/src/storage/repositories/pat.repository.js +14 -0
  267. package/dist/src/storage/repositories/pat.repository.js.map +1 -0
  268. package/dist/src/storage/repositories/scheduler.repository.d.ts +2 -0
  269. package/dist/src/storage/repositories/scheduler.repository.d.ts.map +1 -1
  270. package/dist/src/storage/repositories/session.repository.d.ts +51 -0
  271. package/dist/src/storage/repositories/session.repository.d.ts.map +1 -1
  272. package/dist/src/storage/repositories/session.repository.js +12 -0
  273. package/dist/src/storage/repositories/session.repository.js.map +1 -1
  274. package/dist/src/utils/env-config.d.ts.map +1 -1
  275. package/dist/src/utils/env-config.js +25 -1
  276. package/dist/src/utils/env-config.js.map +1 -1
  277. package/dist/src/utils/env-validator.d.ts.map +1 -1
  278. package/dist/src/utils/env-validator.js +4 -0
  279. package/dist/src/utils/env-validator.js.map +1 -1
  280. package/package.json +1 -1
  281. package/prisma/schema.prisma +48 -0
@@ -1,7 +1,11 @@
1
1
  /**
2
- * Admin Dashboard — Services Tab
3
- * Manage installed service extensions and their instances.
4
- * Install bundled extensions with one-click.
2
+ * @file services-tab.js
3
+ * @description Admin Dashboard — Services Tab. Manage installed service
4
+ * extensions and their instances, install bundled extensions one-click,
5
+ * edit action scripts and instance translations.
6
+ * @version-history
7
+ * v1.1.0 — 2026-06-02 — Admin design unification: raw script/JSON textareas
8
+ * → adm-textarea adm-input-full (drop inline mono/resize/border styles).
5
9
  */
6
10
  import { h } from 'preact';
7
11
  import { useState, useEffect } from 'preact/hooks';
@@ -17,6 +21,7 @@ import {
17
21
  getDiskScript, saveDiskScript, addDiskAction, reinstallExtension,
18
22
  } from '/js/services/admin.js';
19
23
  import { useConfirm } from '/components/Modal.js';
24
+ import { CopyButton } from '/components/CopyButton.js';
20
25
 
21
26
  const inputStyle = 'adm-input';
22
27
  const labelStyle = 'adm-text-sm adm-text-dim';
@@ -212,22 +217,6 @@ function TranslationEditor({ extName, inst, onSave }) {
212
217
  setSaving(false);
213
218
  }
214
219
 
215
- async function copyAiPrompt() {
216
- const prompt = buildTranslationAiPrompt(extName, inst.id, allKeys.length > 0 ? allKeys : ['(no keys detected — add categories to config first)'], locale, translations);
217
- try {
218
- await navigator.clipboard.writeText(prompt);
219
- setMsg({ ok: true, text: t('dashboard.servicesTlPromptCopied') });
220
- } catch {
221
- const ta = document.createElement('textarea');
222
- ta.value = prompt;
223
- document.body.appendChild(ta);
224
- ta.select();
225
- document.execCommand('copy');
226
- document.body.removeChild(ta);
227
- setMsg({ ok: true, text: t('dashboard.servicesTlPromptCopied') });
228
- }
229
- }
230
-
231
220
  const displayKeys = [...new Set([...allKeys, ...Object.keys(translations)])].sort();
232
221
 
233
222
  return html`
@@ -241,13 +230,15 @@ function TranslationEditor({ extName, inst, onSave }) {
241
230
  </select>
242
231
  <button class="adm-btn-sm" onClick=${() => { setJsonMode(!jsonMode); if (!jsonMode) setJsonText(JSON.stringify(translations, null, 2)); }}
243
232
  style="font-size:.75rem">${jsonMode ? t('dashboard.servicesTlFormMode') : 'JSON'}</button>
244
- <button class="adm-btn-sm" onClick=${copyAiPrompt}
245
- style="font-size:.75rem;color:#818cf8;border-color:rgba(79,70,229,0.3)">${t('dashboard.servicesTlAiPrompt')}</button>
233
+ <${CopyButton} className="adm-btn-sm"
234
+ text=${buildTranslationAiPrompt(extName, inst.id, allKeys.length > 0 ? allKeys : ['(no keys detected — add categories to config first)'], locale, translations)}
235
+ label=${t('dashboard.servicesTlAiPrompt')} copiedLabel=${t('dashboard.servicesTlAiPrompt')}
236
+ onCopied=${() => setMsg({ ok: true, text: t('dashboard.servicesTlPromptCopied') })} />
246
237
  </div>
247
238
 
248
239
  ${jsonMode ? html`
249
- <textarea value=${jsonText} onInput=${e => setJsonText(e.target.value)}
250
- style="width:100%;height:200px;font-family:monospace;font-size:12px;padding:8px;border:1px solid var(--glass-border);border-radius:4px;background:rgba(0,0,0,0.2);color:var(--text-bright);resize:vertical"
240
+ <textarea class="adm-textarea adm-input-full" value=${jsonText} onInput=${e => setJsonText(e.target.value)}
241
+ style="height:200px;font-size:12px"
251
242
  spellcheck="false" />
252
243
  ` : html`
253
244
  <div class="adm-flex-col" style="gap:4px;max-height:300px;overflow-y:auto">
@@ -328,8 +319,8 @@ function ActionScriptEditor({ extName, actions, onUpdated }) {
328
319
  <div class="adm-flex-between adm-mb-xs">
329
320
  <code style="font-size:.8rem;color:var(--text-dim)">${extName}/${selectedAction}</code>
330
321
  </div>
331
- <textarea value=${script} onInput=${e => setScript(e.target.value)}
332
- style="width:100%;height:320px;font-family:'Fira Code',monospace;font-size:12px;line-height:1.5;padding:10px;border:1px solid var(--glass-border);border-radius:4px;background:rgba(0,0,0,0.3);color:var(--text-bright);resize:vertical;tab-size:2"
322
+ <textarea class="adm-textarea adm-input-full" value=${script} onInput=${e => setScript(e.target.value)}
323
+ style="height:320px;font-size:12px"
333
324
  spellcheck="false"
334
325
  onKeyDown=${e => {
335
326
  if (e.key === 'Tab') {
@@ -791,8 +782,8 @@ function AvailableExtCard({ ext, isInstalled, isInstalling, onInstall, onReinsta
791
782
  ${selectedAction && !loading && html`
792
783
  <div>
793
784
  <code class="adm-text-sm adm-text-dim">${ext.name}/actions/${selectedAction}.js</code>
794
- <textarea value=${script} onInput=${e => setScript(e.target.value)}
795
- style="width:100%;height:320px;font-family:'Fira Code',monospace;font-size:12px;line-height:1.5;padding:10px;border:1px solid var(--glass-border);border-radius:4px;background:rgba(0,0,0,0.3);color:var(--text-bright);resize:vertical;tab-size:2;margin-top:4px"
785
+ <textarea class="adm-textarea adm-input-full" value=${script} onInput=${e => setScript(e.target.value)}
786
+ style="height:320px;font-size:12px;margin-top:4px"
796
787
  spellcheck="false"
797
788
  onKeyDown=${e => {
798
789
  if (e.key === 'Tab') {
@@ -1,48 +1,52 @@
1
1
  /**
2
- * Admin Dashboard — Shared UI helpers
3
- * Reusable utilities used across admin tab components.
2
+ * @file shared.js
3
+ * @description Admin Dashboard shared UI helpers — the admin design system's own
4
+ * primitives (Badge, StatCard, StatsGrid, Spinner, Empty, ErrorBox, DataTable,
5
+ * ExpandableHelp, useToast/Toast, EconRow/HealthRow) + formatters. Admin is a
6
+ * self-contained design system (adm-* scoped); these are intentionally separate
7
+ * from the main /components primitives.
8
+ * @version-history
9
+ * v1.2.0 — 2026-06-02 — Component unification (#13 tables): DataTable is now a
10
+ * thin wrapper that renders `.adm-card` around the canonical
11
+ * /components/DataTable.js (imported, not bare-re-exported). Admin keeps its
12
+ * card wrapper and unchanged appearance; the table body is shared.
13
+ * v1.1.0 — 2026-06-02 — i18n the Spinner/ErrorBox defaults (t('common.loading') /
14
+ * t('common.error')) — were hardcoded English (Rule 4/7.8).
4
15
  */
5
16
  import { h } from 'preact';
6
17
  import { useState } from 'preact/hooks';
7
18
  import htm from 'htm';
8
19
  const html = htm.bind(h);
9
20
  import { escHtml } from '/js/utils.js';
10
-
11
- /** Format a number with locale */
12
- export function num(n) {
13
- return typeof n === 'number' ? n.toLocaleString() : String(n ?? '\u2014');
14
- }
15
-
16
- /** Format a date string */
17
- export function dt(s) {
18
- return s ? new Date(s).toLocaleString() : '\u2014';
19
- }
20
-
21
- /** Format uptime seconds to human-readable */
22
- export function fmtUp(s) {
23
- const d = Math.floor(s / 86400);
24
- const hr = Math.floor((s % 86400) / 3600);
25
- const m = Math.floor((s % 3600) / 60);
26
- return (d ? d + 'd ' : '') + (hr ? hr + 'h ' : '') + (m ? m + 'm' : '<1m');
27
- }
28
-
29
- /** Format bytes to human-readable */
30
- export function fmtBytes(b) {
31
- if (b < 1024) return b + ' B';
32
- if (b < 1048576) return (b / 1024).toFixed(1) + ' KB';
33
- return (b / 1048576).toFixed(1) + ' MB';
34
- }
21
+ import { t } from '/js/i18n.js';
22
+ import { EmptyState } from '/components/EmptyState.js';
23
+ // Import (not bare re-export) so we hold a local binding to wrap below — a
24
+ // `export { DataTable } from ...` would NOT create a usable local reference.
25
+ import { DataTable as GenericDataTable } from '/components/DataTable.js';
26
+
27
+ // Display formatters now live in the shared /js/format.js. Import them into local
28
+ // scope (StatCard etc. call num() directly) AND re-export so the existing admin
29
+ // importers (`import { num, dt, fmtUp, fmtBytes } from './shared.js'`) keep working.
30
+ import { num, dt, fmtUp, fmtBytes } from '/js/format.js';
31
+ export { num, dt, fmtUp, fmtBytes };
35
32
 
36
33
  /** Render a badge */
37
34
  export function Badge({ type }) {
38
35
  return html`<span class="adm-badge adm-badge-${type}">${type}</span>`;
39
36
  }
40
37
 
41
- /** Render a stat card */
42
- export function StatCard({ label, value, sub, color }) {
38
+ /**
39
+ * Render a stat card.
40
+ * @param {{ label: string, value: any, sub?: string, tone?: string, color?: string }} props
41
+ * tone — theme-aware modifier class (indigo|mint|green|cyan|amber|purple|blue|red). Preferred.
42
+ * color — legacy inline color (still honored if no tone); migrate callers to `tone`.
43
+ */
44
+ export function StatCard({ label, value, sub, tone, color }) {
45
+ const toneClass = tone ? ` ${tone}` : '';
46
+ const style = !tone && color ? `color:${color}` : '';
43
47
  return html`<div class="adm-card">
44
48
  <h2>${label}</h2>
45
- <div class="adm-stat" style="color:${color || 'var(--accent, #06b6d4)'}">${num(value)}</div>
49
+ <div class="adm-stat${toneClass}" style=${style}>${num(value)}</div>
46
50
  ${sub && html`<div class="adm-stat-label">${sub}</div>`}
47
51
  </div>`;
48
52
  }
@@ -50,7 +54,7 @@ export function StatCard({ label, value, sub, color }) {
50
54
  /** Render a stats grid (4-column) */
51
55
  export function StatsGrid({ items }) {
52
56
  return html`<div class="adm-grid adm-grid-4">
53
- ${items.map(i => html`<${StatCard} label=${i.label} value=${i.value} sub=${i.sub} color=${i.color} />`)}
57
+ ${items.map(i => html`<${StatCard} label=${i.label} value=${i.value} sub=${i.sub} tone=${i.tone} color=${i.color} />`)}
54
58
  </div>`;
55
59
  }
56
60
 
@@ -72,17 +76,17 @@ export function HealthRow({ label, obj }) {
72
76
 
73
77
  /** Loading spinner */
74
78
  export function Spinner({ text }) {
75
- return html`<div class="empty"><div class="spinner"></div> ${text || 'Loading...'}</div>`;
79
+ return html`<div class="empty"><div class="spinner"></div> ${text || t('common.loading')}</div>`;
76
80
  }
77
81
 
78
- /** Empty state */
82
+ /** Empty state — delegates to the canonical /components/EmptyState.js. */
79
83
  export function Empty({ text }) {
80
- return html`<div class="empty">${text}</div>`;
84
+ return html`<${EmptyState} text=${text} />`;
81
85
  }
82
86
 
83
87
  /** Error box */
84
88
  export function ErrorBox({ message }) {
85
- return html`<div class="error-box"><strong>Error</strong><br/>${escHtml(message)}</div>`;
89
+ return html`<div class="error-box"><strong>${t('common.error')}</strong><br/>${escHtml(message)}</div>`;
86
90
  }
87
91
 
88
92
  /** Expandable/collapsible help section — reusable across all tabs and portal pages */
@@ -94,33 +98,19 @@ export function ExpandableHelp({ title, children }) {
94
98
  }
95
99
 
96
100
  /**
97
- * DataTable — renders rows with optional raw HTML cells.
101
+ * DataTable (admin) — thin wrapper around the canonical
102
+ * /components/DataTable.js that adds admin's `.adm-card` container. The 36
103
+ * admin importers keep the same `{ headers, rows, scroll }` signature and the
104
+ * same admin appearance (the `.adm table` / `.adm .scrollable` / `.adm .mono`
105
+ * scoped CSS still wins over the generic `.data-table` inside `.adm`).
98
106
  *
99
- * SECURITY: When a cell object has `_html: true`, `cell.text` is rendered
100
- * as raw HTML via dangerouslySetInnerHTML. Callers MUST ensure `cell.text`
101
- * is sanitized (use escHtml() for any user-generated content).
102
- * Only use `_html` for trusted, server-generated markup like badges.
107
+ * SECURITY: cell objects with `_html: true` render `cell.text` as raw HTML;
108
+ * callers MUST sanitize (escHtml()) any user-generated content. See the
109
+ * generic DataTable for the full cell protocol.
103
110
  */
104
111
  export function DataTable({ headers, rows, scroll }) {
105
- const table = html`<table>
106
- <thead><tr>${headers.map(h => html`<th>${h}</th>`)}</tr></thead>
107
- <tbody>
108
- ${rows.map(row => html`<tr>
109
- ${row.map(cell => {
110
- if (cell && typeof cell === 'object' && cell._html) {
111
- return html`<td class=${cell.mono ? 'mono' : ''} title=${cell.title || ''}
112
- dangerouslySetInnerHTML=${{ __html: cell.text }}></td>`;
113
- }
114
- if (cell && typeof cell === 'object' && cell.mono) {
115
- return html`<td class="mono" title=${cell.title || ''}>${cell.text}</td>`;
116
- }
117
- return html`<td>${cell}</td>`;
118
- })}
119
- </tr>`)}
120
- </tbody>
121
- </table>`;
122
112
  return html`<div class="adm-card">
123
- ${scroll ? html`<div class="scrollable">${table}</div>` : table}
113
+ <${GenericDataTable} headers=${headers} rows=${rows} scroll=${scroll} />
124
114
  </div>`;
125
115
  }
126
116
 
@@ -11,7 +11,7 @@ import htm from 'htm';
11
11
  const html = htm.bind(h);
12
12
  import { t } from '/js/i18n.js';
13
13
  import { escHtml } from '/js/utils.js';
14
- import { num, dt, Empty } from './shared.js';
14
+ import { num, dt, Empty, StatsGrid } from './shared.js';
15
15
  import { apiGet } from '/js/api.js';
16
16
 
17
17
  export default function SharingGroupsTab({ data, reload, session }) {
@@ -19,8 +19,8 @@ export default function SharingGroupsTab({ data, reload, session }) {
19
19
  const [total, setTotal] = useState(0);
20
20
  const [loading, setLoading] = useState(false);
21
21
 
22
- const loadGroups = useCallback(async () => {
23
- setLoading(true);
22
+ const loadGroups = useCallback(async ({ showSpinner = true } = {}) => {
23
+ if (showSpinner) setLoading(true);
24
24
  try {
25
25
  const r = await apiGet('/v1/admin/sharing-groups');
26
26
  if (r.data) {
@@ -37,7 +37,7 @@ export default function SharingGroupsTab({ data, reload, session }) {
37
37
 
38
38
  // Listen for live updates
39
39
  useEffect(() => {
40
- const handler = () => { loadGroups(); };
40
+ const handler = () => { loadGroups({ showSpinner: false }); }; // silent: no flash
41
41
  window.addEventListener('aimeat-live-update', handler);
42
42
  return () => window.removeEventListener('aimeat-live-update', handler);
43
43
  }, [loadGroups]);
@@ -45,12 +45,7 @@ export default function SharingGroupsTab({ data, reload, session }) {
45
45
  return html`
46
46
  <div>
47
47
  <!-- Stats summary -->
48
- <div class="adm-mem-stats">
49
- <div class="adm-mem-stat">
50
- <span class="adm-mem-stat-value">${num(total)}</span>
51
- <span class="adm-mem-stat-label">${t('dashboard.sharingGroupsTotal')}</span>
52
- </div>
53
- </div>
48
+ <${StatsGrid} items=${[{ label: t('dashboard.sharingGroupsTotal'), value: total }]} />
54
49
 
55
50
  <!-- Table -->
56
51
  ${groups.length === 0 && !loading && html`<${Empty} text=${t('dashboard.sharingGroupsEmpty')} />`}
@@ -137,8 +137,8 @@ export default function StatsTab({ data }) {
137
137
  const [loading, setLoading] = useState(false);
138
138
  const chartRefs = useRef({});
139
139
 
140
- const fetchStats = useCallback(async (p, cf, ct) => {
141
- setLoading(true);
140
+ const fetchStats = useCallback(async (p, cf, ct, { showSpinner = true } = {}) => {
141
+ if (showSpinner) setLoading(true);
142
142
  try {
143
143
  const range = p === 'custom'
144
144
  ? (cf && ct ? { from: cf, to: ct } : null)
@@ -155,7 +155,7 @@ export default function StatsTab({ data }) {
155
155
 
156
156
  // Listen for live updates
157
157
  useEffect(() => {
158
- const handler = () => { fetchStats(period, customFrom, customTo); };
158
+ const handler = () => { fetchStats(period, customFrom, customTo, { showSpinner: false }); }; // silent: no flash
159
159
  window.addEventListener('aimeat-live-update', handler);
160
160
  return () => window.removeEventListener('aimeat-live-update', handler);
161
161
  }, [period, customFrom, customTo]);
@@ -1,12 +1,16 @@
1
1
  /**
2
- * Guides — View Module
3
- * Interactive guide index + detail views with copy-to-clipboard for AI chat.
2
+ * @file guides.js
3
+ * @description Guides — interactive guide index + detail views with
4
+ * copy-to-clipboard (markdown) for pasting a whole guide into an AI chat.
5
+ * @version-history
6
+ * v1.1.0 — 2026-06-02 — Migrate bespoke copy button to canonical CopyButton component
4
7
  */
5
8
  import { h } from 'preact';
6
9
  import { useState, useEffect, useCallback, useRef } from 'preact/hooks';
7
10
  import htm from 'htm';
8
11
  import { t } from '/js/i18n.js';
9
- import { escHtml, copyToClipboard } from '/js/utils.js';
12
+ import { escHtml } from '/js/utils.js';
13
+ import { CopyButton } from '/components/CopyButton.js';
10
14
 
11
15
  const html = htm.bind(h);
12
16
  const NODE_URL = window.location.origin;
@@ -294,7 +298,6 @@ function GuideIndex({ locale, onSelectGuide, navigate }) {
294
298
  }
295
299
 
296
300
  function GuideDetail({ slug, locale, onBack }) {
297
- const [copied, setCopied] = useState(false);
298
301
  const labels = LABELS[locale] || LABELS.en;
299
302
  const guide = GUIDES[slug];
300
303
  if (!guide) return html`<div class="gd-container"><p>Guide not found.</p></div>`;
@@ -312,12 +315,6 @@ function GuideDetail({ slug, locale, onBack }) {
312
315
  return md;
313
316
  }, [data]);
314
317
 
315
- const handleCopy = useCallback(() => {
316
- copyToClipboard(buildMarkdown());
317
- setCopied(true);
318
- setTimeout(() => setCopied(false), 2000);
319
- }, [buildMarkdown]);
320
-
321
318
  useEffect(() => {
322
319
  document.title = data.title + ' | AIME AT';
323
320
  window.scrollTo(0, 0);
@@ -334,9 +331,8 @@ function GuideDetail({ slug, locale, onBack }) {
334
331
 
335
332
  <div class="gd-copy-section">
336
333
  <div class="gd-copy-instruction">${labels.copyInstruction}</div>
337
- <button class="gd-copy-btn ${copied ? 'copied' : ''}" onClick=${handleCopy}>
338
- ${copied ? '\u2714 ' + labels.copiedLabel : '\uD83D\uDCCB ' + labels.copyBtnLabel}
339
- </button>
334
+ <${CopyButton} text=${buildMarkdown()} className="gd-copy-btn"
335
+ label=${'\uD83D\uDCCB ' + labels.copyBtnLabel} copiedLabel=${'\u2714 ' + labels.copiedLabel} />
340
336
  </div>
341
337
 
342
338
  <div class="gd-tech-warning">${labels.techWarning}</div>
@@ -7,12 +7,14 @@
7
7
  * @usage import HelpView from '/views/help.js'
8
8
  * @version-history
9
9
  * v1.0.0 — 2026-03-23 — Initial implementation
10
+ * v1.1.0 — 2026-06-02 — Migrate bespoke copy button to canonical CopyButton component
10
11
  */
11
12
  import { h } from 'preact';
12
13
  import { useState, useEffect } from 'preact/hooks';
13
14
  import htm from 'htm';
14
15
  import { t } from '/js/i18n.js';
15
16
  import { useViewCSS } from '/components/useViewCSS.js';
17
+ import { CopyButton } from '/components/CopyButton.js';
16
18
 
17
19
  const html = htm.bind(h);
18
20
 
@@ -22,7 +24,6 @@ export default function HelpView() {
22
24
  const [prompt, setPrompt] = useState('');
23
25
  const [loading, setLoading] = useState(true);
24
26
  const [error, setError] = useState(false);
25
- const [copied, setCopied] = useState(false);
26
27
 
27
28
  useEffect(() => {
28
29
  fetch('/v1/help/prompt')
@@ -34,26 +35,6 @@ export default function HelpView() {
34
35
  .catch(() => { setError(true); setLoading(false); });
35
36
  }, []);
36
37
 
37
- const handleCopy = async () => {
38
- try {
39
- await navigator.clipboard.writeText(prompt);
40
- setCopied(true);
41
- setTimeout(() => setCopied(false), 2000);
42
- } catch {
43
- // Fallback for older browsers
44
- const ta = document.createElement('textarea');
45
- ta.value = prompt;
46
- ta.style.position = 'fixed';
47
- ta.style.left = '-9999px';
48
- document.body.appendChild(ta);
49
- ta.select();
50
- document.execCommand('copy');
51
- document.body.removeChild(ta);
52
- setCopied(true);
53
- setTimeout(() => setCopied(false), 2000);
54
- }
55
- };
56
-
57
38
  return html`
58
39
  <div class="hlp-container">
59
40
  <div class="hlp-card">
@@ -69,9 +50,7 @@ export default function HelpView() {
69
50
  </div>
70
51
 
71
52
  ${!loading && !error && html`
72
- <button class="${copied ? 'hlp-copy-btn hlp-copied' : 'hlp-copy-btn'}" onClick=${handleCopy}>
73
- ${copied ? t('help.copied') : t('help.copyBtn')}
74
- </button>
53
+ <${CopyButton} text=${prompt} label=${t('help.copyBtn')} copiedLabel=${t('help.copied')} className="hlp-copy-btn" />
75
54
  `}
76
55
  </div>
77
56
  </div>