@elizaos/ui 2.0.0-alpha.192 → 2.0.0-alpha.196

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 (273) hide show
  1. package/apps/app-companion/src/components/companion/companion-app.js +2 -2
  2. package/apps/app-lifeops/src/action.d.ts.map +1 -1
  3. package/apps/app-lifeops/src/action.js +21 -2
  4. package/apps/app-lifeops/src/actions/app-blocker.d.ts +3 -1
  5. package/apps/app-lifeops/src/actions/app-blocker.d.ts.map +1 -1
  6. package/apps/app-lifeops/src/actions/app-blocker.js +2 -0
  7. package/apps/app-lifeops/src/actions/calendar.d.ts.map +1 -1
  8. package/apps/app-lifeops/src/actions/calendar.js +140 -7
  9. package/apps/app-lifeops/src/actions/computer-use.d.ts.map +1 -1
  10. package/apps/app-lifeops/src/actions/computer-use.js +248 -17
  11. package/apps/app-lifeops/src/actions/cross-channel-send.d.ts +3 -1
  12. package/apps/app-lifeops/src/actions/cross-channel-send.d.ts.map +1 -1
  13. package/apps/app-lifeops/src/actions/cross-channel-send.js +1 -0
  14. package/apps/app-lifeops/src/actions/dossier.d.ts.map +1 -1
  15. package/apps/app-lifeops/src/actions/dossier.js +51 -34
  16. package/apps/app-lifeops/src/actions/gmail.d.ts.map +1 -1
  17. package/apps/app-lifeops/src/actions/gmail.js +5 -0
  18. package/apps/app-lifeops/src/actions/inbox.d.ts.map +1 -1
  19. package/apps/app-lifeops/src/actions/inbox.js +8 -1
  20. package/apps/app-lifeops/src/actions/life.d.ts.map +1 -1
  21. package/apps/app-lifeops/src/actions/life.extractor.d.ts.map +1 -1
  22. package/apps/app-lifeops/src/actions/life.extractor.js +103 -4
  23. package/apps/app-lifeops/src/actions/life.js +8 -0
  24. package/apps/app-lifeops/src/actions/non-actionable-request.d.ts +6 -0
  25. package/apps/app-lifeops/src/actions/non-actionable-request.d.ts.map +1 -0
  26. package/apps/app-lifeops/src/actions/non-actionable-request.js +33 -0
  27. package/apps/app-lifeops/src/actions/password-manager.d.ts.map +1 -1
  28. package/apps/app-lifeops/src/actions/password-manager.js +11 -2
  29. package/apps/app-lifeops/src/actions/relationships.d.ts +3 -1
  30. package/apps/app-lifeops/src/actions/relationships.d.ts.map +1 -1
  31. package/apps/app-lifeops/src/actions/relationships.js +167 -11
  32. package/apps/app-lifeops/src/actions/remote-desktop.js +1 -1
  33. package/apps/app-lifeops/src/actions/screen-time.d.ts.map +1 -1
  34. package/apps/app-lifeops/src/actions/screen-time.js +8 -2
  35. package/apps/app-lifeops/src/actions/start-remote-session.js +1 -1
  36. package/apps/app-lifeops/src/actions/subscriptions.d.ts +5 -0
  37. package/apps/app-lifeops/src/actions/subscriptions.d.ts.map +1 -0
  38. package/apps/app-lifeops/src/actions/subscriptions.js +251 -0
  39. package/apps/app-lifeops/src/actions/twilio-call.d.ts.map +1 -1
  40. package/apps/app-lifeops/src/actions/twilio-call.js +48 -3
  41. package/apps/app-lifeops/src/actions/update-owner-profile.d.ts.map +1 -1
  42. package/apps/app-lifeops/src/actions/update-owner-profile.js +2 -1
  43. package/apps/app-lifeops/src/actions/website-blocker.d.ts +6 -2
  44. package/apps/app-lifeops/src/actions/website-blocker.d.ts.map +1 -1
  45. package/apps/app-lifeops/src/actions/website-blocker.js +38 -0
  46. package/apps/app-lifeops/src/actions/x-read.d.ts.map +1 -1
  47. package/apps/app-lifeops/src/actions/x-read.js +27 -3
  48. package/apps/app-lifeops/src/dossier/action.d.ts.map +1 -1
  49. package/apps/app-lifeops/src/dossier/action.js +8 -7
  50. package/apps/app-lifeops/src/followup/actions/listOverdueFollowups.d.ts.map +1 -1
  51. package/apps/app-lifeops/src/followup/actions/listOverdueFollowups.js +1 -0
  52. package/apps/app-lifeops/src/followup/actions/markFollowupDone.d.ts.map +1 -1
  53. package/apps/app-lifeops/src/followup/actions/markFollowupDone.js +2 -1
  54. package/apps/app-lifeops/src/followup/actions/setFollowupThreshold.d.ts.map +1 -1
  55. package/apps/app-lifeops/src/followup/actions/setFollowupThreshold.js +2 -1
  56. package/apps/app-lifeops/src/lifeops/calendly-client.d.ts.map +1 -1
  57. package/apps/app-lifeops/src/lifeops/calendly-client.js +4 -2
  58. package/apps/app-lifeops/src/lifeops/discord-browser-scraper.d.ts.map +1 -1
  59. package/apps/app-lifeops/src/lifeops/discord-browser-scraper.js +6 -5
  60. package/apps/app-lifeops/src/lifeops/password-manager-bridge.d.ts +1 -1
  61. package/apps/app-lifeops/src/lifeops/password-manager-bridge.d.ts.map +1 -1
  62. package/apps/app-lifeops/src/lifeops/password-manager-bridge.js +84 -6
  63. package/apps/app-lifeops/src/lifeops/remote-desktop.d.ts.map +1 -1
  64. package/apps/app-lifeops/src/lifeops/remote-desktop.js +30 -0
  65. package/apps/app-lifeops/src/lifeops/repository.d.ts +15 -0
  66. package/apps/app-lifeops/src/lifeops/repository.d.ts.map +1 -1
  67. package/apps/app-lifeops/src/lifeops/repository.js +330 -1
  68. package/apps/app-lifeops/src/lifeops/service-mixin-discord.js +222 -42
  69. package/apps/app-lifeops/src/lifeops/service-mixin-subscriptions.d.ts +2 -0
  70. package/apps/app-lifeops/src/lifeops/service-mixin-subscriptions.d.ts.map +1 -0
  71. package/apps/app-lifeops/src/lifeops/service-mixin-subscriptions.js +763 -0
  72. package/apps/app-lifeops/src/lifeops/service.d.ts +64 -1
  73. package/apps/app-lifeops/src/lifeops/service.d.ts.map +1 -1
  74. package/apps/app-lifeops/src/lifeops/service.js +2 -1
  75. package/apps/app-lifeops/src/lifeops/subscriptions-playbooks.d.ts +54 -0
  76. package/apps/app-lifeops/src/lifeops/subscriptions-playbooks.d.ts.map +1 -0
  77. package/apps/app-lifeops/src/lifeops/subscriptions-playbooks.js +213 -0
  78. package/apps/app-lifeops/src/lifeops/subscriptions-types.d.ts +80 -0
  79. package/apps/app-lifeops/src/lifeops/subscriptions-types.d.ts.map +1 -0
  80. package/apps/app-lifeops/src/lifeops/twilio.d.ts.map +1 -1
  81. package/apps/app-lifeops/src/lifeops/twilio.js +4 -2
  82. package/apps/app-lifeops/src/lifeops/whatsapp-client.d.ts.map +1 -1
  83. package/apps/app-lifeops/src/lifeops/whatsapp-client.js +4 -2
  84. package/apps/app-lifeops/src/lifeops/x-poster.d.ts.map +1 -1
  85. package/apps/app-lifeops/src/lifeops/x-poster.js +12 -6
  86. package/apps/app-lifeops/src/lifeops/x-reader.d.ts.map +1 -1
  87. package/apps/app-lifeops/src/lifeops/x-reader.js +7 -5
  88. package/apps/app-lifeops/src/plugin.d.ts.map +1 -1
  89. package/apps/app-lifeops/src/plugin.js +31 -24
  90. package/apps/app-lifeops/src/provider.d.ts.map +1 -1
  91. package/apps/app-lifeops/src/provider.js +4 -4
  92. package/apps/app-lifeops/src/providers/inbox-triage.js +2 -2
  93. package/apps/app-lifeops/src/providers/lifeops.d.ts.map +1 -1
  94. package/apps/app-lifeops/src/providers/lifeops.js +19 -10
  95. package/apps/app-lifeops/src/providers/website-blocker.js +1 -1
  96. package/apps/app-lifeops/src/public.d.ts +4 -0
  97. package/apps/app-lifeops/src/public.d.ts.map +1 -0
  98. package/apps/app-lifeops/src/public.js +3 -0
  99. package/apps/app-lifeops/src/routes/plugin.d.ts +18 -0
  100. package/apps/app-lifeops/src/routes/plugin.d.ts.map +1 -0
  101. package/apps/app-lifeops/src/routes/plugin.js +270 -0
  102. package/apps/app-lifeops/src/website-blocker/chat-integration/actions/blockUntilTaskComplete.d.ts.map +1 -1
  103. package/apps/app-lifeops/src/website-blocker/chat-integration/actions/blockUntilTaskComplete.js +18 -2
  104. package/package.json +1 -1
  105. package/packages/agent/src/actions/browser-session.d.ts +3 -0
  106. package/packages/agent/src/actions/browser-session.d.ts.map +1 -0
  107. package/packages/agent/src/actions/browser-session.js +207 -0
  108. package/packages/agent/src/actions/context-signal.d.ts +1 -1
  109. package/packages/agent/src/actions/context-signal.d.ts.map +1 -1
  110. package/packages/agent/src/actions/context-signal.js +1 -1
  111. package/packages/agent/src/actions/extract-page.d.ts +3 -0
  112. package/packages/agent/src/actions/extract-page.d.ts.map +1 -0
  113. package/packages/agent/src/actions/extract-page.js +124 -0
  114. package/packages/agent/src/actions/grounded-action-reply.js +1 -1
  115. package/packages/agent/src/actions/recent-conversation-texts.d.ts +9 -0
  116. package/packages/agent/src/actions/recent-conversation-texts.d.ts.map +1 -0
  117. package/packages/agent/src/actions/recent-conversation-texts.js +81 -0
  118. package/packages/agent/src/actions/set-user-name.js +1 -1
  119. package/packages/agent/src/actions/web-search.d.ts +4 -10
  120. package/packages/agent/src/actions/web-search.d.ts.map +1 -1
  121. package/packages/agent/src/actions/web-search.js +89 -19
  122. package/packages/agent/src/api/binance-skill-helpers.d.ts.map +1 -1
  123. package/packages/agent/src/api/binance-skill-helpers.js +1 -1
  124. package/packages/agent/src/api/chat-augmentation.d.ts +1 -20
  125. package/packages/agent/src/api/chat-augmentation.d.ts.map +1 -1
  126. package/packages/agent/src/api/chat-augmentation.js +2 -110
  127. package/packages/agent/src/api/conversation-metadata.d.ts +9 -0
  128. package/packages/agent/src/api/conversation-metadata.d.ts.map +1 -0
  129. package/packages/agent/src/api/conversation-metadata.js +98 -0
  130. package/packages/agent/src/api/conversation-routes.d.ts.map +1 -1
  131. package/packages/agent/src/api/conversation-routes.js +40 -9
  132. package/packages/agent/src/api/permissions-routes.js +1 -1
  133. package/packages/agent/src/api/server-helpers-auth.js +1 -1
  134. package/packages/agent/src/api/server-helpers.d.ts +2 -1
  135. package/packages/agent/src/api/server-helpers.d.ts.map +1 -1
  136. package/packages/agent/src/api/server-helpers.js +2 -110
  137. package/packages/agent/src/api/server-types.d.ts +14 -0
  138. package/packages/agent/src/api/server-types.d.ts.map +1 -1
  139. package/packages/agent/src/api/server.d.ts.map +1 -1
  140. package/packages/agent/src/api/server.js +3 -13
  141. package/packages/agent/src/api/workbench-routes.d.ts.map +1 -1
  142. package/packages/agent/src/api/workbench-routes.js +0 -14
  143. package/packages/agent/src/auth/credentials.d.ts.map +1 -1
  144. package/packages/agent/src/auth/credentials.js +8 -0
  145. package/packages/agent/src/providers/automation-terminal-bridge.d.ts +3 -0
  146. package/packages/agent/src/providers/automation-terminal-bridge.d.ts.map +1 -0
  147. package/packages/agent/src/providers/automation-terminal-bridge.js +71 -0
  148. package/packages/agent/src/providers/recent-conversations.d.ts.map +1 -1
  149. package/packages/agent/src/providers/recent-conversations.js +5 -0
  150. package/packages/agent/src/providers/relevant-conversations.d.ts.map +1 -1
  151. package/packages/agent/src/providers/relevant-conversations.js +5 -0
  152. package/packages/agent/src/providers/user-name.d.ts.map +1 -1
  153. package/packages/agent/src/providers/user-name.js +1 -1
  154. package/packages/agent/src/runtime/eliza-plugin.d.ts.map +1 -1
  155. package/packages/agent/src/runtime/eliza-plugin.js +6 -0
  156. package/packages/agent/src/runtime/plugin-resolver.d.ts.map +1 -1
  157. package/packages/agent/src/runtime/plugin-resolver.js +115 -0
  158. package/packages/agent/src/services/browser-workspace-types.d.ts +5 -1
  159. package/packages/agent/src/services/browser-workspace-types.d.ts.map +1 -1
  160. package/packages/agent/src/services/browser-workspace.d.ts.map +1 -1
  161. package/packages/agent/src/services/browser-workspace.js +178 -7
  162. package/packages/agent/src/services/escalation.d.ts +0 -11
  163. package/packages/agent/src/services/escalation.d.ts.map +1 -1
  164. package/packages/agent/src/services/escalation.js +19 -58
  165. package/packages/agent/src/services/hosted-tools.d.ts +70 -0
  166. package/packages/agent/src/services/hosted-tools.d.ts.map +1 -0
  167. package/packages/agent/src/services/hosted-tools.js +87 -0
  168. package/packages/agent/src/services/owner-name.d.ts +4 -0
  169. package/packages/agent/src/services/owner-name.d.ts.map +1 -0
  170. package/packages/agent/src/services/owner-name.js +46 -0
  171. package/packages/agent/src/services/registry-client-queries.d.ts +1 -1
  172. package/packages/agent/src/services/registry-client-queries.js +1 -1
  173. package/packages/agent/src/services/registry-client-types.d.ts +1 -1
  174. package/packages/app-core/src/api/client-automations.d.ts +8 -0
  175. package/packages/app-core/src/api/client-automations.d.ts.map +1 -0
  176. package/packages/app-core/src/api/client-automations.js +7 -0
  177. package/packages/app-core/src/api/client-browser-workspace.d.ts +3 -0
  178. package/packages/app-core/src/api/client-browser-workspace.d.ts.map +1 -1
  179. package/packages/app-core/src/api/client-browser-workspace.js +3 -0
  180. package/packages/app-core/src/api/client-chat.d.ts +8 -2
  181. package/packages/app-core/src/api/client-chat.d.ts.map +1 -1
  182. package/packages/app-core/src/api/client-chat.js +14 -1
  183. package/packages/app-core/src/api/client-types-chat.d.ts +4 -0
  184. package/packages/app-core/src/api/client-types-chat.d.ts.map +1 -1
  185. package/packages/app-core/src/api/client-types-config.d.ts +67 -2
  186. package/packages/app-core/src/api/client-types-config.d.ts.map +1 -1
  187. package/packages/app-core/src/api/client.d.ts +1 -0
  188. package/packages/app-core/src/api/client.d.ts.map +1 -1
  189. package/packages/app-core/src/api/client.js +1 -0
  190. package/packages/app-core/src/components/apps/helpers.d.ts +1 -1
  191. package/packages/app-core/src/components/apps/helpers.d.ts.map +1 -1
  192. package/packages/app-core/src/components/apps/helpers.js +5 -9
  193. package/packages/app-core/src/components/apps/internal-tool-apps.d.ts.map +1 -1
  194. package/packages/app-core/src/components/apps/internal-tool-apps.js +10 -1
  195. package/packages/app-core/src/components/conversations/conversation-sidebar-model.d.ts.map +1 -1
  196. package/packages/app-core/src/components/conversations/conversation-sidebar-model.js +6 -0
  197. package/packages/app-core/src/components/pages/AutomationRoomChatPane.d.ts +16 -0
  198. package/packages/app-core/src/components/pages/AutomationRoomChatPane.d.ts.map +1 -0
  199. package/packages/app-core/src/components/pages/AutomationRoomChatPane.js +202 -0
  200. package/packages/app-core/src/components/pages/AutomationsView.d.ts +1 -4
  201. package/packages/app-core/src/components/pages/AutomationsView.d.ts.map +1 -1
  202. package/packages/app-core/src/components/pages/AutomationsView.js +565 -265
  203. package/packages/app-core/src/components/pages/BrowserWorkspaceView.d.ts.map +1 -1
  204. package/packages/app-core/src/components/pages/BrowserWorkspaceView.js +118 -24
  205. package/packages/app-core/src/components/pages/automation-conversations.d.ts +15 -0
  206. package/packages/app-core/src/components/pages/automation-conversations.d.ts.map +1 -0
  207. package/packages/app-core/src/components/pages/automation-conversations.js +190 -0
  208. package/packages/app-core/src/components/pages/plugin-list-utils.js +4 -4
  209. package/packages/app-core/src/config/boot-config-store.d.ts +1 -1
  210. package/packages/app-core/src/config/boot-config-store.d.ts.map +1 -1
  211. package/packages/app-core/src/state/useOnboardingCallbacks.d.ts +14 -0
  212. package/packages/app-core/src/state/useOnboardingCallbacks.d.ts.map +1 -1
  213. package/packages/app-core/src/state/useOnboardingCallbacks.js +1 -1
  214. package/packages/app-core/src/state/usePluginsSkillsState.d.ts +2 -2
  215. package/packages/app-core/src/utils/desktop-workspace.d.ts +2 -2
  216. package/packages/app-core/src/utils/desktop-workspace.d.ts.map +1 -1
  217. package/packages/app-core/src/utils/desktop-workspace.js +7 -2
  218. package/packages/shared/src/contracts/lifeops.d.ts +26 -0
  219. package/packages/shared/src/contracts/lifeops.d.ts.map +1 -1
  220. package/packages/shared/src/contracts/lifeops.js +28 -0
  221. package/packages/typescript/src/actions.d.ts.map +1 -1
  222. package/packages/typescript/src/actions.js +12 -0
  223. package/packages/typescript/src/features/advanced-capabilities/actions/scheduleFollowUp.d.ts.map +1 -1
  224. package/packages/typescript/src/features/advanced-capabilities/actions/scheduleFollowUp.js +8 -2
  225. package/packages/typescript/src/features/advanced-capabilities/providers/settings.d.ts.map +1 -1
  226. package/packages/typescript/src/features/advanced-capabilities/providers/settings.js +11 -3
  227. package/packages/typescript/src/features/basic-capabilities/providers/actions.d.ts.map +1 -1
  228. package/packages/typescript/src/features/basic-capabilities/providers/actions.js +23 -1
  229. package/packages/typescript/src/features/basic-capabilities/providers/non-actionable-chatter.d.ts +5 -0
  230. package/packages/typescript/src/features/basic-capabilities/providers/non-actionable-chatter.d.ts.map +1 -0
  231. package/packages/typescript/src/features/basic-capabilities/providers/non-actionable-chatter.js +22 -0
  232. package/packages/typescript/src/features/basic-capabilities/providers/providers.d.ts.map +1 -1
  233. package/packages/typescript/src/features/basic-capabilities/providers/providers.js +10 -5
  234. package/packages/typescript/src/features/knowledge/documents-provider.d.ts.map +1 -1
  235. package/packages/typescript/src/features/knowledge/documents-provider.js +1 -0
  236. package/packages/typescript/src/features/shared/schedule-follow-up-response.d.ts +10 -0
  237. package/packages/typescript/src/features/shared/schedule-follow-up-response.d.ts.map +1 -0
  238. package/packages/typescript/src/features/shared/schedule-follow-up-response.js +49 -0
  239. package/packages/typescript/src/features/trajectories/TrajectoriesService.d.ts +1 -0
  240. package/packages/typescript/src/features/trajectories/TrajectoriesService.d.ts.map +1 -1
  241. package/packages/typescript/src/features/trajectories/TrajectoriesService.js +21 -2
  242. package/packages/typescript/src/features/trust/providers/settings.d.ts.map +1 -1
  243. package/packages/typescript/src/features/trust/providers/settings.js +11 -1
  244. package/packages/typescript/src/prompts.d.ts +4 -4
  245. package/packages/typescript/src/prompts.d.ts.map +1 -1
  246. package/packages/typescript/src/prompts.js +10 -2
  247. package/packages/typescript/src/services/message.d.ts +1 -0
  248. package/packages/typescript/src/services/message.d.ts.map +1 -1
  249. package/packages/typescript/src/services/message.js +483 -18
  250. package/packages/typescript/src/types/components.d.ts +6 -0
  251. package/packages/typescript/src/types/components.d.ts.map +1 -1
  252. package/packages/typescript/src/utils/context-routing.d.ts.map +1 -1
  253. package/packages/typescript/src/utils/context-routing.js +5 -1
  254. package/packages/typescript/src/utils/toon.d.ts.map +1 -1
  255. package/packages/typescript/src/utils/toon.js +23 -2
  256. package/packages/typescript/src/utils.d.ts +1 -0
  257. package/packages/typescript/src/utils.d.ts.map +1 -1
  258. package/packages/typescript/src/utils.js +1 -0
  259. package/apps/app-lifeops/src/types/app-blocker-settings-card.d.ts +0 -2
  260. package/apps/app-lifeops/src/types/app-blocker-settings-card.d.ts.map +0 -1
  261. package/apps/app-lifeops/src/types/index.d.ts +0 -3
  262. package/apps/app-lifeops/src/types/index.d.ts.map +0 -1
  263. package/apps/app-lifeops/src/types/index.js +0 -1
  264. package/apps/app-lifeops/src/types/website-blocker-settings-card.d.ts +0 -2
  265. package/apps/app-lifeops/src/types/website-blocker-settings-card.d.ts.map +0 -1
  266. package/apps/app-lifeops/src/types/website-blocker-settings-card.js +0 -1
  267. package/packages/agent/src/contracts/lifeops.d.ts +0 -2
  268. package/packages/agent/src/contracts/lifeops.d.ts.map +0 -1
  269. package/packages/agent/src/contracts/lifeops.js +0 -1
  270. package/packages/app-core/src/components/pages/N8nWorkflowsPanel.d.ts +0 -18
  271. package/packages/app-core/src/components/pages/N8nWorkflowsPanel.d.ts.map +0 -1
  272. package/packages/app-core/src/components/pages/N8nWorkflowsPanel.js +0 -542
  273. /package/apps/app-lifeops/src/{types/app-blocker-settings-card.js → lifeops/subscriptions-types.js} +0 -0
