@elizaos/app-core 2.0.0-alpha.10

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 (399) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +90 -0
  3. package/src/App.tsx +472 -0
  4. package/src/actions/character.test.ts +139 -0
  5. package/src/actions/character.ts +152 -0
  6. package/src/actions/chat-helpers.ts +100 -0
  7. package/src/actions/cloud.ts +59 -0
  8. package/src/actions/index.ts +12 -0
  9. package/src/actions/lifecycle.ts +175 -0
  10. package/src/actions/onboarding.ts +46 -0
  11. package/src/actions/triggers.ts +190 -0
  12. package/src/ambient.d.ts +16 -0
  13. package/src/api/client.ts +5516 -0
  14. package/src/api/index.ts +1 -0
  15. package/src/autonomy/index.ts +477 -0
  16. package/src/bridge/capacitor-bridge.ts +295 -0
  17. package/src/bridge/electrobun-rpc.ts +58 -0
  18. package/src/bridge/electrobun-runtime.ts +28 -0
  19. package/src/bridge/index.ts +5 -0
  20. package/src/bridge/native-plugins.ts +134 -0
  21. package/src/bridge/plugin-bridge.ts +352 -0
  22. package/src/bridge/storage-bridge.ts +162 -0
  23. package/src/chat/index.ts +250 -0
  24. package/src/coding/index.ts +43 -0
  25. package/src/components/AdvancedPageView.tsx +362 -0
  26. package/src/components/AgentActivityBox.tsx +49 -0
  27. package/src/components/ApiKeyConfig.tsx +224 -0
  28. package/src/components/AppsPageView.tsx +52 -0
  29. package/src/components/AppsView.tsx +293 -0
  30. package/src/components/AvatarLoader.tsx +86 -0
  31. package/src/components/AvatarSelector.tsx +223 -0
  32. package/src/components/BscTradePanel.tsx +549 -0
  33. package/src/components/BugReportModal.tsx +499 -0
  34. package/src/components/CharacterView.tsx +1645 -0
  35. package/src/components/ChatAvatar.test.ts +96 -0
  36. package/src/components/ChatAvatar.tsx +147 -0
  37. package/src/components/ChatComposer.tsx +330 -0
  38. package/src/components/ChatMessage.tsx +448 -0
  39. package/src/components/ChatModalView.test.tsx +118 -0
  40. package/src/components/ChatModalView.tsx +125 -0
  41. package/src/components/ChatView.tsx +992 -0
  42. package/src/components/CloudSourceControls.tsx +80 -0
  43. package/src/components/CodingAgentSettingsSection.tsx +536 -0
  44. package/src/components/CommandPalette.tsx +284 -0
  45. package/src/components/CompanionSceneHost.tsx +497 -0
  46. package/src/components/CompanionShell.tsx +31 -0
  47. package/src/components/CompanionView.tsx +109 -0
  48. package/src/components/ConfigPageView.tsx +758 -0
  49. package/src/components/ConfigSaveFooter.tsx +41 -0
  50. package/src/components/ConfirmModal.tsx +379 -0
  51. package/src/components/ConnectionFailedBanner.tsx +91 -0
  52. package/src/components/ConnectorsPageView.tsx +13 -0
  53. package/src/components/ConversationsSidebar.tsx +279 -0
  54. package/src/components/CustomActionEditor.tsx +1125 -0
  55. package/src/components/CustomActionsPanel.tsx +288 -0
  56. package/src/components/CustomActionsView.tsx +322 -0
  57. package/src/components/DatabasePageView.tsx +55 -0
  58. package/src/components/DatabaseView.tsx +814 -0
  59. package/src/components/ElizaCloudDashboard.tsx +1696 -0
  60. package/src/components/EmotePicker.tsx +529 -0
  61. package/src/components/ErrorBoundary.tsx +76 -0
  62. package/src/components/FineTuningView.tsx +1077 -0
  63. package/src/components/GameView.tsx +552 -0
  64. package/src/components/GameViewOverlay.tsx +133 -0
  65. package/src/components/GlobalEmoteOverlay.tsx +155 -0
  66. package/src/components/Header.test.tsx +413 -0
  67. package/src/components/Header.tsx +403 -0
  68. package/src/components/HeartbeatsView.tsx +1003 -0
  69. package/src/components/InventoryView.tsx +385 -0
  70. package/src/components/KnowledgeView.tsx +1128 -0
  71. package/src/components/LanguageDropdown.tsx +188 -0
  72. package/src/components/LifoMonitorPanel.tsx +196 -0
  73. package/src/components/LifoSandboxView.tsx +499 -0
  74. package/src/components/LoadingScreen.tsx +77 -0
  75. package/src/components/LogsPageView.tsx +17 -0
  76. package/src/components/LogsView.tsx +239 -0
  77. package/src/components/MediaGalleryView.tsx +433 -0
  78. package/src/components/MediaSettingsSection.tsx +893 -0
  79. package/src/components/MessageContent.tsx +815 -0
  80. package/src/components/OnboardingWizard.test.tsx +107 -0
  81. package/src/components/OnboardingWizard.tsx +189 -0
  82. package/src/components/PairingView.tsx +110 -0
  83. package/src/components/PermissionsSection.tsx +1186 -0
  84. package/src/components/PluginsPageView.tsx +9 -0
  85. package/src/components/PluginsView.tsx +3157 -0
  86. package/src/components/ProviderSwitcher.tsx +908 -0
  87. package/src/components/RestartBanner.tsx +76 -0
  88. package/src/components/RuntimeView.tsx +460 -0
  89. package/src/components/SaveCommandModal.tsx +211 -0
  90. package/src/components/SecretsView.tsx +569 -0
  91. package/src/components/SettingsView.tsx +825 -0
  92. package/src/components/ShellOverlays.tsx +41 -0
  93. package/src/components/ShortcutsOverlay.tsx +155 -0
  94. package/src/components/SkillsView.tsx +1435 -0
  95. package/src/components/StartupFailureView.tsx +63 -0
  96. package/src/components/StreamView.tsx +483 -0
  97. package/src/components/StripeEmbeddedCheckout.tsx +155 -0
  98. package/src/components/SubscriptionStatus.tsx +640 -0
  99. package/src/components/SystemWarningBanner.tsx +71 -0
  100. package/src/components/ThemeToggle.tsx +100 -0
  101. package/src/components/TrajectoriesView.tsx +526 -0
  102. package/src/components/TrajectoryDetailView.tsx +426 -0
  103. package/src/components/TriggersView.tsx +1 -0
  104. package/src/components/VectorBrowserView.tsx +1633 -0
  105. package/src/components/VoiceConfigView.tsx +675 -0
  106. package/src/components/VrmStage.test.ts +219 -0
  107. package/src/components/VrmStage.tsx +432 -0
  108. package/src/components/WhatsAppQrOverlay.tsx +230 -0
  109. package/src/components/__tests__/chainConfig.test.ts +220 -0
  110. package/src/components/apps/AppDetailPane.tsx +242 -0
  111. package/src/components/apps/AppsCatalogGrid.tsx +137 -0
  112. package/src/components/apps/extensions/HyperscapeAppDetailPanel.tsx +577 -0
  113. package/src/components/apps/extensions/registry.ts +16 -0
  114. package/src/components/apps/extensions/types.ts +9 -0
  115. package/src/components/apps/helpers.ts +44 -0
  116. package/src/components/avatar/VrmAnimationLoader.test.ts +164 -0
  117. package/src/components/avatar/VrmAnimationLoader.ts +151 -0
  118. package/src/components/avatar/VrmBlinkController.ts +118 -0
  119. package/src/components/avatar/VrmCameraManager.ts +407 -0
  120. package/src/components/avatar/VrmEngine.ts +2678 -0
  121. package/src/components/avatar/VrmFootShadow.ts +96 -0
  122. package/src/components/avatar/VrmViewer.tsx +421 -0
  123. package/src/components/avatar/__tests__/VrmCameraManager.test.ts +168 -0
  124. package/src/components/avatar/__tests__/VrmEngine.test.ts +1574 -0
  125. package/src/components/avatar/mixamoVRMRigMap.ts +62 -0
  126. package/src/components/avatar/retargetMixamoFbxToVrm.ts +144 -0
  127. package/src/components/avatar/retargetMixamoGltfToVrm.ts +119 -0
  128. package/src/components/chainConfig.ts +380 -0
  129. package/src/components/companion/CompanionHeader.tsx +47 -0
  130. package/src/components/companion/CompanionSceneHost.tsx +1 -0
  131. package/src/components/companion/VrmStage.tsx +2 -0
  132. package/src/components/companion/__tests__/walletUtils.test.ts +742 -0
  133. package/src/components/companion/walletUtils.ts +290 -0
  134. package/src/components/companion-shell-styles.test.ts +142 -0
  135. package/src/components/companion-shell-styles.ts +270 -0
  136. package/src/components/confirm-delete-control.tsx +69 -0
  137. package/src/components/conversations/ConversationListItem.tsx +185 -0
  138. package/src/components/conversations/conversation-utils.ts +151 -0
  139. package/src/components/format.ts +131 -0
  140. package/src/components/index.ts +94 -0
  141. package/src/components/inventory/CopyableAddress.tsx +41 -0
  142. package/src/components/inventory/InventoryToolbar.tsx +142 -0
  143. package/src/components/inventory/NftGrid.tsx +99 -0
  144. package/src/components/inventory/TokenLogo.tsx +71 -0
  145. package/src/components/inventory/TokensTable.tsx +216 -0
  146. package/src/components/inventory/constants.ts +170 -0
  147. package/src/components/inventory/index.ts +29 -0
  148. package/src/components/inventory/media-url.test.ts +38 -0
  149. package/src/components/inventory/media-url.ts +36 -0
  150. package/src/components/inventory/useInventoryData.ts +460 -0
  151. package/src/components/knowledge-upload-image.ts +215 -0
  152. package/src/components/labels.ts +46 -0
  153. package/src/components/onboarding/ActivateStep.tsx +30 -0
  154. package/src/components/onboarding/ConnectionStep.tsx +1530 -0
  155. package/src/components/onboarding/IdentityStep.tsx +147 -0
  156. package/src/components/onboarding/OnboardingPanel.tsx +39 -0
  157. package/src/components/onboarding/OnboardingStepNav.tsx +31 -0
  158. package/src/components/onboarding/PermissionsStep.tsx +20 -0
  159. package/src/components/onboarding/RpcStep.tsx +402 -0
  160. package/src/components/onboarding/WakeUpStep.tsx +184 -0
  161. package/src/components/permissions/PermissionIcon.tsx +25 -0
  162. package/src/components/permissions/StreamingPermissions.tsx +413 -0
  163. package/src/components/plugins/showcase-data.ts +481 -0
  164. package/src/components/shared/ShellHeaderControls.tsx +193 -0
  165. package/src/components/shared-companion-scene-context.ts +15 -0
  166. package/src/components/skeletons.tsx +88 -0
  167. package/src/components/stream/ActivityFeed.tsx +113 -0
  168. package/src/components/stream/AvatarPip.tsx +10 -0
  169. package/src/components/stream/ChatContent.tsx +126 -0
  170. package/src/components/stream/ChatTicker.tsx +55 -0
  171. package/src/components/stream/IdleContent.tsx +73 -0
  172. package/src/components/stream/StatusBar.tsx +469 -0
  173. package/src/components/stream/StreamSettings.tsx +506 -0
  174. package/src/components/stream/StreamTerminal.tsx +94 -0
  175. package/src/components/stream/StreamVoiceConfig.tsx +160 -0
  176. package/src/components/stream/helpers.ts +134 -0
  177. package/src/components/stream/overlays/OverlayLayer.tsx +75 -0
  178. package/src/components/stream/overlays/built-in/ActionTickerWidget.tsx +64 -0
  179. package/src/components/stream/overlays/built-in/AlertPopupWidget.tsx +87 -0
  180. package/src/components/stream/overlays/built-in/BrandingWidget.tsx +51 -0
  181. package/src/components/stream/overlays/built-in/CustomHtmlWidget.tsx +105 -0
  182. package/src/components/stream/overlays/built-in/PeonGlassWidget.tsx +265 -0
  183. package/src/components/stream/overlays/built-in/PeonHudWidget.tsx +247 -0
  184. package/src/components/stream/overlays/built-in/PeonSakuraWidget.tsx +278 -0
  185. package/src/components/stream/overlays/built-in/ThoughtBubbleWidget.tsx +77 -0
  186. package/src/components/stream/overlays/built-in/ViewerCountWidget.tsx +46 -0
  187. package/src/components/stream/overlays/built-in/index.ts +13 -0
  188. package/src/components/stream/overlays/registry.ts +22 -0
  189. package/src/components/stream/overlays/types.ts +90 -0
  190. package/src/components/stream/overlays/useOverlayLayout.ts +218 -0
  191. package/src/components/trajectory-format.ts +50 -0
  192. package/src/components/ui-badges.tsx +109 -0
  193. package/src/components/ui-switch.tsx +57 -0
  194. package/src/components/vector-browser-three.ts +27 -0
  195. package/src/config/config-catalog.ts +1092 -0
  196. package/src/config/config-field.tsx +1901 -0
  197. package/src/config/config-renderer.tsx +730 -0
  198. package/src/config/index.ts +11 -0
  199. package/src/config/ui-renderer.tsx +1751 -0
  200. package/src/config/ui-spec.ts +256 -0
  201. package/src/events/index.ts +89 -0
  202. package/src/hooks/index.ts +13 -0
  203. package/src/hooks/useBugReport.tsx +43 -0
  204. package/src/hooks/useCanvasWindow.ts +372 -0
  205. package/src/hooks/useChatAvatarVoice.ts +111 -0
  206. package/src/hooks/useContextMenu.ts +127 -0
  207. package/src/hooks/useKeyboardShortcuts.ts +86 -0
  208. package/src/hooks/useLifoSync.ts +143 -0
  209. package/src/hooks/useMemoryMonitor.ts +334 -0
  210. package/src/hooks/useRenderGuard.ts +43 -0
  211. package/src/hooks/useRetakeCapture.ts +67 -0
  212. package/src/hooks/useStreamPopoutNavigation.ts +27 -0
  213. package/src/hooks/useTimeout.ts +37 -0
  214. package/src/hooks/useVoiceChat.ts +1441 -0
  215. package/src/hooks/useWhatsAppPairing.ts +123 -0
  216. package/src/i18n/index.ts +76 -0
  217. package/src/i18n/locales/en.json +1194 -0
  218. package/src/i18n/locales/es.json +1194 -0
  219. package/src/i18n/locales/ko.json +1194 -0
  220. package/src/i18n/locales/pt.json +1194 -0
  221. package/src/i18n/locales/zh-CN.json +1194 -0
  222. package/src/i18n/messages.ts +21 -0
  223. package/src/index.ts +6 -0
  224. package/src/navigation/index.ts +282 -0
  225. package/src/navigation.test.ts +189 -0
  226. package/src/onboarding-config.test.ts +104 -0
  227. package/src/onboarding-config.ts +114 -0
  228. package/src/platform/browser-launch.test.ts +94 -0
  229. package/src/platform/browser-launch.ts +149 -0
  230. package/src/platform/index.ts +58 -0
  231. package/src/platform/init.ts +236 -0
  232. package/src/platform/lifo.ts +215 -0
  233. package/src/providers/index.ts +99 -0
  234. package/src/state/AppContext.tsx +5846 -0
  235. package/src/state/index.ts +6 -0
  236. package/src/state/internal.ts +86 -0
  237. package/src/state/onboarding-resume.test.ts +135 -0
  238. package/src/state/onboarding-resume.ts +263 -0
  239. package/src/state/parsers.test.ts +124 -0
  240. package/src/state/parsers.ts +308 -0
  241. package/src/state/persistence.ts +321 -0
  242. package/src/state/shell-routing.ts +32 -0
  243. package/src/state/types.ts +701 -0
  244. package/src/state/ui-preferences.ts +3 -0
  245. package/src/state/useApp.ts +23 -0
  246. package/src/state/vrm.ts +76 -0
  247. package/src/stories/AppMockProvider.tsx +32 -0
  248. package/src/stories/ChatEmptyState.stories.tsx +27 -0
  249. package/src/stories/ChatMessage.stories.tsx +115 -0
  250. package/src/stories/CompanionHeader.stories.tsx +74 -0
  251. package/src/stories/CompanionView.stories.tsx +33 -0
  252. package/src/stories/ConversationListItem.stories.tsx +102 -0
  253. package/src/stories/TypingIndicator.stories.tsx +28 -0
  254. package/src/styles/anime.css +6324 -0
  255. package/src/styles/base.css +196 -0
  256. package/src/styles/onboarding-game.css +738 -0
  257. package/src/styles/styles.css +2087 -0
  258. package/src/styles/xterm.css +241 -0
  259. package/src/types/index.ts +715 -0
  260. package/src/types/react-test-renderer.d.ts +45 -0
  261. package/src/utils/asset-url.ts +110 -0
  262. package/src/utils/assistant-text.ts +172 -0
  263. package/src/utils/clipboard.ts +41 -0
  264. package/src/utils/desktop-dialogs.ts +80 -0
  265. package/src/utils/index.ts +6 -0
  266. package/src/utils/number-parsing.ts +125 -0
  267. package/src/utils/openExternalUrl.ts +20 -0
  268. package/src/utils/spoken-text.ts +65 -0
  269. package/src/utils/streaming-text.ts +120 -0
  270. package/src/voice/index.ts +1 -0
  271. package/src/voice/types.ts +197 -0
  272. package/src/wallet-rpc.ts +176 -0
  273. package/test/app/AppContext.pty-sessions.test.tsx +143 -0
  274. package/test/app/MessageContent.test.tsx +326 -0
  275. package/test/app/PermissionsOnboarding.test.tsx +356 -0
  276. package/test/app/PermissionsSection.test.tsx +573 -0
  277. package/test/app/advanced-trajectory-fine-tuning.e2e.test.ts +393 -0
  278. package/test/app/agent-activity-box.test.tsx +132 -0
  279. package/test/app/agent-transfer-lock.test.ts +274 -0
  280. package/test/app/api-client-electron-fallback.test.ts +139 -0
  281. package/test/app/api-client-timeout.test.ts +75 -0
  282. package/test/app/api-client-ws.test.ts +98 -0
  283. package/test/app/api-client.ws-max-reconnect.test.ts +139 -0
  284. package/test/app/api-client.ws-reconnect.test.ts +157 -0
  285. package/test/app/app-context-autonomy-events.test.ts +478 -0
  286. package/test/app/apps-page-view.test.ts +114 -0
  287. package/test/app/apps-view.test.ts +769 -0
  288. package/test/app/autonomous-workflows.e2e.test.ts +765 -0
  289. package/test/app/autonomy-events.test.ts +150 -0
  290. package/test/app/avatar-selector.test.tsx +52 -0
  291. package/test/app/bsc-trade-panel.test.tsx +134 -0
  292. package/test/app/bug-report-modal.test.tsx +353 -0
  293. package/test/app/character-customization.e2e.test.ts +1199 -0
  294. package/test/app/chat-advanced-features.e2e.test.ts +706 -0
  295. package/test/app/chat-composer.test.tsx +181 -0
  296. package/test/app/chat-language-header.test.ts +64 -0
  297. package/test/app/chat-message.test.tsx +222 -0
  298. package/test/app/chat-modal-view.test.tsx +191 -0
  299. package/test/app/chat-routine-filter.test.ts +96 -0
  300. package/test/app/chat-send-lock.test.ts +1465 -0
  301. package/test/app/chat-stream-api-client.test.tsx +390 -0
  302. package/test/app/chat-view-game-modal.test.tsx +661 -0
  303. package/test/app/chat-view.test.tsx +877 -0
  304. package/test/app/cloud-api.e2e.test.ts +258 -0
  305. package/test/app/cloud-login-flow.e2e.test.ts +494 -0
  306. package/test/app/cloud-login-lock.test.ts +411 -0
  307. package/test/app/command-palette.test.tsx +184 -0
  308. package/test/app/command-registry.test.ts +75 -0
  309. package/test/app/companion-greeting-wave.test.tsx +425 -0
  310. package/test/app/companion-stale-conversation.test.tsx +447 -0
  311. package/test/app/companion-view.test.tsx +686 -0
  312. package/test/app/confirm-delete-control.test.ts +79 -0
  313. package/test/app/confirm-modal.test.tsx +219 -0
  314. package/test/app/connectors-ui.e2e.test.ts +508 -0
  315. package/test/app/conversations-sidebar-game-modal.test.tsx +260 -0
  316. package/test/app/conversations-sidebar.test.tsx +160 -0
  317. package/test/app/custom-actions-smoke.test.ts +387 -0
  318. package/test/app/custom-avatar-api-client.test.ts +207 -0
  319. package/test/app/desktop-utils.test.ts +145 -0
  320. package/test/app/electrobun-rpc-bridge.test.ts +83 -0
  321. package/test/app/events.test.ts +88 -0
  322. package/test/app/export-import-flows.e2e.test.ts +700 -0
  323. package/test/app/fine-tuning-view.test.ts +471 -0
  324. package/test/app/game-view-auth-session.test.tsx +186 -0
  325. package/test/app/game-view.test.ts +444 -0
  326. package/test/app/global-emote-overlay.test.tsx +106 -0
  327. package/test/app/header-status.test.tsx +149 -0
  328. package/test/app/i18n.test.ts +152 -0
  329. package/test/app/inventory-bsc-view.test.ts +940 -0
  330. package/test/app/knowledge-ui.e2e.test.ts +762 -0
  331. package/test/app/knowledge-upload-helpers.test.ts +124 -0
  332. package/test/app/lifecycle-lock.test.ts +267 -0
  333. package/test/app/lifo-popout-utils.test.ts +208 -0
  334. package/test/app/lifo-safe-endpoint.test.ts +34 -0
  335. package/test/app/loading-screen.test.tsx +45 -0
  336. package/test/app/memory-monitor.test.ts +332 -0
  337. package/test/app/navigation.test.tsx +22 -0
  338. package/test/app/onboarding-finish-lock.test.ts +663 -0
  339. package/test/app/onboarding-language.test.tsx +160 -0
  340. package/test/app/onboarding-steps.test.tsx +375 -0
  341. package/test/app/open-external-url.test.ts +65 -0
  342. package/test/app/pages-navigation-smoke.e2e.test.ts +633 -0
  343. package/test/app/pairing-lock.test.ts +260 -0
  344. package/test/app/pairing-view.test.tsx +74 -0
  345. package/test/app/permissions-section.test.ts +432 -0
  346. package/test/app/plugin-bridge.test.ts +109 -0
  347. package/test/app/plugins-ui.e2e.test.ts +605 -0
  348. package/test/app/plugins-view-game-modal.test.tsx +650 -0
  349. package/test/app/plugins-view-toggle-restart.test.ts +129 -0
  350. package/test/app/provider-dropdown-default.test.tsx +302 -0
  351. package/test/app/restart-banner.test.tsx +197 -0
  352. package/test/app/retake-capture.test.ts +84 -0
  353. package/test/app/sandbox-api-client.test.ts +108 -0
  354. package/test/app/save-command-modal.test.tsx +109 -0
  355. package/test/app/secrets-view.test.tsx +92 -0
  356. package/test/app/settings-control-styles.test.tsx +142 -0
  357. package/test/app/settings-reset.e2e.test.ts +726 -0
  358. package/test/app/settings-sections.e2e.test.ts +614 -0
  359. package/test/app/shared-format.test.ts +44 -0
  360. package/test/app/shared-switch.test.ts +69 -0
  361. package/test/app/shell-mode-switching.e2e.test.ts +829 -0
  362. package/test/app/shell-mode-tab-memory.test.tsx +58 -0
  363. package/test/app/shell-overlays.test.tsx +50 -0
  364. package/test/app/shortcuts-overlay.test.tsx +111 -0
  365. package/test/app/sse-interruption.test.ts +122 -0
  366. package/test/app/startup-asset-missing.e2e.test.ts +126 -0
  367. package/test/app/startup-backend-missing.e2e.test.ts +118 -0
  368. package/test/app/startup-chat.e2e.test.ts +305 -0
  369. package/test/app/startup-conversation-restore.test.tsx +344 -0
  370. package/test/app/startup-failure-view.test.tsx +103 -0
  371. package/test/app/startup-onboarding.e2e.test.ts +618 -0
  372. package/test/app/startup-timeout.test.tsx +80 -0
  373. package/test/app/startup-token-401.e2e.test.ts +103 -0
  374. package/test/app/stream-helpers.test.ts +46 -0
  375. package/test/app/stream-popout-navigation.test.tsx +41 -0
  376. package/test/app/stream-status-bar.test.tsx +89 -0
  377. package/test/app/theme-toggle.test.tsx +33 -0
  378. package/test/app/training-api-client.test.ts +128 -0
  379. package/test/app/trajectories-view.test.tsx +220 -0
  380. package/test/app/triggers-api-client.test.ts +77 -0
  381. package/test/app/triggers-navigation.test.ts +113 -0
  382. package/test/app/triggers-view.e2e.test.ts +674 -0
  383. package/test/app/update-channel-lock.test.ts +259 -0
  384. package/test/app/vector-browser.async-cleanup.test.tsx +367 -0
  385. package/test/app/vector-browser.e2e.test.ts +653 -0
  386. package/test/app/vrm-stage.test.tsx +351 -0
  387. package/test/app/vrm-viewer.test.tsx +298 -0
  388. package/test/app/wallet-api-save-lock.test.ts +298 -0
  389. package/test/app/wallet-hooks.test.ts +405 -0
  390. package/test/app/wallet-ui-flows.e2e.test.ts +556 -0
  391. package/test/avatar/asset-url.test.ts +90 -0
  392. package/test/avatar/avatar-selector.test.ts +173 -0
  393. package/test/avatar/mixamo-vrm-rig-map.test.ts +111 -0
  394. package/test/avatar/voice-chat-streaming-text.test.ts +96 -0
  395. package/test/avatar/voice-chat.test.ts +391 -0
  396. package/test/ui/command-palette-commands.test.ts +57 -0
  397. package/test/ui/ui-renderer.test.ts +39 -0
  398. package/tsconfig.build.json +19 -0
  399. package/tsconfig.json +20 -0
