@octo-cyber/workflow 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/dist/controllers/ai-workflow-session.controller.d.ts +19 -0
  2. package/dist/controllers/ai-workflow-session.controller.d.ts.map +1 -0
  3. package/dist/controllers/ai-workflow-session.controller.js +135 -0
  4. package/dist/controllers/ai-workflow-session.controller.js.map +1 -0
  5. package/dist/controllers/credential.controller.d.ts +68 -0
  6. package/dist/controllers/credential.controller.d.ts.map +1 -0
  7. package/dist/controllers/credential.controller.js +303 -0
  8. package/dist/controllers/credential.controller.js.map +1 -0
  9. package/dist/controllers/index.d.ts +3 -0
  10. package/dist/controllers/index.d.ts.map +1 -0
  11. package/dist/controllers/index.js +8 -0
  12. package/dist/controllers/index.js.map +1 -0
  13. package/dist/controllers/workflow-ai.controller.d.ts +23 -0
  14. package/dist/controllers/workflow-ai.controller.d.ts.map +1 -0
  15. package/dist/controllers/workflow-ai.controller.js +164 -0
  16. package/dist/controllers/workflow-ai.controller.js.map +1 -0
  17. package/dist/controllers/workflow.controller.d.ts +66 -0
  18. package/dist/controllers/workflow.controller.d.ts.map +1 -0
  19. package/dist/controllers/workflow.controller.js +239 -0
  20. package/dist/controllers/workflow.controller.js.map +1 -0
  21. package/dist/core/expression-resolver.d.ts +49 -0
  22. package/dist/core/expression-resolver.d.ts.map +1 -0
  23. package/dist/core/expression-resolver.js +113 -0
  24. package/dist/core/expression-resolver.js.map +1 -0
  25. package/dist/core/node-registry.d.ts +24 -0
  26. package/dist/core/node-registry.d.ts.map +1 -0
  27. package/dist/core/node-registry.js +62 -0
  28. package/dist/core/node-registry.js.map +1 -0
  29. package/dist/core/workflow-executor.d.ts +50 -0
  30. package/dist/core/workflow-executor.d.ts.map +1 -0
  31. package/dist/core/workflow-executor.js +458 -0
  32. package/dist/core/workflow-executor.js.map +1 -0
  33. package/dist/entities/ai-workflow-session.entity.d.ts +17 -0
  34. package/dist/entities/ai-workflow-session.entity.d.ts.map +1 -0
  35. package/dist/entities/ai-workflow-session.entity.js +70 -0
  36. package/dist/entities/ai-workflow-session.entity.js.map +1 -0
  37. package/dist/entities/ai-workflow-version.entity.d.ts +18 -0
  38. package/dist/entities/ai-workflow-version.entity.d.ts.map +1 -0
  39. package/dist/entities/ai-workflow-version.entity.js +71 -0
  40. package/dist/entities/ai-workflow-version.entity.js.map +1 -0
  41. package/dist/entities/credential-definition.entity.d.ts +17 -0
  42. package/dist/entities/credential-definition.entity.d.ts.map +1 -0
  43. package/dist/entities/credential-definition.entity.js +66 -0
  44. package/dist/entities/credential-definition.entity.js.map +1 -0
  45. package/dist/entities/index.d.ts +9 -0
  46. package/dist/entities/index.d.ts.map +1 -0
  47. package/dist/entities/index.js +22 -0
  48. package/dist/entities/index.js.map +1 -0
  49. package/dist/entities/workflow-definition.entity.d.ts +20 -0
  50. package/dist/entities/workflow-definition.entity.d.ts.map +1 -0
  51. package/dist/entities/workflow-definition.entity.js +85 -0
  52. package/dist/entities/workflow-definition.entity.js.map +1 -0
  53. package/dist/entities/workflow-execution.entity.d.ts +26 -0
  54. package/dist/entities/workflow-execution.entity.d.ts.map +1 -0
  55. package/dist/entities/workflow-execution.entity.js +99 -0
  56. package/dist/entities/workflow-execution.entity.js.map +1 -0
  57. package/dist/index.d.ts +52 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +96 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/n8n/cipher/cipher.d.ts +11 -0
  62. package/dist/n8n/cipher/cipher.d.ts.map +1 -0
  63. package/dist/n8n/cipher/cipher.js +54 -0
  64. package/dist/n8n/cipher/cipher.js.map +1 -0
  65. package/dist/n8n/controllers/n8n-session.controller.d.ts +20 -0
  66. package/dist/n8n/controllers/n8n-session.controller.d.ts.map +1 -0
  67. package/dist/n8n/controllers/n8n-session.controller.js +84 -0
  68. package/dist/n8n/controllers/n8n-session.controller.js.map +1 -0
  69. package/dist/n8n/index.d.ts +8 -0
  70. package/dist/n8n/index.d.ts.map +1 -0
  71. package/dist/n8n/index.js +14 -0
  72. package/dist/n8n/index.js.map +1 -0
  73. package/dist/n8n/launcher/n8n-launcher.d.ts +41 -0
  74. package/dist/n8n/launcher/n8n-launcher.d.ts.map +1 -0
  75. package/dist/n8n/launcher/n8n-launcher.js +186 -0
  76. package/dist/n8n/launcher/n8n-launcher.js.map +1 -0
  77. package/dist/n8n/nodes/OctoClaudeCliTool.node.d.ts +13 -0
  78. package/dist/n8n/nodes/OctoClaudeCliTool.node.d.ts.map +1 -0
  79. package/dist/n8n/nodes/OctoClaudeCliTool.node.js +103 -0
  80. package/dist/n8n/nodes/OctoClaudeCliTool.node.js.map +1 -0
  81. package/dist/n8n/nodes/OctoCliChatModel.node.d.ts +14 -0
  82. package/dist/n8n/nodes/OctoCliChatModel.node.d.ts.map +1 -0
  83. package/dist/n8n/nodes/OctoCliChatModel.node.js +431 -0
  84. package/dist/n8n/nodes/OctoCliChatModel.node.js.map +1 -0
  85. package/dist/n8n/nodes/OctoCodexCliTool.node.d.ts +13 -0
  86. package/dist/n8n/nodes/OctoCodexCliTool.node.d.ts.map +1 -0
  87. package/dist/n8n/nodes/OctoCodexCliTool.node.js +100 -0
  88. package/dist/n8n/nodes/OctoCodexCliTool.node.js.map +1 -0
  89. package/dist/n8n/nodes/OctoGeminiCliTool.node.d.ts +13 -0
  90. package/dist/n8n/nodes/OctoGeminiCliTool.node.d.ts.map +1 -0
  91. package/dist/n8n/nodes/OctoGeminiCliTool.node.js +91 -0
  92. package/dist/n8n/nodes/OctoGeminiCliTool.node.js.map +1 -0
  93. package/dist/n8n/nodes/OctoKnowledge.node.d.ts +6 -0
  94. package/dist/n8n/nodes/OctoKnowledge.node.d.ts.map +1 -0
  95. package/dist/n8n/nodes/OctoKnowledge.node.js +95 -0
  96. package/dist/n8n/nodes/OctoKnowledge.node.js.map +1 -0
  97. package/dist/n8n/nodes/OctoNotification.node.d.ts +6 -0
  98. package/dist/n8n/nodes/OctoNotification.node.d.ts.map +1 -0
  99. package/dist/n8n/nodes/OctoNotification.node.js +72 -0
  100. package/dist/n8n/nodes/OctoNotification.node.js.map +1 -0
  101. package/dist/n8n/nodes/OctoSyncBridge.node.d.ts +6 -0
  102. package/dist/n8n/nodes/OctoSyncBridge.node.d.ts.map +1 -0
  103. package/dist/n8n/nodes/OctoSyncBridge.node.js +60 -0
  104. package/dist/n8n/nodes/OctoSyncBridge.node.js.map +1 -0
  105. package/dist/n8n/nodes/n8n-node-types.d.ts +111 -0
  106. package/dist/n8n/nodes/n8n-node-types.d.ts.map +1 -0
  107. package/dist/n8n/nodes/n8n-node-types.js +30 -0
  108. package/dist/n8n/nodes/n8n-node-types.js.map +1 -0
  109. package/dist/n8n/nodes/octo-cli-chat-model.svg +23 -0
  110. package/dist/n8n/nodes/package.json +16 -0
  111. package/dist/n8n/proxy/css-injector.d.ts +3 -0
  112. package/dist/n8n/proxy/css-injector.d.ts.map +1 -0
  113. package/dist/n8n/proxy/css-injector.js +62 -0
  114. package/dist/n8n/proxy/css-injector.js.map +1 -0
  115. package/dist/n8n/proxy/n8n-proxy.middleware.d.ts +12 -0
  116. package/dist/n8n/proxy/n8n-proxy.middleware.d.ts.map +1 -0
  117. package/dist/n8n/proxy/n8n-proxy.middleware.js +131 -0
  118. package/dist/n8n/proxy/n8n-proxy.middleware.js.map +1 -0
  119. package/dist/n8n/proxy/n8n-theme.css +45 -0
  120. package/dist/n8n/sync/n8n-user-sync.service.d.ts +75 -0
  121. package/dist/n8n/sync/n8n-user-sync.service.d.ts.map +1 -0
  122. package/dist/n8n/sync/n8n-user-sync.service.js +286 -0
  123. package/dist/n8n/sync/n8n-user-sync.service.js.map +1 -0
  124. package/dist/n8n/watcher/n8n-execution-watcher.d.ts +39 -0
  125. package/dist/n8n/watcher/n8n-execution-watcher.d.ts.map +1 -0
  126. package/dist/n8n/watcher/n8n-execution-watcher.js +110 -0
  127. package/dist/n8n/watcher/n8n-execution-watcher.js.map +1 -0
  128. package/dist/nodes/code.node.d.ts +24 -0
  129. package/dist/nodes/code.node.d.ts.map +1 -0
  130. package/dist/nodes/code.node.js +150 -0
  131. package/dist/nodes/code.node.js.map +1 -0
  132. package/dist/nodes/error-trigger.node.d.ts +15 -0
  133. package/dist/nodes/error-trigger.node.d.ts.map +1 -0
  134. package/dist/nodes/error-trigger.node.js +53 -0
  135. package/dist/nodes/error-trigger.node.js.map +1 -0
  136. package/dist/nodes/execute-command.node.d.ts +9 -0
  137. package/dist/nodes/execute-command.node.d.ts.map +1 -0
  138. package/dist/nodes/execute-command.node.js +81 -0
  139. package/dist/nodes/execute-command.node.js.map +1 -0
  140. package/dist/nodes/filter.node.d.ts +10 -0
  141. package/dist/nodes/filter.node.d.ts.map +1 -0
  142. package/dist/nodes/filter.node.js +95 -0
  143. package/dist/nodes/filter.node.js.map +1 -0
  144. package/dist/nodes/http-request.node.d.ts +11 -0
  145. package/dist/nodes/http-request.node.d.ts.map +1 -0
  146. package/dist/nodes/http-request.node.js +139 -0
  147. package/dist/nodes/http-request.node.js.map +1 -0
  148. package/dist/nodes/if.node.d.ts +13 -0
  149. package/dist/nodes/if.node.d.ts.map +1 -0
  150. package/dist/nodes/if.node.js +137 -0
  151. package/dist/nodes/if.node.js.map +1 -0
  152. package/dist/nodes/index.d.ts +12 -0
  153. package/dist/nodes/index.d.ts.map +1 -0
  154. package/dist/nodes/index.js +26 -0
  155. package/dist/nodes/index.js.map +1 -0
  156. package/dist/nodes/manual-trigger.node.d.ts +10 -0
  157. package/dist/nodes/manual-trigger.node.d.ts.map +1 -0
  158. package/dist/nodes/manual-trigger.node.js +36 -0
  159. package/dist/nodes/manual-trigger.node.js.map +1 -0
  160. package/dist/nodes/merge.node.d.ts +15 -0
  161. package/dist/nodes/merge.node.d.ts.map +1 -0
  162. package/dist/nodes/merge.node.js +99 -0
  163. package/dist/nodes/merge.node.js.map +1 -0
  164. package/dist/nodes/noop.node.d.ts +12 -0
  165. package/dist/nodes/noop.node.d.ts.map +1 -0
  166. package/dist/nodes/noop.node.js +32 -0
  167. package/dist/nodes/noop.node.js.map +1 -0
  168. package/dist/nodes/placeholder.node.d.ts +10 -0
  169. package/dist/nodes/placeholder.node.d.ts.map +1 -0
  170. package/dist/nodes/placeholder.node.js +50 -0
  171. package/dist/nodes/placeholder.node.js.map +1 -0
  172. package/dist/nodes/remove-duplicates.node.d.ts +9 -0
  173. package/dist/nodes/remove-duplicates.node.d.ts.map +1 -0
  174. package/dist/nodes/remove-duplicates.node.js +68 -0
  175. package/dist/nodes/remove-duplicates.node.js.map +1 -0
  176. package/dist/nodes/respond-to-webhook.node.d.ts +14 -0
  177. package/dist/nodes/respond-to-webhook.node.d.ts.map +1 -0
  178. package/dist/nodes/respond-to-webhook.node.js +116 -0
  179. package/dist/nodes/respond-to-webhook.node.js.map +1 -0
  180. package/dist/nodes/schedule-trigger.node.d.ts +9 -0
  181. package/dist/nodes/schedule-trigger.node.d.ts.map +1 -0
  182. package/dist/nodes/schedule-trigger.node.js +67 -0
  183. package/dist/nodes/schedule-trigger.node.js.map +1 -0
  184. package/dist/nodes/set.node.d.ts +12 -0
  185. package/dist/nodes/set.node.d.ts.map +1 -0
  186. package/dist/nodes/set.node.js +81 -0
  187. package/dist/nodes/set.node.js.map +1 -0
  188. package/dist/nodes/sort.node.d.ts +9 -0
  189. package/dist/nodes/sort.node.d.ts.map +1 -0
  190. package/dist/nodes/sort.node.js +61 -0
  191. package/dist/nodes/sort.node.js.map +1 -0
  192. package/dist/nodes/split-in-batches.node.d.ts +9 -0
  193. package/dist/nodes/split-in-batches.node.d.ts.map +1 -0
  194. package/dist/nodes/split-in-batches.node.js +53 -0
  195. package/dist/nodes/split-in-batches.node.js.map +1 -0
  196. package/dist/nodes/split-out.node.d.ts +9 -0
  197. package/dist/nodes/split-out.node.d.ts.map +1 -0
  198. package/dist/nodes/split-out.node.js +76 -0
  199. package/dist/nodes/split-out.node.js.map +1 -0
  200. package/dist/nodes/switch.node.d.ts +16 -0
  201. package/dist/nodes/switch.node.d.ts.map +1 -0
  202. package/dist/nodes/switch.node.js +156 -0
  203. package/dist/nodes/switch.node.js.map +1 -0
  204. package/dist/nodes/wait.node.d.ts +12 -0
  205. package/dist/nodes/wait.node.d.ts.map +1 -0
  206. package/dist/nodes/wait.node.js +81 -0
  207. package/dist/nodes/wait.node.js.map +1 -0
  208. package/dist/nodes/webhook.node.d.ts +9 -0
  209. package/dist/nodes/webhook.node.d.ts.map +1 -0
  210. package/dist/nodes/webhook.node.js +69 -0
  211. package/dist/nodes/webhook.node.js.map +1 -0
  212. package/dist/services/ai-workflow-session.service.d.ts +31 -0
  213. package/dist/services/ai-workflow-session.service.d.ts.map +1 -0
  214. package/dist/services/ai-workflow-session.service.js +118 -0
  215. package/dist/services/ai-workflow-session.service.js.map +1 -0
  216. package/dist/services/credential.service.d.ts +57 -0
  217. package/dist/services/credential.service.d.ts.map +1 -0
  218. package/dist/services/credential.service.js +155 -0
  219. package/dist/services/credential.service.js.map +1 -0
  220. package/dist/services/index.d.ts +10 -0
  221. package/dist/services/index.d.ts.map +1 -0
  222. package/dist/services/index.js +14 -0
  223. package/dist/services/index.js.map +1 -0
  224. package/dist/services/push.service.d.ts +60 -0
  225. package/dist/services/push.service.d.ts.map +1 -0
  226. package/dist/services/push.service.js +121 -0
  227. package/dist/services/push.service.js.map +1 -0
  228. package/dist/services/workflow-ai.service.d.ts +61 -0
  229. package/dist/services/workflow-ai.service.d.ts.map +1 -0
  230. package/dist/services/workflow-ai.service.js +219 -0
  231. package/dist/services/workflow-ai.service.js.map +1 -0
  232. package/dist/services/workflow-context.service.d.ts +32 -0
  233. package/dist/services/workflow-context.service.d.ts.map +1 -0
  234. package/dist/services/workflow-context.service.js +155 -0
  235. package/dist/services/workflow-context.service.js.map +1 -0
  236. package/dist/services/workflow-engine.service.d.ts +90 -0
  237. package/dist/services/workflow-engine.service.d.ts.map +1 -0
  238. package/dist/services/workflow-engine.service.js +305 -0
  239. package/dist/services/workflow-engine.service.js.map +1 -0
  240. package/dist/services/workflow.service.d.ts +84 -0
  241. package/dist/services/workflow.service.d.ts.map +1 -0
  242. package/dist/services/workflow.service.js +241 -0
  243. package/dist/services/workflow.service.js.map +1 -0
  244. package/dist/triggers/cron-trigger.d.ts +39 -0
  245. package/dist/triggers/cron-trigger.d.ts.map +1 -0
  246. package/dist/triggers/cron-trigger.js +137 -0
  247. package/dist/triggers/cron-trigger.js.map +1 -0
  248. package/dist/triggers/index.d.ts +3 -0
  249. package/dist/triggers/index.d.ts.map +1 -0
  250. package/dist/triggers/index.js +8 -0
  251. package/dist/triggers/index.js.map +1 -0
  252. package/dist/triggers/webhook-trigger.d.ts +51 -0
  253. package/dist/triggers/webhook-trigger.d.ts.map +1 -0
  254. package/dist/triggers/webhook-trigger.js +122 -0
  255. package/dist/triggers/webhook-trigger.js.map +1 -0
  256. package/dist/types/index.d.ts +5 -0
  257. package/dist/types/index.d.ts.map +1 -0
  258. package/dist/types/index.js +8 -0
  259. package/dist/types/index.js.map +1 -0
  260. package/dist/types/node.types.d.ts +313 -0
  261. package/dist/types/node.types.d.ts.map +1 -0
  262. package/dist/types/node.types.js +23 -0
  263. package/dist/types/node.types.js.map +1 -0
  264. package/dist/types/workflow.types.d.ts +153 -0
  265. package/dist/types/workflow.types.d.ts.map +1 -0
  266. package/dist/types/workflow.types.js +44 -0
  267. package/dist/types/workflow.types.js.map +1 -0
  268. package/dist/utils/n8n-converter.d.ts +52 -0
  269. package/dist/utils/n8n-converter.d.ts.map +1 -0
  270. package/dist/utils/n8n-converter.js +107 -0
  271. package/dist/utils/n8n-converter.js.map +1 -0
  272. package/dist/utils/n8n-node-map.d.ts +6 -0
  273. package/dist/utils/n8n-node-map.d.ts.map +1 -0
  274. package/dist/utils/n8n-node-map.js +59 -0
  275. package/dist/utils/n8n-node-map.js.map +1 -0
  276. package/dist/workflow.module.d.ts +40 -0
  277. package/dist/workflow.module.d.ts.map +1 -0
  278. package/dist/workflow.module.js +240 -0
  279. package/dist/workflow.module.js.map +1 -0
  280. package/package.json +97 -0
  281. package/web/components/ChatPanel.tsx +344 -0
  282. package/web/components/N8nIframe.tsx +119 -0
  283. package/web/components/SessionPanel.tsx +301 -0
  284. package/web/components/ToolPanel.tsx +404 -0
  285. package/web/components/WorkflowDiff.tsx +161 -0
  286. package/web/components/WorkflowGraph.tsx +158 -0
  287. package/web/components/WorkflowPreviewPanel.tsx +186 -0
  288. package/web/hooks/use-n8n-session.ts +46 -0
  289. package/web/index.ts +29 -0
  290. package/web/manifest.ts +16 -0
  291. package/web/messages/en-US.json +94 -0
  292. package/web/messages/zh-CN.json +94 -0
  293. package/web/pages/AiDesignPage.tsx +215 -0
  294. package/web/pages/CredentialsPage.tsx +7 -0
  295. package/web/pages/ExecutionsPage.tsx +7 -0
  296. package/web/pages/WorkflowsPage.tsx +7 -0
  297. package/web/services/workflow-ai-service.ts +173 -0