@@ -1,542 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /**
3
- * N8nWorkflowsPanel — n8n integration embedded in AutomationsView.
4
- *
5
- * Renders when filter === "workflows". Contains:
6
- * - N8nStatusBanner (always visible in workflows tab)
7
- * - Sidebar workflow list (replaces the normal item list)
8
- * - Detail pane: workflow detail + scoped chat (option A: vertical split)
9
- *
10
- * This component is self-contained — it owns its own fetch state and does NOT
11
- * use AutomationsViewContext. It is rendered by AutomationsLayout when
12
- * filter === "workflows".
13
- */
14
- import { Button, FieldLabel, StatusBadge, Textarea } from "@elizaos/ui";
15
- import { ChevronDown, ChevronUp, RefreshCw, Send, Square, Workflow, X, Zap, } from "lucide-react";
16
- import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
17
- import { client } from "../../api";
18
- import { useApp } from "../../state";
19
- import { confirmDesktopAction } from "../../utils";
20
- // ---------------------------------------------------------------------------
21
- // System addendum constant
22
- // ---------------------------------------------------------------------------
23
- const AUTOMATIONS_SYSTEM_ADDENDUM = "You are in the Automations assistant. When the user asks to automate, " +
24
- "schedule, trigger, or connect apps, use the n8n workflow actions " +
25
- "(CREATE_N8N_WORKFLOW, ACTIVATE_N8N_WORKFLOW, DEACTIVATE_N8N_WORKFLOW, " +
26
- "DELETE_N8N_WORKFLOW, GET_N8N_EXECUTIONS). Confirm workflow drafts with the " +
27
- "user before deploying.";
28
- // Action keywords used to detect workflow mutations in assistant replies.
29
- const WORKFLOW_ACTION_KEYWORDS = /CREATE_N8N_WORKFLOW|ACTIVATE_N8N_WORKFLOW|DEACTIVATE_N8N_WORKFLOW|DELETE_N8N_WORKFLOW|workflow.*(created|deployed|activated|deactivated|deleted)/i;
30
- // Stable conversation title — same value the reference implementation uses.
31
- const AUTOMATIONS_CONVERSATION_TITLE = "__automations-scope__";
32
- // Module-level cache so re-mounting the tab doesn't re-resolve the conversation.
33
- // M5: guard against concurrent resolves with an in-flight promise.
34
- let _cachedConvId = null;
35
- let _inflight = null;
36
- async function resolveAutomationsConversation() {
37
- if (_cachedConvId)
38
- return _cachedConvId;
39
- if (_inflight)
40
- return _inflight;
41
- _inflight = (async () => {
42
- try {
43
- const { conversations } = await client.listConversations();
44
- const existing = conversations.find((c) => c.title === AUTOMATIONS_CONVERSATION_TITLE);
45
- if (existing) {
46
- _cachedConvId = existing.id;
47
- return existing.id;
48
- }
49
- }
50
- catch {
51
- /* fall through to create */
52
- }
53
- const { conversation } = await client.createConversation(AUTOMATIONS_CONVERSATION_TITLE);
54
- _cachedConvId = conversation.id;
55
- return conversation.id;
56
- })().finally(() => {
57
- _inflight = null;
58
- });
59
- return _inflight;
60
- }
61
- function N8nStatusBanner({ status, loading, onRetry, onStartLocal, onDismiss, dismissed, retrying, }) {
62
- const { t, setTab, setState } = useApp();
63
- if (loading || !status)
64
- return null;
65
- const mode = status.mode;
66
- const sidecarStatus = status.status;
67
- const platform = status.platform ?? "desktop";
68
- const cloudHealth = status.cloudHealth ?? "ok";
69
- // ── CTA block for disabled mode ─────────────────────────────────────────
70
- if (mode === "disabled") {
71
- const headingId = "n8n-cta-heading";
72
- if (platform === "mobile") {
73
- return (_jsxs("div", { role: "region", "aria-labelledby": headingId, className: "rounded-xl border border-border/30 bg-bg/30 px-4 py-5 mb-3 space-y-3", children: [_jsx("h3", { id: headingId, className: "text-sm font-semibold text-txt-strong", children: t("automations.n8n.ctaHeadingMobile") }), _jsx("p", { className: "text-xs text-muted", children: t("automations.n8n.ctaBodyMobile") }), _jsx(Button, { type: "button", variant: "default", size: "sm", className: "h-8 px-4 text-xs", onClick: () => {
74
- setState("cloudDashboardView", "overview");
75
- setTab("settings");
76
- }, children: t("automations.n8n.ctaSignInCloud") })] }));
77
- }
78
- // desktop: cloud (recommended) + local secondary
79
- return (_jsxs("div", { role: "region", "aria-labelledby": headingId, className: "rounded-xl border border-border/30 bg-bg/30 px-4 py-5 mb-3 space-y-3", children: [_jsx("h3", { id: headingId, className: "text-sm font-semibold text-txt-strong", children: t("automations.n8n.ctaHeadingDesktop") }), _jsx("p", { className: "text-xs text-muted", children: t("automations.n8n.ctaBodyDesktop") }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { type: "button", variant: "default", size: "sm", className: "h-8 px-4 text-xs", onClick: () => {
80
- setState("cloudDashboardView", "overview");
81
- setTab("settings");
82
- }, children: t("automations.n8n.ctaSignInCloud") }), _jsx(Button, { type: "button", variant: "outline", size: "sm", className: "h-8 px-4 text-xs", onClick: onStartLocal, children: t("automations.n8n.ctaEnableLocal") })] })] }));
83
- }
84
- // ── Cloud-health degraded banner ─────────────────────────────────────────
85
- if (mode === "cloud" && cloudHealth === "degraded") {
86
- return (_jsxs("div", { role: "status", className: "flex items-start gap-3 rounded-lg border border-warning/30 bg-warning/5 px-3 py-3 text-xs mb-3", children: [_jsxs("div", { className: "flex-1 space-y-1", children: [_jsx("div", { className: "font-semibold text-warning", children: t("automations.n8n.cloudDegradedHeading") }), _jsx("div", { className: "text-muted", children: t("automations.n8n.cloudDegradedBody") })] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", "aria-busy": retrying, className: "shrink-0 text-warning underline hover:no-underline h-auto w-auto p-0 text-xs", onClick: onRetry, disabled: retrying, children: t("automations.n8n.cloudDegradedRetry") }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", "aria-label": t("automations.n8n.bannerDismiss"), onClick: onDismiss, className: "shrink-0 text-muted hover:text-txt h-5 w-5", children: _jsx(X, { className: "h-3 w-3" }) })] }));
87
- }
88
- if (dismissed)
89
- return null;
90
- // ── Mode pill ────────────────────────────────────────────────────────────
91
- let pillLabel;
92
- let pillVariant;
93
- let pillAria;
94
- if (mode === "cloud") {
95
- // cloudHealth is "ok" or "unknown" here (degraded handled above)
96
- pillLabel = t("automations.n8n.pillCloudHealthy");
97
- pillVariant = "success";
98
- pillAria = t("automations.n8n.pillAriaCloudHealthy");
99
- }
100
- else if (mode === "local" && sidecarStatus === "ready") {
101
- pillLabel = t("automations.n8n.pillLocalReady");
102
- pillVariant = "success";
103
- pillAria = t("automations.n8n.pillAriaLocalReady");
104
- }
105
- else if (mode === "local" && sidecarStatus === "error") {
106
- pillLabel = t("automations.n8n.bannerLocalError");
107
- pillVariant = "danger";
108
- pillAria = t("automations.n8n.bannerLocalError");
109
- }
110
- else {
111
- // local + starting or stopped
112
- pillLabel = t("automations.n8n.pillLocalStarting");
113
- pillVariant = "warning";
114
- pillAria = t("automations.n8n.pillAriaLocalStarting");
115
- }
116
- return (_jsxs("div", { role: "status", className: "flex items-center gap-2 rounded-lg border border-border/20 bg-bg/20 px-3 py-2 text-xs mb-3", children: [_jsx(StatusBadge, { label: pillLabel, variant: pillVariant, withDot: true, "aria-label": pillAria }), _jsx("span", { className: "flex-1" }), mode === "local" && sidecarStatus === "error" && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", "aria-busy": retrying, className: "text-danger underline hover:no-underline h-auto w-auto p-0 text-xs", onClick: onRetry, disabled: retrying, children: t("automations.n8n.bannerRetry") })), _jsx(Button, { type: "button", variant: "ghost", size: "icon", "aria-label": t("automations.n8n.bannerDismiss"), onClick: onDismiss, className: "text-muted hover:text-txt h-5 w-5", children: _jsx(X, { className: "h-3 w-3" }) })] }));
117
- }
118
- function AutomationsChatPane({ collapsed, onToggleCollapse, composerRef, onWorkflowMutated, }) {
119
- const { t } = useApp();
120
- const [convId, setConvId] = useState(null);
121
- const [messages, setMessages] = useState([]);
122
- const [input, setInput] = useState("");
123
- const [sending, setSending] = useState(false);
124
- const [firstTokenReceived, setFirstTokenReceived] = useState(false);
125
- const abortRef = useRef(null);
126
- const scrollRef = useRef(null);
127
- // Remove unsafe cast — composerRef is already the right type
128
- const textareaRef = composerRef;
129
- // Resolve scoped conversation on mount. M4: reset cache on 404.
130
- useEffect(() => {
131
- let cancelled = false;
132
- void resolveAutomationsConversation().then(async (id) => {
133
- if (cancelled)
134
- return;
135
- setConvId(id);
136
- try {
137
- const { messages: msgs } = await client.getConversationMessages(id);
138
- if (!cancelled)
139
- setMessages(msgs);
140
- }
141
- catch (err) {
142
- const msg = err instanceof Error ? err.message : "";
143
- if (msg.toLowerCase().includes("not found") ||
144
- msg.includes("404") ||
145
- err.status === 404) {
146
- // M4: invalidate cache and re-resolve
147
- _cachedConvId = null;
148
- if (!cancelled) {
149
- void resolveAutomationsConversation().then((newId) => {
150
- if (!cancelled)
151
- setConvId(newId);
152
- });
153
- }
154
- }
155
- }
156
- });
157
- return () => {
158
- cancelled = true;
159
- // M1: abort in-flight stream on unmount
160
- abortRef.current?.abort();
161
- };
162
- }, []);
163
- // Auto-scroll to bottom.
164
- // biome-ignore lint/correctness/useExhaustiveDependencies: scroll fires on any message/send change
165
- useEffect(() => {
166
- const el = scrollRef.current;
167
- if (!el)
168
- return;
169
- const dist = el.scrollHeight - el.scrollTop - el.clientHeight;
170
- el.scrollTo({
171
- top: el.scrollHeight,
172
- behavior: dist < 150 ? "auto" : "smooth",
173
- });
174
- }, [messages, sending]);
175
- // Auto-resize textarea.
176
- useEffect(() => {
177
- const ta = textareaRef.current;
178
- if (!ta)
179
- return;
180
- if (!input) {
181
- ta.style.height = "38px";
182
- ta.style.overflowY = "hidden";
183
- return;
184
- }
185
- ta.style.height = "auto";
186
- ta.style.overflowY = "hidden";
187
- const h = Math.min(ta.scrollHeight, 150);
188
- ta.style.height = `${h}px`;
189
- ta.style.overflowY = ta.scrollHeight > 150 ? "auto" : "hidden";
190
- }, [input, textareaRef]);
191
- const handleSend = useCallback(async () => {
192
- const raw = input.trim();
193
- if (!raw || !convId || sending)
194
- return;
195
- const now = Date.now();
196
- const userMsgId = `auto-u-${now}`;
197
- const assistantMsgId = `auto-a-${now}`;
198
- // B5: Only prepend system addendum on first turn (messages.length === 0).
199
- // systemAddendum field not yet present in backend streaming endpoint — keep prepend gate.
200
- const isFirstTurn = messages.length === 0;
201
- const textToSend = isFirstTurn
202
- ? `[SYSTEM]${AUTOMATIONS_SYSTEM_ADDENDUM}[/SYSTEM]\n\n${raw}`
203
- : raw;
204
- setMessages((prev) => [
205
- ...prev,
206
- { id: userMsgId, role: "user", text: raw, timestamp: now },
207
- { id: assistantMsgId, role: "assistant", text: "", timestamp: now },
208
- ]);
209
- setInput("");
210
- setSending(true);
211
- setFirstTokenReceived(false);
212
- const controller = new AbortController();
213
- abortRef.current = controller;
214
- let streamed = "";
215
- let finalText = "";
216
- try {
217
- const data = await client.sendConversationMessageStream(convId, textToSend, (token) => {
218
- if (!token)
219
- return;
220
- const delta = token.slice(streamed.length);
221
- if (!delta)
222
- return;
223
- streamed += delta;
224
- setFirstTokenReceived(true);
225
- setMessages((prev) => prev.map((m) => m.id === assistantMsgId ? { ...m, text: m.text + delta } : m));
226
- }, "DM", controller.signal);
227
- finalText = data.text ?? streamed;
228
- if (data.text && data.text !== streamed) {
229
- setMessages((prev) => prev.map((m) => m.id === assistantMsgId ? { ...m, text: data.text } : m));
230
- }
231
- // M3: Only trigger workflow refresh if reply mentions workflow actions.
232
- if (WORKFLOW_ACTION_KEYWORDS.test(finalText)) {
233
- onWorkflowMutated();
234
- }
235
- }
236
- catch (err) {
237
- if (err.name === "AbortError")
238
- return;
239
- setMessages((prev) => prev.map((m) => m.id === assistantMsgId
240
- ? { ...m, text: t("automations.chat.errorGeneric") }
241
- : m));
242
- }
243
- finally {
244
- setSending(false);
245
- abortRef.current = null;
246
- }
247
- }, [input, convId, sending, messages.length, onWorkflowMutated, t]);
248
- const handleStop = useCallback(() => {
249
- abortRef.current?.abort();
250
- }, []);
251
- const handleKeyDown = (e) => {
252
- if (sending)
253
- return;
254
- if (e.key === "Enter" && !e.shiftKey) {
255
- e.preventDefault();
256
- void handleSend();
257
- }
258
- };
259
- const visibleMsgs = useMemo(() => messages.filter((m) => !(sending &&
260
- !firstTokenReceived &&
261
- m.role === "assistant" &&
262
- !m.text.trim())), [messages, sending, firstTokenReceived]);
263
- const label = t("automations.chat.assistantLabel");
264
- if (collapsed) {
265
- return (_jsx("div", { className: "border border-border/40 bg-card/60 rounded-xl overflow-hidden", children: _jsxs(Button, { type: "button", variant: "ghost", className: "w-full flex items-center gap-2 px-4 py-2.5 hover:bg-bg/50 transition-colors text-left h-auto justify-start", onClick: onToggleCollapse, "aria-label": t("automations.chat.expand"), children: [_jsx(Zap, { className: "w-3.5 h-3.5 text-accent shrink-0" }), _jsx("span", { className: "text-xs font-semibold text-txt-strong flex-1", children: label }), _jsx(ChevronDown, { className: "w-3.5 h-3.5 text-muted" })] }) }));
266
- }
267
- return (_jsxs("section", { className: "flex flex-col border border-border/40 bg-card/60 rounded-xl overflow-hidden", style: { minHeight: 0 }, "aria-label": label, children: [_jsxs(Button, { type: "button", variant: "ghost", className: "w-full flex items-center gap-2 px-4 py-2.5 border-b border-border/30 hover:bg-bg/50 transition-colors text-left h-auto justify-start", onClick: onToggleCollapse, "aria-label": t("automations.chat.collapse"), children: [_jsx(Zap, { className: "w-3.5 h-3.5 text-accent shrink-0" }), _jsx("span", { className: "text-xs font-semibold text-txt-strong flex-1", children: label }), _jsx(ChevronUp, { className: "w-3.5 h-3.5 text-muted" })] }), _jsx("div", { ref: scrollRef, role: "log", "aria-live": "polite", "aria-atomic": "false", className: "flex-1 overflow-y-auto px-3 py-2 flex flex-col", style: { maxHeight: "240px", minHeight: "80px" }, children: visibleMsgs.length === 0 && !sending ? (_jsx("div", { className: "flex-1 flex items-center justify-center text-center px-4 py-5", children: _jsx("p", { className: "text-sm text-muted", children: t("automations.chat.placeholder") }) })) : (_jsxs("div", { className: "w-full space-y-1", children: [visibleMsgs.map((msg) => (_jsxs("div", { className: `text-sm leading-relaxed rounded-lg px-3 py-2 ${msg.role === "user"
268
- ? "bg-accent/10 text-txt self-end ml-8"
269
- : "bg-bg/50 text-txt mr-8"}`, children: [_jsx("div", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted mb-0.5", children: msg.role === "user"
270
- ? t("automations.chat.roleUser")
271
- : t("automations.chat.roleAssistant") }), _jsx("div", { className: "whitespace-pre-wrap", children: msg.text })] }, msg.id))), sending && !firstTokenReceived && (_jsxs("div", { className: "bg-bg/50 rounded-lg px-3 py-2 mr-8", children: [_jsx("div", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted mb-0.5", children: t("automations.chat.roleAssistant") }), _jsxs("div", { className: "flex gap-1 items-center", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-muted/60 animate-bounce [animation-delay:0ms]" }), _jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-muted/60 animate-bounce [animation-delay:150ms]" }), _jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-muted/60 animate-bounce [animation-delay:300ms]" })] })] }))] })) }), _jsxs("div", { className: "flex gap-1.5 items-end border-t border-border/30 px-3 py-2", children: [_jsx(Textarea, { ref: textareaRef, variant: "default", className: "flex-1 min-w-0 px-3 py-2 bg-bg/40 border border-border/40 rounded-lg focus:border-accent/40 focus-visible:ring-0 focus:outline-none text-txt text-sm resize-none overflow-y-hidden min-h-[38px] max-h-[150px] placeholder:text-muted/60", rows: 1, "aria-label": label, placeholder: t("automations.chat.placeholder"), value: input, onChange: (e) => setInput(e.target.value), onKeyDown: handleKeyDown, disabled: sending || !convId }), sending ? (_jsxs(Button, { variant: "destructive", className: "h-[38px] shrink-0 px-3 text-sm gap-1.5", onClick: handleStop, title: t("automations.chat.stop"), children: [_jsx(Square, { className: "w-3 h-3 fill-current" }), _jsx("span", { children: t("automations.chat.stop") })] })) : (_jsxs(Button, { variant: "default", className: "h-[38px] shrink-0 px-4 text-sm gap-1.5", onClick: () => void handleSend(), disabled: !input.trim() || !convId, "aria-label": t("automations.chat.send"), children: [_jsx(Send, { className: "w-4 h-4" }), _jsx("span", { className: "hidden sm:inline", children: t("automations.chat.send") })] }))] })] }));
272
- }
273
- // ---------------------------------------------------------------------------
274
- // Workflow sidebar row
275
- // ---------------------------------------------------------------------------
276
- function WorkflowSidebarRow({ workflow, selected, onClick, onKeyDown, }) {
277
- const { t } = useApp();
278
- const nodeCount = workflow.nodeCount ?? workflow.nodes?.length ?? 0;
279
- return (
280
- // M7: aria-pressed for selection toggle
281
- _jsxs("button", { type: "button", onClick: onClick, onKeyDown: onKeyDown, "aria-pressed": selected, className: `w-full text-left px-3 py-2.5 flex items-center gap-2.5 rounded-lg transition-colors cursor-pointer hover:bg-bg/50 ${selected ? "bg-accent/10" : ""}`, children: [_jsx(Workflow, { className: "h-3.5 w-3.5 shrink-0 text-muted/60" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-sm font-semibold text-txt truncate", children: workflow.name }), nodeCount > 0 && (_jsx("div", { className: "text-xs-tight text-muted mt-0.5", children: t("automations.n8n.nodeCount", { count: nodeCount }) }))] }), _jsx(StatusBadge, { label: workflow.active
282
- ? t("automations.n8n.workflowActive")
283
- : t("automations.n8n.workflowInactive"), variant: workflow.active ? "success" : "muted", withDot: true })] }));
284
- }
285
- // ---------------------------------------------------------------------------
286
- // Workflow detail pane (with embedded chat)
287
- // ---------------------------------------------------------------------------
288
- function WorkflowDetailPane({ workflow, busy, onToggleActive, onDelete, composerRef, onWorkflowMutated, }) {
289
- const { t } = useApp();
290
- const [chatCollapsed, setChatCollapsed] = useState(false);
291
- // biome-ignore lint/correctness/useExhaustiveDependencies: expand chat when selection changes
292
- useEffect(() => {
293
- setChatCollapsed(false);
294
- }, [workflow?.id]);
295
- if (!workflow) {
296
- // No selection: chat fills the pane.
297
- return (_jsx("div", { className: "flex flex-col gap-4 p-4 h-full", children: _jsx(AutomationsChatPane, { collapsed: false, onToggleCollapse: () => { }, composerRef: composerRef, onWorkflowMutated: onWorkflowMutated }) }));
298
- }
299
- const nodes = workflow.nodes ?? [];
300
- const nodeCount = workflow.nodeCount ?? nodes.length;
301
- return (_jsxs("div", { className: "flex flex-col gap-4 p-4 overflow-y-auto", children: [_jsx(AutomationsChatPane, { collapsed: chatCollapsed, onToggleCollapse: () => setChatCollapsed((v) => !v), composerRef: composerRef, onWorkflowMutated: onWorkflowMutated }), _jsxs("div", { className: "rounded-xl border border-border/40 bg-card/50 p-5 space-y-4", children: [_jsx("div", { className: "flex items-start gap-3", children: _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 mb-1", children: [_jsxs(FieldLabel, { variant: "kicker", children: [_jsx(Workflow, { className: "mr-1.5 inline h-3.5 w-3.5" }), "Workflow"] }), _jsx(StatusBadge, { label: workflow.active
302
- ? t("automations.n8n.workflowActive")
303
- : t("automations.n8n.workflowInactive"), variant: workflow.active ? "success" : "muted", withDot: true })] }), _jsx("h2", { className: "text-2xl font-semibold text-txt", children: workflow.name }), workflow.description && (_jsx("p", { className: "text-sm text-muted mt-1", children: workflow.description }))] }) }), _jsx("div", { className: "flex items-center gap-2", children: _jsx(Button, { variant: "outline", size: "sm", className: `h-8 px-3 text-xs ${workflow.active
304
- ? "border-warning/30 text-warning hover:bg-warning/10"
305
- : "border-ok/30 text-ok hover:bg-ok/10"}`, disabled: busy === workflow.id, onClick: () => onToggleActive(workflow), children: busy === workflow.id
306
- ? t("automations.n8n.updating")
307
- : workflow.active
308
- ? t("automations.n8n.deactivate")
309
- : t("automations.n8n.activate") }) })] }), nodeCount > 0 && (_jsxs("div", { className: "rounded-xl border border-border/40 bg-card/50 p-4 space-y-2", children: [_jsx("div", { className: "text-xs font-semibold uppercase tracking-wider text-muted", children: t("automations.n8n.nodeCount", { count: nodeCount }) }), _jsx("div", { className: "space-y-1", children: nodes.length > 0
310
- ? nodes.map((node) => (_jsxs("div", { className: "text-sm text-txt flex items-center gap-2 py-1 border-b border-border/20 last:border-b-0", children: [_jsx("span", { className: "flex-1", children: node.name }), _jsx("span", { className: "text-xs text-muted font-mono", children: node.type.split(".").pop() })] }, node.id)))
311
- : Array.from({ length: nodeCount }, (_, index) => index + 1).map((nodeNumber) => (_jsxs("div", { className: "text-sm text-muted py-1", children: ["Node ", nodeNumber] }, `workflow-node-${nodeNumber}`))) })] })), _jsxs("div", { className: "rounded-xl border border-danger/30 bg-danger/5 p-4 space-y-2", children: [_jsx("div", { className: "text-xs font-semibold uppercase tracking-wider text-danger", children: t("automations.n8n.dangerZone") }), _jsx("p", { className: "text-sm text-muted", children: t("automations.n8n.deleteConfirmMessage") }), _jsx(Button, { variant: "outline", size: "sm", className: "border-danger/40 text-danger hover:bg-danger/10", disabled: busy === workflow.id, onClick: () => onDelete(workflow), children: t("automations.n8n.deleteWorkflow") })] })] }));
312
- }
313
- export function N8nWorkflowsPanel({ composerRef }) {
314
- const { t } = useApp();
315
- // ── Status + workflow state ─────────────────────────────────────────────
316
- const [n8nStatus, setN8nStatus] = useState(null);
317
- const [statusLoading, setStatusLoading] = useState(true);
318
- const [workflows, setWorkflows] = useState([]);
319
- const [workflowsLoading, setWorkflowsLoading] = useState(false);
320
- // M8: error queue instead of single string
321
- const [errors, setErrors] = useState([]);
322
- const [selectedId, setSelectedId] = useState(null);
323
- const [busy, setBusy] = useState(null);
324
- const [bannerDismissed, setBannerDismissed] = useState(false);
325
- const [retrying, setRetrying] = useState(false);
326
- const didAutoStart = useRef(false);
327
- const selectedWorkflow = workflows.find((wf) => wf.id === selectedId) ?? null;
328
- const pushError = useCallback((message) => {
329
- const id = `err-${Date.now()}-${Math.random()}`;
330
- setErrors((prev) => {
331
- const next = [...prev, { id, message }];
332
- // Cap at 3, drop oldest
333
- return next.length > 3 ? next.slice(next.length - 3) : next;
334
- });
335
- }, []);
336
- const dismissError = useCallback((id) => {
337
- setErrors((prev) => prev.filter((e) => e.id !== id));
338
- }, []);
339
- const loadStatus = useCallback(async () => {
340
- setStatusLoading(true);
341
- try {
342
- const s = await client.getN8nStatus();
343
- setN8nStatus(s);
344
- }
345
- catch (err) {
346
- pushError(t("automations.n8n.errorLoadStatus", {
347
- message: err instanceof Error ? err.message : "network error",
348
- }));
349
- }
350
- finally {
351
- setStatusLoading(false);
352
- }
353
- }, [pushError, t]);
354
- const loadWorkflows = useCallback(async () => {
355
- setWorkflowsLoading(true);
356
- try {
357
- const list = await client.listN8nWorkflows();
358
- setWorkflows(list);
359
- // M8: clear errors on successful refresh
360
- setErrors([]);
361
- // Minor: rewrite as proper ternary
362
- setSelectedId((cur) => cur && list.some((wf) => wf.id === cur) ? cur : null);
363
- }
364
- catch (err) {
365
- pushError(t("automations.n8n.errorLoadWorkflows", {
366
- message: err instanceof Error ? err.message : "network error",
367
- }));
368
- }
369
- finally {
370
- setWorkflowsLoading(false);
371
- }
372
- }, [pushError, t]);
373
- // Bootstrap on mount.
374
- useEffect(() => {
375
- void loadStatus();
376
- }, [loadStatus]);
377
- // Load workflows once status is known.
378
- useEffect(() => {
379
- if (n8nStatus && n8nStatus.mode !== "disabled") {
380
- void loadWorkflows();
381
- }
382
- }, [n8nStatus, loadWorkflows]);
383
- // B3: Auto-start local sidecar when mode is "local" and status is "stopped".
384
- useEffect(() => {
385
- if (didAutoStart.current)
386
- return;
387
- if (!n8nStatus)
388
- return;
389
- if (n8nStatus.mode === "local" &&
390
- n8nStatus.status === "stopped" &&
391
- n8nStatus.localEnabled !== false &&
392
- !n8nStatus.cloudConnected) {
393
- didAutoStart.current = true;
394
- void client.startN8nSidecar().catch(() => {
395
- /* ignore — status poll will reflect actual state */
396
- });
397
- }
398
- }, [n8nStatus]);
399
- // B4: Poll status every 3s while starting; stop once ready/error.
400
- // M2: Visibility-guarded.
401
- useEffect(() => {
402
- if (!n8nStatus || n8nStatus.status !== "starting")
403
- return;
404
- let id = null;
405
- const start = () => {
406
- if (!id)
407
- id = setInterval(() => void loadStatus(), 3_000);
408
- };
409
- const stop = () => {
410
- if (id) {
411
- clearInterval(id);
412
- id = null;
413
- }
414
- };
415
- if (document.visibilityState === "visible")
416
- start();
417
- const onVis = () => document.visibilityState === "visible" ? start() : stop();
418
- document.addEventListener("visibilitychange", onVis);
419
- return () => {
420
- stop();
421
- document.removeEventListener("visibilitychange", onVis);
422
- };
423
- }, [n8nStatus, loadStatus]);
424
- // M2: Poll workflows every 10s with visibility guard.
425
- useEffect(() => {
426
- if (!n8nStatus || n8nStatus.mode === "disabled")
427
- return;
428
- let id = null;
429
- const start = () => {
430
- if (!id)
431
- id = setInterval(() => void loadWorkflows(), 10_000);
432
- };
433
- const stop = () => {
434
- if (id) {
435
- clearInterval(id);
436
- id = null;
437
- }
438
- };
439
- if (document.visibilityState === "visible")
440
- start();
441
- const onVis = () => document.visibilityState === "visible" ? start() : stop();
442
- document.addEventListener("visibilitychange", onVis);
443
- return () => {
444
- stop();
445
- document.removeEventListener("visibilitychange", onVis);
446
- };
447
- }, [n8nStatus, loadWorkflows]);
448
- const handleWorkflowMutated = useCallback(() => {
449
- if (n8nStatus && n8nStatus.mode !== "disabled") {
450
- void loadWorkflows();
451
- }
452
- }, [n8nStatus, loadWorkflows]);
453
- const handleRefresh = useCallback(() => {
454
- void loadStatus();
455
- void loadWorkflows();
456
- }, [loadStatus, loadWorkflows]);
457
- const handleRetry = useCallback(async () => {
458
- setRetrying(true);
459
- try {
460
- await client.startN8nSidecar().catch(() => { });
461
- await loadStatus();
462
- }
463
- finally {
464
- setRetrying(false);
465
- }
466
- }, [loadStatus]);
467
- // User-initiated local sidecar start (from the CTA block on desktop disabled mode).
468
- const handleStartLocal = useCallback(async () => {
469
- setRetrying(true);
470
- try {
471
- await client.startN8nSidecar().catch(() => { });
472
- await loadStatus();
473
- }
474
- finally {
475
- setRetrying(false);
476
- }
477
- }, [loadStatus]);
478
- const handleToggleActive = useCallback(async (wf) => {
479
- setBusy(wf.id);
480
- try {
481
- if (wf.active) {
482
- await client.deactivateN8nWorkflow(wf.id);
483
- }
484
- else {
485
- await client.activateN8nWorkflow(wf.id);
486
- }
487
- setWorkflows((prev) => prev.map((w) => (w.id === wf.id ? { ...w, active: !wf.active } : w)));
488
- }
489
- catch (err) {
490
- pushError(t("automations.n8n.errorUpdateWorkflow", {
491
- message: err instanceof Error ? err.message : "error",
492
- }));
493
- }
494
- finally {
495
- setBusy(null);
496
- }
497
- }, [pushError, t]);
498
- const handleDelete = useCallback(async (wf) => {
499
- const confirmed = await confirmDesktopAction({
500
- title: t("automations.n8n.deleteWorkflow"),
501
- // M9: i18n the delete confirm message with workflow name
502
- message: t("automations.n8n.deleteConfirmWorkflow", { name: wf.name }),
503
- confirmLabel: t("automations.n8n.deleteWorkflow"),
504
- cancelLabel: t("common.cancel"),
505
- type: "warning",
506
- });
507
- if (!confirmed)
508
- return;
509
- setBusy(wf.id);
510
- try {
511
- await client.deleteN8nWorkflow(wf.id);
512
- setWorkflows((prev) => prev.filter((w) => w.id !== wf.id));
513
- setSelectedId((cur) => (cur === wf.id ? null : cur));
514
- }
515
- catch (err) {
516
- pushError(t("automations.n8n.errorDeleteWorkflow", {
517
- message: err instanceof Error ? err.message : "error",
518
- }));
519
- }
520
- finally {
521
- setBusy(null);
522
- }
523
- }, [t, pushError]);
524
- // M7: Keyboard navigation between workflow rows.
525
- const handleRowKeyDown = useCallback((e, index) => {
526
- if (e.key === "ArrowDown" || e.key === "ArrowUp") {
527
- e.preventDefault();
528
- const nextIndex = e.key === "ArrowDown"
529
- ? Math.min(index + 1, workflows.length - 1)
530
- : Math.max(index - 1, 0);
531
- // Focus the button at nextIndex — they are siblings in the parent div
532
- const parent = e.currentTarget.parentElement;
533
- if (!parent)
534
- return;
535
- const buttons = parent.querySelectorAll("button[type='button']");
536
- buttons[nextIndex]?.focus();
537
- }
538
- }, [workflows.length]);
539
- // ── Sidebar workflow list ───────────────────────────────────────────────
540
- const workflowSidebar = (_jsx("div", { className: "space-y-1 px-1", children: workflowsLoading ? (_jsxs("div", { className: "flex items-center gap-2 py-4 text-sm text-muted/70 px-3", children: [_jsx("div", { className: "h-4 w-4 animate-spin rounded-full border-2 border-muted/30 border-t-muted/80" }), t("common.loading")] })) : workflows.length === 0 ? (_jsxs("div", { className: "px-3 py-6 text-center text-sm text-muted", children: [_jsx(Zap, { className: "mx-auto mb-2 h-5 w-5 text-muted/40" }), _jsx("div", { className: "font-semibold text-txt-strong text-xs mb-1", children: t("automations.n8n.noWorkflowsTitle") }), _jsx("div", { className: "text-xs text-muted", children: t("automations.n8n.noWorkflowsHint") })] })) : (workflows.map((wf, index) => (_jsx(WorkflowSidebarRow, { workflow: wf, selected: selectedId === wf.id, onClick: () => setSelectedId((cur) => (cur === wf.id ? null : wf.id)), onKeyDown: (e) => handleRowKeyDown(e, index) }, wf.id)))) }));
541
- return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [_jsxs("div", { className: "flex items-center gap-2 mb-1", children: [_jsx("div", { className: "flex-1 min-w-0", children: _jsx(N8nStatusBanner, { status: n8nStatus, loading: statusLoading, onRetry: () => void handleRetry(), onStartLocal: () => void handleStartLocal(), onDismiss: () => setBannerDismissed(true), dismissed: bannerDismissed, retrying: retrying }) }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "shrink-0 flex items-center gap-1 text-muted hover:text-txt text-xs px-2 py-1 rounded-lg hover:bg-bg/50 transition-colors mb-3 h-7 w-7", onClick: handleRefresh, disabled: workflowsLoading, "aria-label": t("actions.refresh"), children: _jsx(RefreshCw, { className: `h-3.5 w-3.5 ${workflowsLoading ? "animate-spin" : ""}` }) })] }), errors.length > 0 && (_jsx("div", { className: "mb-2 flex flex-col gap-1", children: errors.map((err) => (_jsxs("div", { className: "flex items-center justify-between rounded-lg border border-danger/20 bg-danger/10 px-3 py-2 text-xs text-danger", children: [_jsx("span", { children: err.message }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "ml-2 text-danger/60 hover:text-danger h-5 w-5", onClick: () => dismissError(err.id), children: _jsx(X, { className: "h-3 w-3" }) })] }, err.id))) })), _jsxs("div", { className: "flex flex-1 min-h-0 gap-4", children: [_jsx("nav", { "aria-label": t("automations.n8n.workflowListLabel"), className: "w-56 shrink-0 overflow-y-auto", children: workflowSidebar }), _jsx("div", { className: "flex-1 min-w-0 overflow-y-auto", children: _jsx(WorkflowDetailPane, { workflow: selectedWorkflow, busy: busy, onToggleActive: (wf) => void handleToggleActive(wf), onDelete: (wf) => void handleDelete(wf), composerRef: composerRef, onWorkflowMutated: handleWorkflowMutated }) })] })] }));
542
- }