@@ -0,0 +1,71 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { useApp } from "../state";
3
+
4
+ const AUTO_DISMISS_MS = 20_000;
5
+
6
+ /**
7
+ * Renders amber warning banners for system-level warnings
8
+ * broadcast via WebSocket `system-warning` events.
9
+ */
10
+ export function SystemWarningBanner() {
11
+ const { systemWarnings, dismissSystemWarning, backendConnection } = useApp();
12
+ const timersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(
13
+ new Map(),
14
+ );
15
+
16
+ useEffect(() => {
17
+ if (!systemWarnings?.length) return;
18
+ const timers = timersRef.current;
19
+ for (const message of systemWarnings) {
20
+ if (!timers.has(message)) {
21
+ const timer = setTimeout(() => {
22
+ timers.delete(message);
23
+ dismissSystemWarning(message);
24
+ }, AUTO_DISMISS_MS);
25
+ timers.set(message, timer);
26
+ }
27
+ }
28
+ for (const [msg, timer] of timers) {
29
+ if (!systemWarnings.includes(msg)) {
30
+ clearTimeout(timer);
31
+ timers.delete(msg);
32
+ }
33
+ }
34
+ }, [systemWarnings, dismissSystemWarning]);
35
+
36
+ useEffect(() => {
37
+ const timers = timersRef.current;
38
+ return () => {
39
+ for (const timer of timers.values()) clearTimeout(timer);
40
+ timers.clear();
41
+ };
42
+ }, []);
43
+
44
+ if (!systemWarnings?.length) return null;
45
+
46
+ const connectionBannerVisible =
47
+ backendConnection?.state === "reconnecting" ||
48
+ backendConnection?.state === "failed";
49
+ const baseTop = connectionBannerVisible ? 36 : 0;
50
+
51
+ return (
52
+ <>
53
+ {systemWarnings.map((message, index) => (
54
+ <div
55
+ key={message}
56
+ className="fixed left-0 right-0 z-[9998] flex items-center justify-between gap-3 bg-amber-500 px-4 py-2 text-[13px] font-medium text-white shadow-lg"
57
+ style={{ top: `${baseTop + index * 36}px` }}
58
+ >
59
+ <span className="truncate">{message}</span>
60
+ <button
61
+ type="button"
62
+ onClick={() => dismissSystemWarning(message)}
63
+ className="rounded px-2 py-0.5 text-[12px] text-amber-100 hover:bg-amber-600 transition-colors cursor-pointer shrink-0"
64
+ >
65
+ x
66
+ </button>
67
+ </div>
68
+ ))}
69
+ </>
70
+ );
71
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * ThemeToggle — day/night switch for light/dark theme.
3
+ *
4
+ * Fully props-driven; no app context dependency. Takes the current theme
5
+ * and a setter from the caller. Works in both "native" and "companion"
6
+ * visual variants.
7
+ */
8
+
9
+ import { useCallback } from "react";
10
+ import type { UiTheme } from "../state/persistence";
11
+
12
+ /** Minimal translator function type. */
13
+ export type ThemeTranslatorFn = (key: string) => string;
14
+
15
+ export interface ThemeToggleProps {
16
+ uiTheme: UiTheme;
17
+ setUiTheme: (theme: UiTheme) => void;
18
+ /** Optional translator for ARIA labels */
19
+ t?: ThemeTranslatorFn;
20
+ /** Optional extra className on the root */
21
+ className?: string;
22
+ variant?: "native" | "companion";
23
+ }
24
+
25
+ /** Sun icon SVG for light mode indicator */
26
+ function SunIcon({ className }: { className?: string }) {
27
+ return (
28
+ <svg
29
+ className={className}
30
+ width="16"
31
+ height="16"
32
+ viewBox="0 0 24 24"
33
+ fill="none"
34
+ stroke="currentColor"
35
+ strokeWidth="2"
36
+ strokeLinecap="round"
37
+ strokeLinejoin="round"
38
+ aria-hidden="true"
39
+ >
40
+ <circle cx="12" cy="12" r="5" />
41
+ <line x1="12" y1="1" x2="12" y2="3" />
42
+ <line x1="12" y1="21" x2="12" y2="23" />
43
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
44
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
45
+ <line x1="1" y1="12" x2="3" y2="12" />
46
+ <line x1="21" y1="12" x2="23" y2="12" />
47
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
48
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
49
+ </svg>
50
+ );
51
+ }
52
+
53
+ /** Moon icon SVG for dark mode indicator */
54
+ function MoonIcon({ className }: { className?: string }) {
55
+ return (
56
+ <svg
57
+ className={className}
58
+ width="16"
59
+ height="16"
60
+ viewBox="0 0 24 24"
61
+ fill="none"
62
+ stroke="currentColor"
63
+ strokeWidth="2"
64
+ strokeLinecap="round"
65
+ strokeLinejoin="round"
66
+ aria-hidden="true"
67
+ >
68
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
69
+ </svg>
70
+ );
71
+ }
72
+
73
+ export function ThemeToggle({
74
+ uiTheme,
75
+ setUiTheme,
76
+ t: _t,
77
+ className,
78
+ variant: _variant = "native",
79
+ }: ThemeToggleProps) {
80
+ const isDark = uiTheme === "dark";
81
+
82
+ const handleToggle = useCallback(() => {
83
+ setUiTheme(isDark ? "light" : "dark");
84
+ }, [isDark, setUiTheme]);
85
+
86
+ return (
87
+ <button
88
+ type="button"
89
+ onClick={handleToggle}
90
+ className={`inline-flex items-center justify-center w-11 h-11 min-w-[44px] min-h-[44px] border border-border/50 bg-bg/50 backdrop-blur-md cursor-pointer text-sm leading-none hover:border-accent hover:text-txt font-medium hover:-translate-y-0.5 transition-all duration-300 hover:shadow-[0_0_15px_rgba(var(--accent),0.5)] active:scale-95 rounded-xl text-txt shadow-sm ${className ?? ""}`}
91
+ data-testid="theme-toggle"
92
+ >
93
+ {isDark ? (
94
+ <SunIcon className="w-5 h-5" />
95
+ ) : (
96
+ <MoonIcon className="w-5 h-5" />
97
+ )}
98
+ </button>
99
+ );
100
+ }
@@ -0,0 +1,526 @@
1
+ /**
2
+ * TrajectoriesView — view and analyze LLM call trajectories.
3
+ *
4
+ * Shows all captured LLM interactions with token counts, latency, and context.
5
+ * Supports filtering, search, export, and clearing trajectories.
6
+ */
7
+
8
+ import {
9
+ client,
10
+ type TrajectoryConfig,
11
+ type TrajectoryListResult,
12
+ type TrajectoryRecord,
13
+ type TrajectoryStats,
14
+ } from "@elizaos/app-core/api";
15
+ import { useApp } from "@elizaos/app-core/state";
16
+ import { confirmDesktopAction } from "@elizaos/app-core/utils";
17
+ import {
18
+ Button,
19
+ DropdownMenu,
20
+ DropdownMenuContent,
21
+ DropdownMenuItem,
22
+ DropdownMenuTrigger,
23
+ Input,
24
+ Select,
25
+ SelectContent,
26
+ SelectItem,
27
+ SelectTrigger,
28
+ SelectValue,
29
+ } from "@elizaos/ui";
30
+ import { useCallback, useEffect, useState } from "react";
31
+ import {
32
+ formatTrajectoryDuration,
33
+ formatTrajectoryTimestamp,
34
+ formatTrajectoryTokenCount,
35
+ } from "./trajectory-format";
36
+
37
+ type StatusFilter = "" | "active" | "completed" | "error";
38
+
39
+ const STATUS_COLORS: Record<string, { bg: string; fg: string }> = {
40
+ active: { bg: "rgba(59, 130, 246, 0.15)", fg: "rgb(59, 130, 246)" },
41
+ completed: { bg: "rgba(34, 197, 94, 0.15)", fg: "rgb(34, 197, 94)" },
42
+ error: { bg: "rgba(239, 68, 68, 0.15)", fg: "rgb(239, 68, 68)" },
43
+ };
44
+
45
+ const SOURCE_COLORS: Record<string, { bg: string; fg: string }> = {
46
+ chat: { bg: "rgba(99, 102, 241, 0.15)", fg: "rgb(99, 102, 241)" },
47
+ autonomy: { bg: "rgba(245, 158, 11, 0.15)", fg: "rgb(245, 158, 11)" },
48
+ telegram: { bg: "rgba(34, 197, 94, 0.15)", fg: "rgb(34, 197, 94)" },
49
+ discord: { bg: "rgba(88, 101, 242, 0.15)", fg: "rgb(88, 101, 242)" },
50
+ api: { bg: "rgba(156, 163, 175, 0.15)", fg: "rgb(156, 163, 175)" },
51
+ orchestrator: { bg: "rgba(168, 85, 247, 0.15)", fg: "rgb(168, 85, 247)" },
52
+ };
53
+
54
+ interface TrajectoriesViewProps {
55
+ onSelectTrajectory?: (id: string) => void;
56
+ }
57
+
58
+ export function TrajectoriesView({
59
+ onSelectTrajectory,
60
+ }: TrajectoriesViewProps) {
61
+ const { t } = useApp();
62
+ const [loading, setLoading] = useState(true);
63
+ const [result, setResult] = useState<TrajectoryListResult | null>(null);
64
+ const [stats, setStats] = useState<TrajectoryStats | null>(null);
65
+ const [config, setConfig] = useState<TrajectoryConfig | null>(null);
66
+ const [error, setError] = useState<string | null>(null);
67
+
68
+ // Filters
69
+ const [statusFilter, setStatusFilter] = useState<StatusFilter>("");
70
+ const [sourceFilter, setSourceFilter] = useState("");
71
+ const [searchQuery, setSearchQuery] = useState("");
72
+ const [page, setPage] = useState(0);
73
+ const pageSize = 50;
74
+
75
+ // Actions
76
+ const [exporting, setExporting] = useState(false);
77
+ const [clearing, setClearing] = useState(false);
78
+ const [updatingLogging, setUpdatingLogging] = useState(false);
79
+
80
+ const loadTrajectories = useCallback(async () => {
81
+ setLoading(true);
82
+ setError(null);
83
+ try {
84
+ const [trajResult, statsResult, configResult] = await Promise.all([
85
+ client.getTrajectories({
86
+ limit: pageSize,
87
+ offset: page * pageSize,
88
+ status: statusFilter || undefined,
89
+ source: sourceFilter || undefined,
90
+ search: searchQuery || undefined,
91
+ }),
92
+ client.getTrajectoryStats(),
93
+ client.getTrajectoryConfig(),
94
+ ]);
95
+ setResult(trajResult);
96
+ setStats(statsResult);
97
+ setConfig(configResult);
98
+ } catch (err) {
99
+ setError(
100
+ err instanceof Error ? err.message : t("trajectoriesview.FailedToLoad"),
101
+ );
102
+ } finally {
103
+ setLoading(false);
104
+ }
105
+ }, [page, statusFilter, sourceFilter, searchQuery, t]);
106
+
107
+ useEffect(() => {
108
+ void loadTrajectories();
109
+ }, [loadTrajectories]);
110
+
111
+ const handleToggleLogging = async () => {
112
+ if (!config) return;
113
+ setUpdatingLogging(true);
114
+ try {
115
+ const updated = await client.updateTrajectoryConfig({
116
+ enabled: !config.enabled,
117
+ });
118
+ setConfig(updated);
119
+ } catch (err) {
120
+ setError(
121
+ err instanceof Error
122
+ ? err.message
123
+ : t("trajectoriesview.FailedToUpdateConfig"),
124
+ );
125
+ } finally {
126
+ setUpdatingLogging(false);
127
+ }
128
+ };
129
+
130
+ const handleExport = async (
131
+ format: "json" | "csv" | "zip",
132
+ includePrompts: boolean,
133
+ ) => {
134
+ setExporting(true);
135
+ try {
136
+ const blob = await client.exportTrajectories({ format, includePrompts });
137
+ const url = URL.createObjectURL(blob);
138
+ const a = document.createElement("a");
139
+ a.href = url;
140
+ a.download = `trajectories-${new Date().toISOString().split("T")[0]}.${format}`;
141
+ a.click();
142
+ URL.revokeObjectURL(url);
143
+ } catch (err) {
144
+ setError(
145
+ err instanceof Error
146
+ ? err.message
147
+ : t("trajectoriesview.FailedToExport"),
148
+ );
149
+ } finally {
150
+ setExporting(false);
151
+ }
152
+ };
153
+
154
+ const handleClearAll = async () => {
155
+ const confirmed = await confirmDesktopAction({
156
+ title: t("trajectoriesview.DeleteAllTitle"),
157
+ message: t("trajectoriesview.DeleteAllMessage"),
158
+ detail: t("trajectoriesview.DeleteAllDetail"),
159
+ confirmLabel: t("common.deleteAll"),
160
+ cancelLabel: t("common.cancel"),
161
+ type: "warning",
162
+ });
163
+ if (!confirmed) {
164
+ return;
165
+ }
166
+ setClearing(true);
167
+ try {
168
+ await client.clearAllTrajectories();
169
+ void loadTrajectories();
170
+ } catch (err) {
171
+ setError(
172
+ err instanceof Error
173
+ ? err.message
174
+ : t("trajectoriesview.FailedToClear"),
175
+ );
176
+ } finally {
177
+ setClearing(false);
178
+ }
179
+ };
180
+
181
+ const handleClearFilters = () => {
182
+ setStatusFilter("");
183
+ setSourceFilter("");
184
+ setSearchQuery("");
185
+ setPage(0);
186
+ };
187
+
188
+ const hasActiveFilters =
189
+ statusFilter !== "" || sourceFilter !== "" || searchQuery !== "";
190
+ const trajectories = result?.trajectories ?? [];
191
+ const total = result?.total ?? 0;
192
+ const totalPages = Math.ceil(total / pageSize);
193
+
194
+ const sources = stats?.bySource ? Object.keys(stats.bySource) : [];
195
+
196
+ return (
197
+ <div className="flex flex-col h-full gap-3">
198
+ {/* Stats summary */}
199
+ {stats && (
200
+ <div className="flex flex-wrap gap-4 text-xs">
201
+ <div className="flex items-center gap-1.5">
202
+ <span className="text-muted">{t("trajectoriesview.Total")}</span>
203
+ <span className="font-semibold">
204
+ {stats.totalTrajectories.toLocaleString()}
205
+ </span>
206
+ </div>
207
+ <div className="flex items-center gap-1.5">
208
+ <span className="text-muted">{t("trajectoriesview.LLMCalls")}</span>
209
+ <span className="font-semibold">
210
+ {stats.totalLlmCalls.toLocaleString()}
211
+ </span>
212
+ </div>
213
+ <div className="flex items-center gap-1.5">
214
+ <span className="text-muted">{t("trajectoriesview.Tokens")}</span>
215
+ <span className="font-semibold text-txt">
216
+ {formatTrajectoryTokenCount(
217
+ stats.totalPromptTokens + stats.totalCompletionTokens,
218
+ { emptyLabel: "0" },
219
+ )}
220
+ </span>
221
+ <span className="text-muted text-[10px]">
222
+ (
223
+ {formatTrajectoryTokenCount(stats.totalPromptTokens, {
224
+ emptyLabel: "0",
225
+ })}
226
+ ↑{" "}
227
+ {formatTrajectoryTokenCount(stats.totalCompletionTokens, {
228
+ emptyLabel: "0",
229
+ })}
230
+ ↓)
231
+ </span>
232
+ </div>
233
+ <div className="flex items-center gap-1.5">
234
+ <span className="text-muted">
235
+ {t("trajectoriesview.AvgDuration")}
236
+ </span>
237
+ <span className="font-semibold">
238
+ {formatTrajectoryDuration(stats.averageDurationMs)}
239
+ </span>
240
+ </div>
241
+ <div className="ml-auto flex items-center gap-2">
242
+ <div className="flex items-center gap-1.5">
243
+ <span className="text-muted">
244
+ {t("trajectoriesview.Logging")}
245
+ </span>
246
+ <Button
247
+ variant="outline"
248
+ size="sm"
249
+ className={`h-6 px-2 py-0.5 text-[11px] shadow-sm ${
250
+ config?.enabled
251
+ ? "bg-success/20 border-success text-success hover:bg-success/30"
252
+ : "bg-warn/20 border-warn text-warn hover:bg-warn/30"
253
+ }`}
254
+ onClick={handleToggleLogging}
255
+ disabled={!config || updatingLogging}
256
+ aria-pressed={config?.enabled ?? false}
257
+ >
258
+ {config?.enabled ? t("common.on") : t("common.off")}
259
+ </Button>
260
+ </div>
261
+ </div>
262
+ </div>
263
+ )}
264
+
265
+ {/* Filters row */}
266
+ <div className="flex flex-wrap gap-1.5 items-center">
267
+ <Input
268
+ type="text"
269
+ placeholder={t("trajectoriesview.Search")}
270
+ className="h-8 px-3 py-1.5 text-xs bg-card border-border w-48 shadow-sm"
271
+ value={searchQuery}
272
+ onChange={(e) => {
273
+ setSearchQuery(e.target.value);
274
+ setPage(0);
275
+ }}
276
+ />
277
+
278
+ <Select
279
+ value={statusFilter === "" ? "all" : statusFilter}
280
+ onValueChange={(val) => {
281
+ setStatusFilter(val === "all" ? "" : (val as StatusFilter));
282
+ setPage(0);
283
+ }}
284
+ >
285
+ <SelectTrigger className="h-8 px-3 py-1.5 text-xs bg-card border-border shadow-sm w-36">
286
+ <SelectValue placeholder={t("trajectoriesview.AllStatuses")} />
287
+ </SelectTrigger>
288
+ <SelectContent>
289
+ <SelectItem value="all">
290
+ {t("trajectoriesview.AllStatuses")}
291
+ </SelectItem>
292
+ <SelectItem value="active">{t("appsview.Active")}</SelectItem>
293
+ <SelectItem value="completed">
294
+ {t("trajectoriesview.Completed")}
295
+ </SelectItem>
296
+ <SelectItem value="error">{t("logsview.Error")}</SelectItem>
297
+ </SelectContent>
298
+ </Select>
299
+
300
+ {sources.length > 0 && (
301
+ <Select
302
+ value={sourceFilter === "" ? "all" : sourceFilter}
303
+ onValueChange={(val) => {
304
+ setSourceFilter(val === "all" ? "" : val);
305
+ setPage(0);
306
+ }}
307
+ >
308
+ <SelectTrigger className="h-8 px-3 py-1.5 text-xs bg-card border-border shadow-sm w-36">
309
+ <SelectValue placeholder={t("logsview.AllSources")} />
310
+ </SelectTrigger>
311
+ <SelectContent>
312
+ <SelectItem value="all">{t("logsview.AllSources")}</SelectItem>
313
+ {sources.map((s) => (
314
+ <SelectItem key={s} value={s}>
315
+ {s}
316
+ </SelectItem>
317
+ ))}
318
+ </SelectContent>
319
+ </Select>
320
+ )}
321
+
322
+ {hasActiveFilters && (
323
+ <Button
324
+ variant="outline"
325
+ size="sm"
326
+ className="h-auto min-h-[2rem] whitespace-normal break-words px-3 py-1.5 text-xs bg-card text-txt hover:text-txt shadow-sm text-left"
327
+ onClick={handleClearFilters}
328
+ >
329
+ {t("logsview.ClearFilters")}
330
+ </Button>
331
+ )}
332
+
333
+ <div className="ml-auto flex gap-1.5">
334
+ <Button
335
+ variant="outline"
336
+ size="sm"
337
+ className="h-auto min-h-[2rem] whitespace-normal break-words px-3 py-1.5 text-xs bg-card text-txt hover:text-txt shadow-sm text-left"
338
+ onClick={() => void loadTrajectories()}
339
+ disabled={loading}
340
+ >
341
+ {loading
342
+ ? t("common.loading", { defaultValue: "Loading..." })
343
+ : t("common.refresh", { defaultValue: "Refresh" })}
344
+ </Button>
345
+
346
+ <DropdownMenu>
347
+ <DropdownMenuTrigger asChild>
348
+ <Button
349
+ variant="outline"
350
+ size="sm"
351
+ className="h-auto min-h-[2rem] whitespace-normal break-words px-3 py-1.5 text-xs bg-card text-txt hover:text-txt shadow-sm text-left"
352
+ disabled={exporting || trajectories.length === 0}
353
+ >
354
+ {exporting ? t("common.exporting") : t("common.export")}
355
+ </Button>
356
+ </DropdownMenuTrigger>
357
+ <DropdownMenuContent align="end" className="w-48">
358
+ <DropdownMenuItem onClick={() => handleExport("json", true)}>
359
+ {t("trajectoriesview.JSONWithPrompts")}
360
+ </DropdownMenuItem>
361
+ <DropdownMenuItem onClick={() => handleExport("json", false)}>
362
+ {t("trajectoriesview.JSONRedacted")}
363
+ </DropdownMenuItem>
364
+ <DropdownMenuItem onClick={() => handleExport("csv", false)}>
365
+ {t("trajectoriesview.CSVSummaryOnly")}
366
+ </DropdownMenuItem>
367
+ <DropdownMenuItem onClick={() => handleExport("zip", true)}>
368
+ {t("trajectoriesview.ZIPFolders")}
369
+ </DropdownMenuItem>
370
+ </DropdownMenuContent>
371
+ </DropdownMenu>
372
+
373
+ <Button
374
+ variant="outline"
375
+ size="sm"
376
+ className="h-auto min-h-[2rem] whitespace-normal break-words px-3 py-1.5 text-xs bg-card text-danger border-danger/50 hover:bg-danger/10 hover:border-danger shadow-sm text-left"
377
+ onClick={handleClearAll}
378
+ disabled={clearing || stats?.totalTrajectories === 0}
379
+ >
380
+ {clearing ? t("common.clearing") : t("common.clearAll")}
381
+ </Button>
382
+ </div>
383
+ </div>
384
+
385
+ {/* Error display */}
386
+ {error && (
387
+ <div className="text-xs text-danger border border-danger/30 bg-danger/10 px-3 py-2">
388
+ {error}
389
+ </div>
390
+ )}
391
+
392
+ {/* Trajectories list */}
393
+ <div className="flex-1 min-h-0 overflow-y-auto border border-border bg-card">
394
+ {loading && trajectories.length === 0 ? (
395
+ <div className="text-center py-8 text-muted">
396
+ {t("trajectoriesview.LoadingTrajectories")}
397
+ </div>
398
+ ) : trajectories.length === 0 ? (
399
+ <div className="text-center py-8 text-muted">
400
+ {hasActiveFilters
401
+ ? t("trajectoriesview.NoTrajectoriesMatchingFilters")
402
+ : t("trajectoriesview.NoTrajectoriesYet")}
403
+ {!config?.enabled && (
404
+ <div className="mt-2 text-warn text-[11px]">
405
+ {t("trajectoriesview.LoggingHelp")}
406
+ </div>
407
+ )}
408
+ </div>
409
+ ) : (
410
+ <table className="w-full text-xs">
411
+ <thead className="bg-muted/10 sticky top-0">
412
+ <tr>
413
+ <th className="text-left px-2 py-1.5 font-medium">
414
+ {t("trajectoriesview.Time")}
415
+ </th>
416
+ <th className="text-left px-2 py-1.5 font-medium">
417
+ {t("trajectoriesview.Source")}
418
+ </th>
419
+ <th className="text-left px-2 py-1.5 font-medium">
420
+ {t("finetuningview.Status")}
421
+ </th>
422
+ <th className="text-right px-2 py-1.5 font-medium">
423
+ {t("trajectoriesview.Calls")}
424
+ </th>
425
+ <th className="text-right px-2 py-1.5 font-medium">
426
+ {t("trajectoriesview.Tokens1")}
427
+ </th>
428
+ <th className="text-right px-2 py-1.5 font-medium">
429
+ {t("trajectoriesview.Duration")}
430
+ </th>
431
+ </tr>
432
+ </thead>
433
+ <tbody>
434
+ {trajectories.map((traj: TrajectoryRecord) => {
435
+ const statusColor =
436
+ STATUS_COLORS[traj.status] ?? STATUS_COLORS.completed;
437
+ const sourceColor =
438
+ SOURCE_COLORS[traj.source] ?? SOURCE_COLORS.api;
439
+ return (
440
+ <tr
441
+ key={traj.id}
442
+ className="border-t border-border hover:bg-muted/5 cursor-pointer"
443
+ onClick={() => onSelectTrajectory?.(traj.id)}
444
+ >
445
+ <td className="px-2 py-1.5 text-muted whitespace-nowrap">
446
+ {formatTrajectoryTimestamp(traj.createdAt, "smart")}
447
+ </td>
448
+ <td className="px-2 py-1.5">
449
+ <span
450
+ className="inline-block text-[10px] px-1.5 py-px rounded"
451
+ style={{
452
+ background: sourceColor.bg,
453
+ color: sourceColor.fg,
454
+ }}
455
+ >
456
+ {traj.source}
457
+ </span>
458
+ </td>
459
+ <td className="px-2 py-1.5">
460
+ <span
461
+ className="inline-block text-[10px] px-1.5 py-px rounded"
462
+ style={{
463
+ background: statusColor.bg,
464
+ color: statusColor.fg,
465
+ }}
466
+ >
467
+ {traj.status}
468
+ </span>
469
+ </td>
470
+ <td className="px-2 py-1.5 text-right font-mono">
471
+ {traj.llmCallCount}
472
+ </td>
473
+ <td className="px-2 py-1.5 text-right font-mono">
474
+ <span className="text-txt">
475
+ {formatTrajectoryTokenCount(
476
+ traj.totalPromptTokens + traj.totalCompletionTokens,
477
+ { emptyLabel: "0" },
478
+ )}
479
+ </span>
480
+ </td>
481
+ <td className="px-2 py-1.5 text-right text-muted font-mono">
482
+ {formatTrajectoryDuration(traj.durationMs)}
483
+ </td>
484
+ </tr>
485
+ );
486
+ })}
487
+ </tbody>
488
+ </table>
489
+ )}
490
+ </div>
491
+
492
+ {/* Pagination */}
493
+ {totalPages > 1 && (
494
+ <div className="flex items-center justify-between text-xs">
495
+ <span className="text-muted">
496
+ {t("trajectoriesview.ShowingRange", {
497
+ start: page * pageSize + 1,
498
+ end: Math.min((page + 1) * pageSize, total),
499
+ total,
500
+ })}
501
+ </span>
502
+ <div className="flex gap-1">
503
+ <Button
504
+ variant="outline"
505
+ size="sm"
506
+ className="h-auto min-h-[1.75rem] px-2 py-1 text-xs bg-card disabled:opacity-50 shadow-sm"
507
+ onClick={() => setPage((p) => Math.max(0, p - 1))}
508
+ disabled={page === 0}
509
+ >
510
+ {t("databaseview.Prev")}
511
+ </Button>
512
+ <Button
513
+ variant="outline"
514
+ size="sm"
515
+ className="h-auto min-h-[1.75rem] px-2 py-1 text-xs bg-card disabled:opacity-50 shadow-sm"
516
+ onClick={() => setPage((p) => p + 1)}
517
+ disabled={page >= totalPages - 1}
518
+ >
519
+ {t("databaseview.Next")}
520
+ </Button>
521
+ </div>
522
+ </div>
523
+ )}
524
+ </div>
525
+ );
526
+ }