@@ -0,0 +1,344 @@
1
+ 'use client'
2
+
3
+ import { useState, useRef, useEffect, useCallback } from 'react'
4
+ import { useTranslations } from 'next-intl'
5
+ import { toast } from 'sonner'
6
+ import {
7
+ Send,
8
+ Sparkles,
9
+ Copy,
10
+ Download,
11
+ Loader2,
12
+ User,
13
+ Play,
14
+ GitCompare,
15
+ } from 'lucide-react'
16
+ import { Button } from '@octo-cyber/ui/components/ui/button'
17
+ import { Textarea } from '@octo-cyber/ui/components/ui/textarea'
18
+ import { Card, CardContent } from '@octo-cyber/ui/components/ui/card'
19
+ import { Badge } from '@octo-cyber/ui/components/ui/badge'
20
+ import {
21
+ workflowAiService,
22
+ type WorkflowDesignResponse,
23
+ type ConversationMessage,
24
+ } from '../services/workflow-ai-service'
25
+
26
+ export interface ChatMessage {
27
+ id: string
28
+ role: 'user' | 'assistant'
29
+ content: string
30
+ workflowJson?: string
31
+ suggestions?: string[]
32
+ durationMs?: number
33
+ }
34
+
35
+ interface ChatPanelProps {
36
+ sessionId: number | null
37
+ currentWorkflowJson: string | null
38
+ selectedTool: string
39
+ selectedModel: string
40
+ contextDocs: string[]
41
+ onWorkflowGenerated: (workflowJson?: string) => void
42
+ onApply: (json: string) => void
43
+ className?: string
44
+ }
45
+
46
+ function generateId(): string {
47
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
48
+ }
49
+
50
+ export function ChatPanel({
51
+ sessionId,
52
+ currentWorkflowJson,
53
+ selectedTool,
54
+ selectedModel,
55
+ contextDocs,
56
+ onWorkflowGenerated,
57
+ onApply,
58
+ className,
59
+ }: ChatPanelProps) {
60
+ const t = useTranslations('workflows.aiDesign')
61
+ const [messages, setMessages] = useState<ChatMessage[]>([])
62
+ const [input, setInput] = useState('')
63
+ const [isLoading, setIsLoading] = useState(false)
64
+ const messagesEndRef = useRef<HTMLDivElement>(null)
65
+ const textareaRef = useRef<HTMLTextAreaElement>(null)
66
+
67
+ const scrollToBottom = useCallback(() => {
68
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
69
+ }, [])
70
+
71
+ useEffect(() => { scrollToBottom() }, [messages, scrollToBottom])
72
+
73
+ // Reset messages when session changes
74
+ useEffect(() => { setMessages([]) }, [sessionId])
75
+
76
+ const lastWorkflowJson = messages
77
+ .filter((m) => m.role === 'assistant' && m.workflowJson)
78
+ .at(-1)?.workflowJson
79
+
80
+ async function handleSubmit() {
81
+ const prompt = input.trim()
82
+ if (!prompt || isLoading) return
83
+
84
+ const userMsg: ChatMessage = { id: generateId(), role: 'user', content: prompt }
85
+ setMessages((prev) => [...prev, userMsg])
86
+ setInput('')
87
+ setIsLoading(true)
88
+
89
+ try {
90
+ // Build conversation history
91
+ const conversationHistory: ConversationMessage[] = messages.map((m) => ({
92
+ role: m.role,
93
+ content: m.role === 'assistant' && m.workflowJson
94
+ ? `${m.content}\n\nGenerated workflow JSON:\n${m.workflowJson}`
95
+ : m.content,
96
+ }))
97
+
98
+ // Build context from selected docs
99
+ let context: string | undefined
100
+ if (contextDocs.length > 0) {
101
+ const docContents = await Promise.all(
102
+ contextDocs.map((moduleId) =>
103
+ workflowAiService.getApiDocContent(moduleId).catch(() => ''),
104
+ ),
105
+ )
106
+ context = docContents.filter(Boolean).join('\n\n---\n\n')
107
+ }
108
+
109
+ const res: WorkflowDesignResponse = await workflowAiService.design({
110
+ prompt,
111
+ currentWorkflowJson: lastWorkflowJson ?? currentWorkflowJson ?? undefined,
112
+ context,
113
+ tool: selectedTool,
114
+ model: selectedModel || undefined,
115
+ conversationHistory: conversationHistory.length > 0 ? conversationHistory : undefined,
116
+ sessionId: sessionId ?? undefined,
117
+ })
118
+
119
+ const assistantMsg: ChatMessage = {
120
+ id: generateId(),
121
+ role: 'assistant',
122
+ content: res.data?.explanation ?? res.error ?? t('unknownError'),
123
+ workflowJson: res.data?.workflowJson
124
+ ? (typeof res.data.workflowJson === 'string'
125
+ ? res.data.workflowJson
126
+ : JSON.stringify(res.data.workflowJson, null, 2))
127
+ : undefined,
128
+ suggestions: res.data?.suggestions,
129
+ durationMs: res.data?.durationMs,
130
+ }
131
+ setMessages((prev) => [...prev, assistantMsg])
132
+
133
+ if (res.success) {
134
+ onWorkflowGenerated(assistantMsg.workflowJson)
135
+ } else {
136
+ toast.error(res.error ?? t('requestFailed'))
137
+ }
138
+ } catch {
139
+ toast.error(t('requestFailed'))
140
+ setMessages((prev) => [
141
+ ...prev,
142
+ { id: generateId(), role: 'assistant', content: t('requestFailed') },
143
+ ])
144
+ } finally {
145
+ setIsLoading(false)
146
+ textareaRef.current?.focus()
147
+ }
148
+ }
149
+
150
+ function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
151
+ if (e.key === 'Enter' && !e.shiftKey) {
152
+ e.preventDefault()
153
+ handleSubmit()
154
+ }
155
+ }
156
+
157
+ return (
158
+ <div className={`flex flex-col ${className ?? ''}`}>
159
+ {/* Messages area */}
160
+ <div className="flex-1 overflow-y-auto px-4 py-4">
161
+ {messages.length === 0 ? (
162
+ <EmptyState t={t} />
163
+ ) : (
164
+ <div className="mx-auto max-w-3xl space-y-6">
165
+ {messages.map((msg) =>
166
+ msg.role === 'user' ? (
167
+ <UserMessage key={msg.id} msg={msg} />
168
+ ) : (
169
+ <AssistantMessage
170
+ key={msg.id}
171
+ msg={msg}
172
+ t={t}
173
+ onApply={onApply}
174
+ />
175
+ ),
176
+ )}
177
+ {isLoading && <ThinkingIndicator t={t} />}
178
+ <div ref={messagesEndRef} />
179
+ </div>
180
+ )}
181
+ </div>
182
+
183
+ {/* Input area */}
184
+ <div className="border-t border-border bg-background px-4 py-3">
185
+ <div className="mx-auto flex max-w-3xl gap-2">
186
+ <Textarea
187
+ ref={textareaRef}
188
+ value={input}
189
+ onChange={(e) => setInput(e.target.value)}
190
+ onKeyDown={handleKeyDown}
191
+ placeholder={t('inputPlaceholder')}
192
+ disabled={isLoading}
193
+ className="min-h-[44px] max-h-32 resize-none"
194
+ rows={1}
195
+ />
196
+ <Button
197
+ onClick={handleSubmit}
198
+ disabled={!input.trim() || isLoading}
199
+ size="icon"
200
+ className="h-11 w-11 shrink-0"
201
+ >
202
+ {isLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Send className="h-4 w-4" />}
203
+ </Button>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ )
208
+ }
209
+
210
+ // ─── Sub-components ──────────────────────────────────────────
211
+
212
+ function EmptyState({ t }: { t: (key: string) => string }) {
213
+ return (
214
+ <div className="flex h-full items-center justify-center">
215
+ <Card className="max-w-md border-dashed">
216
+ <CardContent className="flex flex-col items-center gap-3 py-8 text-center">
217
+ <Sparkles className="h-10 w-10 text-primary/60" />
218
+ <p className="text-sm text-muted-foreground">{t('placeholder')}</p>
219
+ </CardContent>
220
+ </Card>
221
+ </div>
222
+ )
223
+ }
224
+
225
+ function UserMessage({ msg }: { msg: ChatMessage }) {
226
+ return (
227
+ <div className="flex justify-end gap-3">
228
+ <div className="max-w-[80%] rounded-lg bg-primary px-4 py-2.5 text-primary-foreground">
229
+ <p className="whitespace-pre-wrap text-sm">{msg.content}</p>
230
+ </div>
231
+ <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted">
232
+ <User className="h-4 w-4" />
233
+ </div>
234
+ </div>
235
+ )
236
+ }
237
+
238
+ function AssistantMessage({
239
+ msg,
240
+ t,
241
+ onApply,
242
+ }: {
243
+ msg: ChatMessage
244
+ t: (key: string) => string
245
+ onApply: (json: string) => void
246
+ }) {
247
+ return (
248
+ <div className="flex gap-3">
249
+ <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
250
+ <Sparkles className="h-4 w-4" />
251
+ </div>
252
+ <div className="min-w-0 flex-1 space-y-2">
253
+ <p className="whitespace-pre-wrap text-sm leading-relaxed">{msg.content}</p>
254
+ {msg.workflowJson && (
255
+ <JsonPreview json={msg.workflowJson} t={t} onApply={onApply} />
256
+ )}
257
+ {msg.suggestions && msg.suggestions.length > 0 && (
258
+ <div className="mt-2 flex flex-wrap gap-1.5">
259
+ {msg.suggestions.map((s, i) => (
260
+ <Badge key={i} variant="secondary" className="text-xs">{s}</Badge>
261
+ ))}
262
+ </div>
263
+ )}
264
+ {msg.durationMs != null && (
265
+ <p className="text-xs text-muted-foreground">
266
+ {t('generatedIn')} {(msg.durationMs / 1000).toFixed(1)}s
267
+ </p>
268
+ )}
269
+ </div>
270
+ </div>
271
+ )
272
+ }
273
+
274
+ function JsonPreview({
275
+ json,
276
+ t,
277
+ onApply,
278
+ }: {
279
+ json: string
280
+ t: (key: string) => string
281
+ onApply: (json: string) => void
282
+ }) {
283
+ let formatted = json
284
+ try { formatted = JSON.stringify(JSON.parse(json), null, 2) } catch { /* use raw */ }
285
+
286
+ return (
287
+ <div className="mt-3 rounded-md border border-border bg-muted/50 dark:bg-zinc-900/50">
288
+ <div className="flex items-center justify-between border-b border-border px-3 py-2">
289
+ <span className="text-xs font-medium text-muted-foreground">{t('jsonPreview')}</span>
290
+ <div className="flex gap-1">
291
+ <Button
292
+ variant="ghost"
293
+ size="sm"
294
+ className="h-7 px-2 text-xs"
295
+ onClick={() => onApply(formatted)}
296
+ >
297
+ <Play className="mr-1 h-3 w-3" />
298
+ {t('apply')}
299
+ </Button>
300
+ <Button
301
+ variant="ghost"
302
+ size="sm"
303
+ className="h-7 px-2"
304
+ onClick={() => {
305
+ navigator.clipboard.writeText(formatted).then(() => toast.success(t('copied')))
306
+ }}
307
+ >
308
+ <Copy className="h-3.5 w-3.5" />
309
+ </Button>
310
+ <Button
311
+ variant="ghost"
312
+ size="sm"
313
+ className="h-7 px-2"
314
+ onClick={() => {
315
+ const blob = new Blob([formatted], { type: 'application/json' })
316
+ const url = URL.createObjectURL(blob)
317
+ const a = document.createElement('a')
318
+ a.href = url
319
+ a.download = `workflow-${Date.now()}.json`
320
+ a.click()
321
+ URL.revokeObjectURL(url)
322
+ }}
323
+ >
324
+ <Download className="h-3.5 w-3.5" />
325
+ </Button>
326
+ </div>
327
+ </div>
328
+ <pre className="max-h-64 overflow-auto p-3 text-xs leading-relaxed">
329
+ <code>{formatted}</code>
330
+ </pre>
331
+ </div>
332
+ )
333
+ }
334
+
335
+ function ThinkingIndicator({ t }: { t: (key: string) => string }) {
336
+ return (
337
+ <div className="flex gap-3">
338
+ <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
339
+ <Loader2 className="h-4 w-4 animate-spin" />
340
+ </div>
341
+ <p className="text-sm text-muted-foreground">{t('thinking')}</p>
342
+ </div>
343
+ )
344
+ }
@@ -0,0 +1,119 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useEffect, useRef, useState } from 'react'
4
+ import { useTranslations } from 'next-intl'
5
+ import { Loader2, AlertCircle, ServerOff, LogIn, WifiOff } from 'lucide-react'
6
+ import { Button } from '@octo-cyber/ui/components/ui/button'
7
+ import { useN8nSession, type N8nErrorType } from '../hooks/use-n8n-session'
8
+
9
+ const errorIcons: Record<N8nErrorType, typeof AlertCircle> = {
10
+ auth: LogIn,
11
+ unavailable: ServerOff,
12
+ network: WifiOff,
13
+ unknown: AlertCircle,
14
+ }
15
+
16
+ /** CSS injected into the n8n iframe to fix layout issues */
17
+ const N8N_HEIGHT_CSS = `
18
+ html, body { height: 100% !important; }
19
+ #app { height: 100% !important; }
20
+ .app-grid { height: 100% !important; }
21
+ `
22
+
23
+ interface N8nIframeProps {
24
+ /** n8n sub-path, e.g. "" for home, "credentials", "executions" */
25
+ subPath?: string
26
+ }
27
+
28
+ export function N8nIframe({ subPath = '' }: N8nIframeProps) {
29
+ const t = useTranslations('workflows')
30
+ const tc = useTranslations('common')
31
+ const iframeRef = useRef<HTMLIFrameElement>(null)
32
+ const { ensureSession, isReady, error } = useN8nSession()
33
+ const [isLoading, setIsLoading] = useState(true)
34
+
35
+ useEffect(() => {
36
+ ensureSession().finally(() => setIsLoading(false))
37
+ }, [ensureSession])
38
+
39
+ const handleIframeLoad = useCallback(() => {
40
+ const iframe = iframeRef.current
41
+ if (!iframe) return
42
+ try {
43
+ const doc = iframe.contentDocument
44
+ if (!doc) return
45
+ let style = doc.querySelector('#octo-height-fix') as HTMLStyleElement | null
46
+ if (!style) {
47
+ style = doc.createElement('style')
48
+ style.id = 'octo-height-fix'
49
+ doc.head.appendChild(style)
50
+ }
51
+ style.textContent = N8N_HEIGHT_CSS
52
+ } catch {
53
+ // Cross-origin restriction — CSS injection via proxy should handle this
54
+ }
55
+ }, [])
56
+
57
+ if (isLoading) {
58
+ return (
59
+ <div className="flex h-full flex-col items-center justify-center gap-3">
60
+ <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
61
+ <p className="text-sm text-muted-foreground">{t('loading')}</p>
62
+ </div>
63
+ )
64
+ }
65
+
66
+ if (error) {
67
+ const Icon = errorIcons[error.type]
68
+
69
+ return (
70
+ <div className="flex h-full flex-col items-center justify-center gap-4">
71
+ <div className="flex h-16 w-16 items-center justify-center rounded-full bg-muted">
72
+ <Icon className="h-8 w-8 text-muted-foreground" />
73
+ </div>
74
+ <div className="text-center">
75
+ <h3 className="text-lg font-semibold">{t(`errors.${error.type}.title`)}</h3>
76
+ <p className="mt-1 max-w-sm text-sm text-muted-foreground">
77
+ {t(error.messageKey, error.messageParams)}
78
+ </p>
79
+ </div>
80
+ {error.type === 'unavailable' && (
81
+ <div className="max-w-sm rounded-md border bg-muted/50 p-3 text-xs text-muted-foreground">
82
+ <p className="font-medium">{t('errors.unavailable.troubleshootTitle')}</p>
83
+ <ol className="mt-1 list-inside list-decimal space-y-1">
84
+ <li>{t('errors.unavailable.step1')}</li>
85
+ <li>{t('errors.unavailable.step2')}</li>
86
+ <li>{t('errors.unavailable.step3')}</li>
87
+ </ol>
88
+ </div>
89
+ )}
90
+ <Button
91
+ onClick={() => {
92
+ setIsLoading(true)
93
+ ensureSession().finally(() => setIsLoading(false))
94
+ }}
95
+ >
96
+ {tc('retry')}
97
+ </Button>
98
+ </div>
99
+ )
100
+ }
101
+
102
+ if (!isReady) {
103
+ return null
104
+ }
105
+
106
+ const src = subPath ? `/n8n/${subPath}` : '/n8n/'
107
+
108
+ return (
109
+ <div className="-mx-4 -mt-4 -mb-20 md:-mb-4 h-svh w-[calc(100%+2rem)]">
110
+ <iframe
111
+ ref={iframeRef}
112
+ src={src}
113
+ className="h-full w-full border-0"
114
+ allow="clipboard-read; clipboard-write"
115
+ onLoad={handleIframeLoad}
116
+ />
117
+ </div>
118
+ )
119
+ }