nitrostack 1.0.72 → 1.0.74

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 (370) hide show
  1. package/dist/auth/__tests__/api-key.test.d.ts +2 -0
  2. package/dist/auth/__tests__/api-key.test.d.ts.map +1 -0
  3. package/dist/auth/__tests__/api-key.test.js +136 -0
  4. package/dist/auth/__tests__/api-key.test.js.map +1 -0
  5. package/dist/auth/__tests__/middleware.test.d.ts +2 -0
  6. package/dist/auth/__tests__/middleware.test.d.ts.map +1 -0
  7. package/dist/auth/__tests__/middleware.test.js +186 -0
  8. package/dist/auth/__tests__/middleware.test.js.map +1 -0
  9. package/dist/auth/__tests__/pkce.test.d.ts +2 -0
  10. package/dist/auth/__tests__/pkce.test.d.ts.map +1 -0
  11. package/dist/auth/__tests__/pkce.test.js +61 -0
  12. package/dist/auth/__tests__/pkce.test.js.map +1 -0
  13. package/dist/auth/__tests__/secure-secret.test.d.ts +2 -0
  14. package/dist/auth/__tests__/secure-secret.test.d.ts.map +1 -0
  15. package/dist/auth/__tests__/secure-secret.test.js +42 -0
  16. package/dist/auth/__tests__/secure-secret.test.js.map +1 -0
  17. package/dist/auth/__tests__/simple-jwt.test.d.ts +2 -0
  18. package/dist/auth/__tests__/simple-jwt.test.d.ts.map +1 -0
  19. package/dist/auth/__tests__/simple-jwt.test.js +68 -0
  20. package/dist/auth/__tests__/simple-jwt.test.js.map +1 -0
  21. package/dist/auth/__tests__/token-store.test.d.ts +2 -0
  22. package/dist/auth/__tests__/token-store.test.d.ts.map +1 -0
  23. package/dist/auth/__tests__/token-store.test.js +79 -0
  24. package/dist/auth/__tests__/token-store.test.js.map +1 -0
  25. package/dist/auth/__tests__/token-validation.test.d.ts +2 -0
  26. package/dist/auth/__tests__/token-validation.test.d.ts.map +1 -0
  27. package/dist/auth/__tests__/token-validation.test.js +23 -0
  28. package/dist/auth/__tests__/token-validation.test.js.map +1 -0
  29. package/dist/auth/api-key.js.map +1 -1
  30. package/dist/auth/client.js.map +1 -1
  31. package/dist/auth/index.d.ts +2 -1
  32. package/dist/auth/index.d.ts.map +1 -1
  33. package/dist/auth/index.js +3 -0
  34. package/dist/auth/index.js.map +1 -1
  35. package/dist/auth/middleware.d.ts +1 -1
  36. package/dist/auth/middleware.d.ts.map +1 -1
  37. package/dist/auth/middleware.js.map +1 -1
  38. package/dist/auth/secure-secret.d.ts +136 -0
  39. package/dist/auth/secure-secret.d.ts.map +1 -0
  40. package/dist/auth/secure-secret.js +182 -0
  41. package/dist/auth/secure-secret.js.map +1 -0
  42. package/dist/auth/server-metadata.d.ts.map +1 -1
  43. package/dist/auth/server-metadata.js.map +1 -1
  44. package/dist/auth/simple-jwt.d.ts +100 -14
  45. package/dist/auth/simple-jwt.d.ts.map +1 -1
  46. package/dist/auth/simple-jwt.js +19 -9
  47. package/dist/auth/simple-jwt.js.map +1 -1
  48. package/dist/auth/token-store.js +1 -1
  49. package/dist/auth/token-store.js.map +1 -1
  50. package/dist/auth/token-validation.js +1 -1
  51. package/dist/auth/token-validation.js.map +1 -1
  52. package/dist/cli/__tests__/cli-dev.test.d.ts +2 -0
  53. package/dist/cli/__tests__/cli-dev.test.d.ts.map +1 -0
  54. package/dist/cli/__tests__/cli-dev.test.js +83 -0
  55. package/dist/cli/__tests__/cli-dev.test.js.map +1 -0
  56. package/dist/cli/__tests__/cli-start.test.d.ts +2 -0
  57. package/dist/cli/__tests__/cli-start.test.d.ts.map +1 -0
  58. package/dist/cli/__tests__/cli-start.test.js +61 -0
  59. package/dist/cli/__tests__/cli-start.test.js.map +1 -0
  60. package/dist/cli/__tests__/cli.test.d.ts +2 -0
  61. package/dist/cli/__tests__/cli.test.d.ts.map +1 -0
  62. package/dist/cli/__tests__/cli.test.js +73 -0
  63. package/dist/cli/__tests__/cli.test.js.map +1 -0
  64. package/dist/cli/__tests__/commands.test.d.ts +2 -0
  65. package/dist/cli/__tests__/commands.test.d.ts.map +1 -0
  66. package/dist/cli/__tests__/commands.test.js +64 -0
  67. package/dist/cli/__tests__/commands.test.js.map +1 -0
  68. package/dist/cli/commands/build.js +1 -1
  69. package/dist/cli/commands/build.js.map +1 -1
  70. package/dist/cli/commands/generate-types.js +12 -12
  71. package/dist/cli/commands/generate-types.js.map +1 -1
  72. package/dist/cli/commands/generate.d.ts +8 -1
  73. package/dist/cli/commands/generate.d.ts.map +1 -1
  74. package/dist/cli/commands/generate.js +13 -12
  75. package/dist/cli/commands/generate.js.map +1 -1
  76. package/dist/cli/commands/init.js +1 -1
  77. package/dist/cli/commands/init.js.map +1 -1
  78. package/dist/cli/commands/upgrade.d.ts +10 -0
  79. package/dist/cli/commands/upgrade.d.ts.map +1 -0
  80. package/dist/cli/commands/upgrade.js +221 -0
  81. package/dist/cli/commands/upgrade.js.map +1 -0
  82. package/dist/cli/index.d.ts +2 -1
  83. package/dist/cli/index.d.ts.map +1 -1
  84. package/dist/cli/index.js +69 -39
  85. package/dist/cli/index.js.map +1 -1
  86. package/dist/core/__tests__/core-modules.test.d.ts +2 -0
  87. package/dist/core/__tests__/core-modules.test.d.ts.map +1 -0
  88. package/dist/core/__tests__/core-modules.test.js +36 -0
  89. package/dist/core/__tests__/core-modules.test.js.map +1 -0
  90. package/dist/core/__tests__/models.test.d.ts +2 -0
  91. package/dist/core/__tests__/models.test.d.ts.map +1 -0
  92. package/dist/core/__tests__/models.test.js +142 -0
  93. package/dist/core/__tests__/models.test.js.map +1 -0
  94. package/dist/core/__tests__/module.test.d.ts +2 -0
  95. package/dist/core/__tests__/module.test.d.ts.map +1 -0
  96. package/dist/core/__tests__/module.test.js +60 -0
  97. package/dist/core/__tests__/module.test.js.map +1 -0
  98. package/dist/core/__tests__/server.test.d.ts +2 -0
  99. package/dist/core/__tests__/server.test.d.ts.map +1 -0
  100. package/dist/core/__tests__/server.test.js +120 -0
  101. package/dist/core/__tests__/server.test.js.map +1 -0
  102. package/dist/core/__tests__/transports.test.d.ts +2 -0
  103. package/dist/core/__tests__/transports.test.d.ts.map +1 -0
  104. package/dist/core/__tests__/transports.test.js +12 -0
  105. package/dist/core/__tests__/transports.test.js.map +1 -0
  106. package/dist/core/app-decorator.d.ts +4 -3
  107. package/dist/core/app-decorator.d.ts.map +1 -1
  108. package/dist/core/app-decorator.js +67 -28
  109. package/dist/core/app-decorator.js.map +1 -1
  110. package/dist/core/builders.d.ts +19 -7
  111. package/dist/core/builders.d.ts.map +1 -1
  112. package/dist/core/builders.js +15 -8
  113. package/dist/core/builders.js.map +1 -1
  114. package/dist/core/component.d.ts +8 -8
  115. package/dist/core/component.d.ts.map +1 -1
  116. package/dist/core/component.js +3 -2
  117. package/dist/core/component.js.map +1 -1
  118. package/dist/core/config-module.d.ts +11 -4
  119. package/dist/core/config-module.d.ts.map +1 -1
  120. package/dist/core/config-module.js +1 -1
  121. package/dist/core/config-module.js.map +1 -1
  122. package/dist/core/decorators/__tests__/decorators.test.d.ts +2 -0
  123. package/dist/core/decorators/__tests__/decorators.test.d.ts.map +1 -0
  124. package/dist/core/decorators/__tests__/decorators.test.js +103 -0
  125. package/dist/core/decorators/__tests__/decorators.test.js.map +1 -0
  126. package/dist/core/decorators/__tests__/extended-decorators.test.d.ts +2 -0
  127. package/dist/core/decorators/__tests__/extended-decorators.test.d.ts.map +1 -0
  128. package/dist/core/decorators/__tests__/extended-decorators.test.js +194 -0
  129. package/dist/core/decorators/__tests__/extended-decorators.test.js.map +1 -0
  130. package/dist/core/decorators/cache.decorator.d.ts +9 -9
  131. package/dist/core/decorators/cache.decorator.d.ts.map +1 -1
  132. package/dist/core/decorators/cache.decorator.js +3 -3
  133. package/dist/core/decorators/cache.decorator.js.map +1 -1
  134. package/dist/core/decorators/health-check.decorator.d.ts +3 -3
  135. package/dist/core/decorators/health-check.decorator.d.ts.map +1 -1
  136. package/dist/core/decorators/health-check.decorator.js +2 -2
  137. package/dist/core/decorators/health-check.decorator.js.map +1 -1
  138. package/dist/core/decorators/rate-limit.decorator.d.ts +5 -4
  139. package/dist/core/decorators/rate-limit.decorator.d.ts.map +1 -1
  140. package/dist/core/decorators/rate-limit.decorator.js +3 -3
  141. package/dist/core/decorators/rate-limit.decorator.js.map +1 -1
  142. package/dist/core/decorators.d.ts +47 -29
  143. package/dist/core/decorators.d.ts.map +1 -1
  144. package/dist/core/decorators.js +9 -9
  145. package/dist/core/decorators.js.map +1 -1
  146. package/dist/core/di/__tests__/container.test.d.ts +2 -0
  147. package/dist/core/di/__tests__/container.test.d.ts.map +1 -0
  148. package/dist/core/di/__tests__/container.test.js +88 -0
  149. package/dist/core/di/__tests__/container.test.js.map +1 -0
  150. package/dist/core/di/container.d.ts +21 -4
  151. package/dist/core/di/container.d.ts.map +1 -1
  152. package/dist/core/di/container.js +11 -7
  153. package/dist/core/di/container.js.map +1 -1
  154. package/dist/core/di/injectable.decorator.d.ts +5 -3
  155. package/dist/core/di/injectable.decorator.d.ts.map +1 -1
  156. package/dist/core/di/injectable.decorator.js.map +1 -1
  157. package/dist/core/errors.d.ts +4 -4
  158. package/dist/core/errors.d.ts.map +1 -1
  159. package/dist/core/errors.js.map +1 -1
  160. package/dist/core/events/__tests__/events.test.d.ts +2 -0
  161. package/dist/core/events/__tests__/events.test.d.ts.map +1 -0
  162. package/dist/core/events/__tests__/events.test.js +177 -0
  163. package/dist/core/events/__tests__/events.test.js.map +1 -0
  164. package/dist/core/events/event-emitter.d.ts +3 -3
  165. package/dist/core/events/event-emitter.d.ts.map +1 -1
  166. package/dist/core/events/event-emitter.js.map +1 -1
  167. package/dist/core/events/event.decorator.d.ts +5 -5
  168. package/dist/core/events/event.decorator.d.ts.map +1 -1
  169. package/dist/core/events/event.decorator.js +10 -6
  170. package/dist/core/events/event.decorator.js.map +1 -1
  171. package/dist/core/events/log-emitter.d.ts +7 -1
  172. package/dist/core/events/log-emitter.d.ts.map +1 -1
  173. package/dist/core/events/log-emitter.js.map +1 -1
  174. package/dist/core/filters/__tests__/filters.test.d.ts +2 -0
  175. package/dist/core/filters/__tests__/filters.test.d.ts.map +1 -0
  176. package/dist/core/filters/__tests__/filters.test.js +72 -0
  177. package/dist/core/filters/__tests__/filters.test.js.map +1 -0
  178. package/dist/core/filters/exception-filter.decorator.d.ts +5 -5
  179. package/dist/core/filters/exception-filter.decorator.d.ts.map +1 -1
  180. package/dist/core/filters/exception-filter.decorator.js +3 -3
  181. package/dist/core/filters/exception-filter.decorator.js.map +1 -1
  182. package/dist/core/filters/exception-filter.interface.d.ts +14 -5
  183. package/dist/core/filters/exception-filter.interface.d.ts.map +1 -1
  184. package/dist/core/guards/__tests__/guards.test.d.ts +2 -0
  185. package/dist/core/guards/__tests__/guards.test.d.ts.map +1 -0
  186. package/dist/core/guards/__tests__/guards.test.js +55 -0
  187. package/dist/core/guards/__tests__/guards.test.js.map +1 -0
  188. package/dist/core/guards/apikey.guard.d.ts +1 -1
  189. package/dist/core/guards/apikey.guard.d.ts.map +1 -1
  190. package/dist/core/guards/guard.interface.d.ts +1 -1
  191. package/dist/core/guards/guard.interface.d.ts.map +1 -1
  192. package/dist/core/guards/jwt.guard.d.ts +1 -1
  193. package/dist/core/guards/jwt.guard.d.ts.map +1 -1
  194. package/dist/core/guards/oauth.guard.d.ts +1 -1
  195. package/dist/core/guards/oauth.guard.d.ts.map +1 -1
  196. package/dist/core/guards/use-guards.decorator.d.ts +3 -3
  197. package/dist/core/guards/use-guards.decorator.d.ts.map +1 -1
  198. package/dist/core/guards/use-guards.decorator.js +1 -1
  199. package/dist/core/guards/use-guards.decorator.js.map +1 -1
  200. package/dist/core/health/__tests__/health.test.d.ts +2 -0
  201. package/dist/core/health/__tests__/health.test.d.ts.map +1 -0
  202. package/dist/core/health/__tests__/health.test.js +31 -0
  203. package/dist/core/health/__tests__/health.test.js.map +1 -0
  204. package/dist/core/index.d.ts +2 -2
  205. package/dist/core/index.d.ts.map +1 -1
  206. package/dist/core/index.js.map +1 -1
  207. package/dist/core/interceptors/__tests__/interceptors.test.d.ts +2 -0
  208. package/dist/core/interceptors/__tests__/interceptors.test.d.ts.map +1 -0
  209. package/dist/core/interceptors/__tests__/interceptors.test.js +52 -0
  210. package/dist/core/interceptors/__tests__/interceptors.test.js.map +1 -0
  211. package/dist/core/interceptors/interceptor.decorator.d.ts +4 -4
  212. package/dist/core/interceptors/interceptor.decorator.d.ts.map +1 -1
  213. package/dist/core/interceptors/interceptor.decorator.js +2 -2
  214. package/dist/core/interceptors/interceptor.decorator.js.map +1 -1
  215. package/dist/core/interceptors/interceptor.interface.d.ts +3 -3
  216. package/dist/core/interceptors/interceptor.interface.d.ts.map +1 -1
  217. package/dist/core/logger.d.ts.map +1 -1
  218. package/dist/core/logger.js.map +1 -1
  219. package/dist/core/middleware/__tests__/middleware.test.d.ts +2 -0
  220. package/dist/core/middleware/__tests__/middleware.test.d.ts.map +1 -0
  221. package/dist/core/middleware/__tests__/middleware.test.js +105 -0
  222. package/dist/core/middleware/__tests__/middleware.test.js.map +1 -0
  223. package/dist/core/middleware/middleware.decorator.d.ts +4 -4
  224. package/dist/core/middleware/middleware.decorator.d.ts.map +1 -1
  225. package/dist/core/middleware/middleware.decorator.js +2 -2
  226. package/dist/core/middleware/middleware.decorator.js.map +1 -1
  227. package/dist/core/middleware/middleware.interface.d.ts +3 -3
  228. package/dist/core/middleware/middleware.interface.d.ts.map +1 -1
  229. package/dist/core/module.d.ts +33 -14
  230. package/dist/core/module.d.ts.map +1 -1
  231. package/dist/core/module.js +11 -6
  232. package/dist/core/module.js.map +1 -1
  233. package/dist/core/oauth-module.d.ts +9 -3
  234. package/dist/core/oauth-module.d.ts.map +1 -1
  235. package/dist/core/oauth-module.js +4 -3
  236. package/dist/core/oauth-module.js.map +1 -1
  237. package/dist/core/pipes/__tests__/pipes.test.d.ts +2 -0
  238. package/dist/core/pipes/__tests__/pipes.test.d.ts.map +1 -0
  239. package/dist/core/pipes/__tests__/pipes.test.js +52 -0
  240. package/dist/core/pipes/__tests__/pipes.test.js.map +1 -0
  241. package/dist/core/pipes/pipe.decorator.d.ts +14 -5
  242. package/dist/core/pipes/pipe.decorator.d.ts.map +1 -1
  243. package/dist/core/pipes/pipe.decorator.js +2 -2
  244. package/dist/core/pipes/pipe.decorator.js.map +1 -1
  245. package/dist/core/pipes/pipe.interface.d.ts +9 -4
  246. package/dist/core/pipes/pipe.interface.d.ts.map +1 -1
  247. package/dist/core/prompt.d.ts +13 -4
  248. package/dist/core/prompt.d.ts.map +1 -1
  249. package/dist/core/prompt.js +2 -2
  250. package/dist/core/prompt.js.map +1 -1
  251. package/dist/core/resource.d.ts +7 -2
  252. package/dist/core/resource.d.ts.map +1 -1
  253. package/dist/core/resource.js +2 -2
  254. package/dist/core/resource.js.map +1 -1
  255. package/dist/core/server.d.ts +49 -3
  256. package/dist/core/server.d.ts.map +1 -1
  257. package/dist/core/server.js +61 -34
  258. package/dist/core/server.js.map +1 -1
  259. package/dist/core/tool.d.ts +44 -16
  260. package/dist/core/tool.d.ts.map +1 -1
  261. package/dist/core/tool.js +19 -6
  262. package/dist/core/tool.js.map +1 -1
  263. package/dist/core/transports/__tests__/transports.test.d.ts +2 -0
  264. package/dist/core/transports/__tests__/transports.test.d.ts.map +1 -0
  265. package/dist/core/transports/__tests__/transports.test.js +249 -0
  266. package/dist/core/transports/__tests__/transports.test.js.map +1 -0
  267. package/dist/core/transports/discovery-http-server.d.ts +7 -1
  268. package/dist/core/transports/discovery-http-server.d.ts.map +1 -1
  269. package/dist/core/transports/discovery-http-server.js.map +1 -1
  270. package/dist/core/transports/http-server.d.ts +2 -2
  271. package/dist/core/transports/http-server.d.ts.map +1 -1
  272. package/dist/core/transports/http-server.js +1 -1
  273. package/dist/core/transports/http-server.js.map +1 -1
  274. package/dist/core/transports/streamable-http.d.ts +4 -4
  275. package/dist/core/transports/streamable-http.d.ts.map +1 -1
  276. package/dist/core/transports/streamable-http.js +1 -1
  277. package/dist/core/transports/streamable-http.js.map +1 -1
  278. package/dist/core/types.d.ts +87 -15
  279. package/dist/core/types.d.ts.map +1 -1
  280. package/dist/core/widgets/__tests__/registry.test.d.ts +2 -0
  281. package/dist/core/widgets/__tests__/registry.test.d.ts.map +1 -0
  282. package/dist/core/widgets/__tests__/registry.test.js +69 -0
  283. package/dist/core/widgets/__tests__/registry.test.js.map +1 -0
  284. package/dist/core/widgets/widget-registry.d.ts +2 -2
  285. package/dist/core/widgets/widget-registry.d.ts.map +1 -1
  286. package/dist/core/widgets/widget-registry.js +1 -1
  287. package/dist/core/widgets/widget-registry.js.map +1 -1
  288. package/dist/testing/__tests__/testing.test.d.ts +2 -0
  289. package/dist/testing/__tests__/testing.test.d.ts.map +1 -0
  290. package/dist/testing/__tests__/testing.test.js +167 -0
  291. package/dist/testing/__tests__/testing.test.js.map +1 -0
  292. package/dist/testing/index.d.ts +40 -19
  293. package/dist/testing/index.d.ts.map +1 -1
  294. package/dist/testing/index.js +5 -8
  295. package/dist/testing/index.js.map +1 -1
  296. package/dist/ui-next/__tests__/ui-next.test.d.ts +2 -0
  297. package/dist/ui-next/__tests__/ui-next.test.d.ts.map +1 -0
  298. package/dist/ui-next/__tests__/ui-next.test.js +74 -0
  299. package/dist/ui-next/__tests__/ui-next.test.js.map +1 -0
  300. package/dist/ui-next/index.d.ts +1 -1
  301. package/dist/ui-next/index.d.ts.map +1 -1
  302. package/dist/ui-next/index.js.map +1 -1
  303. package/dist/widgets/__tests__/utils.test.d.ts +2 -0
  304. package/dist/widgets/__tests__/utils.test.d.ts.map +1 -0
  305. package/dist/widgets/__tests__/utils.test.js +80 -0
  306. package/dist/widgets/__tests__/utils.test.js.map +1 -0
  307. package/dist/widgets/__tests__/widgets.test.d.ts +2 -0
  308. package/dist/widgets/__tests__/widgets.test.d.ts.map +1 -0
  309. package/dist/widgets/__tests__/widgets.test.js +245 -0
  310. package/dist/widgets/__tests__/widgets.test.js.map +1 -0
  311. package/dist/widgets/hooks/useWidgetSDK.d.ts +5 -5
  312. package/dist/widgets/runtime/WidgetLayout.js.map +1 -1
  313. package/dist/widgets/sdk.d.ts +5 -5
  314. package/dist/widgets/sdk.d.ts.map +1 -1
  315. package/dist/widgets/sdk.js.map +1 -1
  316. package/package.json +1 -1
  317. package/src/studio/app/api/auth/fetch-metadata/route.ts +3 -2
  318. package/src/studio/app/api/auth/register-client/route.ts +3 -2
  319. package/src/studio/app/api/chat/route.ts +31 -17
  320. package/src/studio/app/api/health/checks/route.ts +5 -4
  321. package/src/studio/app/api/init/route.ts +3 -2
  322. package/src/studio/app/api/ping/route.ts +3 -2
  323. package/src/studio/app/api/prompts/[name]/route.ts +4 -3
  324. package/src/studio/app/api/prompts/route.ts +3 -2
  325. package/src/studio/app/api/resources/[...uri]/route.ts +3 -2
  326. package/src/studio/app/api/resources/route.ts +3 -2
  327. package/src/studio/app/api/roots/route.ts +3 -2
  328. package/src/studio/app/api/sampling/route.ts +3 -2
  329. package/src/studio/app/api/tools/[name]/call/route.ts +3 -2
  330. package/src/studio/app/api/tools/route.ts +4 -3
  331. package/src/studio/app/api/widget-examples/route.ts +5 -4
  332. package/src/studio/app/auth/callback/page.tsx +3 -2
  333. package/src/studio/app/chat/page.tsx +481 -105
  334. package/src/studio/app/health/page.tsx +1 -1
  335. package/src/studio/app/logs/page.tsx +2 -2
  336. package/src/studio/app/page.tsx +5 -5
  337. package/src/studio/app/prompts/page.tsx +2 -2
  338. package/src/studio/app/settings/page.tsx +3 -2
  339. package/src/studio/app/tools/page.tsx +3 -3
  340. package/src/studio/components/LogMessage.tsx +1 -1
  341. package/src/studio/components/MarkdownRenderer.tsx +245 -348
  342. package/src/studio/components/Sidebar.tsx +18 -3
  343. package/src/studio/components/VoiceOrbOverlay.tsx +12 -6
  344. package/src/studio/components/WidgetErrorBoundary.tsx +49 -0
  345. package/src/studio/components/WidgetRenderer.tsx +168 -215
  346. package/src/studio/components/ops/OpsCanvas.tsx +748 -0
  347. package/src/studio/components/ops/OpsNodeDetailPanel.tsx +150 -0
  348. package/src/studio/components/ops/OpsSummaryBar.tsx +90 -0
  349. package/src/studio/components/ops/index.ts +6 -0
  350. package/src/studio/components/ops/nodes/BaseNode.tsx +65 -0
  351. package/src/studio/components/ops/nodes/LLMCallNode.tsx +34 -0
  352. package/src/studio/components/ops/nodes/LLMResponseNode.tsx +33 -0
  353. package/src/studio/components/ops/nodes/ToolCallNode.tsx +30 -0
  354. package/src/studio/components/ops/nodes/ToolResultNode.tsx +43 -0
  355. package/src/studio/components/ops/nodes/UserPromptNode.tsx +34 -0
  356. package/src/studio/components/ops/nodes/WidgetRenderNode.tsx +23 -0
  357. package/src/studio/components/ops/nodes/index.ts +9 -0
  358. package/src/studio/components/tools/ToolsCanvas.tsx +2 -2
  359. package/src/studio/lib/api.ts +61 -42
  360. package/src/studio/lib/http-client-transport.ts +2 -2
  361. package/src/studio/lib/llm-service.ts +126 -47
  362. package/src/studio/lib/mcp-client.ts +9 -6
  363. package/src/studio/lib/ops-store.ts +427 -0
  364. package/src/studio/lib/ops-tracker.ts +416 -0
  365. package/src/studio/lib/ops-types.ts +164 -0
  366. package/src/studio/lib/store.ts +8 -11
  367. package/src/studio/lib/types.ts +228 -38
  368. package/src/studio/lib/widget-loader.ts +2 -2
  369. package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +19 -22
  370. package/dist/cli/build-widgets.mjs +0 -165
@@ -0,0 +1,748 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useMemo, useState, useEffect, useRef } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import ReactFlow, {
6
+ Background,
7
+ Controls,
8
+ useNodesState,
9
+ useEdgesState,
10
+ Panel,
11
+ BackgroundVariant,
12
+ } from 'reactflow';
13
+ import type { Node, Edge, NodeTypes } from 'reactflow';
14
+ import 'reactflow/dist/style.css';
15
+
16
+ import { useOpsStore } from '@/lib/ops-store';
17
+ import type { OpsNode, OpsEdge, OpsNodeType, OpsTurn } from '@/lib/ops-types';
18
+ import { OPS_NODE_HEX_COLORS, OPS_NODE_LABELS } from '@/lib/ops-types';
19
+ import {
20
+ UserPromptNode,
21
+ LLMCallNode,
22
+ ToolCallNode,
23
+ ToolResultNode,
24
+ WidgetRenderNode,
25
+ LLMResponseNode,
26
+ } from './nodes';
27
+ import { OpsSummaryBar } from './OpsSummaryBar';
28
+ import { OpsNodeDetailPanel } from './OpsNodeDetailPanel';
29
+ import {
30
+ ArrowsPointingOutIcon,
31
+ XMarkIcon,
32
+ PlayIcon,
33
+ PauseIcon,
34
+ ArrowPathIcon,
35
+ Squares2X2Icon,
36
+ BoltIcon,
37
+ } from '@heroicons/react/24/outline';
38
+
39
+ // Define custom node types
40
+ const nodeTypes: NodeTypes = {
41
+ user_prompt: UserPromptNode,
42
+ llm_call: LLMCallNode,
43
+ tool_call: ToolCallNode,
44
+ tool_result: ToolResultNode,
45
+ widget_render: WidgetRenderNode,
46
+ llm_response: LLMResponseNode,
47
+ };
48
+
49
+ // Convert OpsNode to React Flow Node - HORIZONTAL LAYOUT
50
+ function convertToFlowNode(opsNode: OpsNode, index: number, isAnimating: boolean = false): Node {
51
+ const baseX = 60;
52
+ const baseY = 120;
53
+ const horizontalSpacing = 200;
54
+
55
+ const position = opsNode.position || {
56
+ x: baseX + (index * horizontalSpacing),
57
+ y: baseY,
58
+ };
59
+
60
+ return {
61
+ id: opsNode.id,
62
+ type: opsNode.type,
63
+ position,
64
+ data: {
65
+ ...opsNode.data,
66
+ duration: opsNode.duration,
67
+ isAnimating,
68
+ },
69
+ className: isAnimating ? 'ops-node-animate' : '',
70
+ };
71
+ }
72
+
73
+ // Convert OpsEdge to React Flow Edge
74
+ function convertToFlowEdge(opsEdge: OpsEdge, isAnimating: boolean = false): Edge {
75
+ return {
76
+ id: opsEdge.id,
77
+ source: opsEdge.source,
78
+ target: opsEdge.target,
79
+ animated: isAnimating,
80
+ style: {
81
+ strokeWidth: 1.5,
82
+ stroke: isAnimating ? '#6366f1' : '#3f3f46',
83
+ },
84
+ type: 'smoothstep',
85
+ };
86
+ }
87
+
88
+ // Turn selector - minimal pill style
89
+ function TurnSelector({
90
+ turns,
91
+ selectedTurnId,
92
+ onSelect,
93
+ disabled,
94
+ }: {
95
+ turns: OpsTurn[];
96
+ selectedTurnId: string | null;
97
+ onSelect: (turnId: string | null) => void;
98
+ disabled?: boolean;
99
+ }) {
100
+ if (turns.length === 0) return null;
101
+
102
+ return (
103
+ <div className={`flex items-center gap-1 ${disabled ? 'opacity-40 pointer-events-none' : ''}`}>
104
+ <button
105
+ onClick={() => onSelect(null)}
106
+ disabled={disabled}
107
+ className={`h-7 px-3 rounded-full text-xs font-medium transition-all ${
108
+ selectedTurnId === null
109
+ ? 'bg-white text-zinc-900'
110
+ : 'bg-zinc-800/80 text-zinc-400 hover:text-zinc-200 hover:bg-zinc-700/80'
111
+ }`}
112
+ >
113
+ All
114
+ </button>
115
+ {turns.map((turn, index) => (
116
+ <button
117
+ key={turn.id}
118
+ onClick={() => onSelect(turn.id)}
119
+ disabled={disabled}
120
+ className={`h-7 min-w-7 px-2.5 rounded-full text-xs font-medium transition-all flex items-center justify-center ${
121
+ selectedTurnId === turn.id
122
+ ? 'bg-white text-zinc-900'
123
+ : 'bg-zinc-800/80 text-zinc-400 hover:text-zinc-200 hover:bg-zinc-700/80'
124
+ } ${turn.hasError ? 'ring-1 ring-red-500/40' : ''}`}
125
+ title={turn.promptPreview}
126
+ >
127
+ {index + 1}
128
+ </button>
129
+ ))}
130
+ </div>
131
+ );
132
+ }
133
+
134
+ // Minimal playback controls
135
+ function PlaybackControls({
136
+ isPlaying,
137
+ onPlay,
138
+ onPause,
139
+ onReset,
140
+ currentNodeIndex,
141
+ totalNodes,
142
+ playbackSpeed,
143
+ onSpeedChange,
144
+ }: {
145
+ isPlaying: boolean;
146
+ onPlay: () => void;
147
+ onPause: () => void;
148
+ onReset: () => void;
149
+ currentNodeIndex: number;
150
+ totalNodes: number;
151
+ playbackSpeed: number;
152
+ onSpeedChange: (speed: number) => void;
153
+ }) {
154
+ if (totalNodes === 0) return null;
155
+
156
+ const progress = totalNodes > 0 ? (currentNodeIndex / totalNodes) * 100 : 0;
157
+
158
+ return (
159
+ <div className="flex items-center gap-2">
160
+ <button
161
+ onClick={isPlaying ? onPause : onPlay}
162
+ className="h-7 w-7 rounded-full bg-indigo-500 hover:bg-indigo-400 flex items-center justify-center transition-colors"
163
+ title={isPlaying ? "Pause" : "Play"}
164
+ >
165
+ {isPlaying ? (
166
+ <PauseIcon className="w-3.5 h-3.5 text-white" />
167
+ ) : (
168
+ <PlayIcon className="w-3.5 h-3.5 text-white ml-0.5" />
169
+ )}
170
+ </button>
171
+
172
+ <button
173
+ onClick={onReset}
174
+ className="h-7 w-7 rounded-full bg-zinc-800/80 hover:bg-zinc-700/80 flex items-center justify-center transition-colors"
175
+ title="Reset"
176
+ >
177
+ <ArrowPathIcon className="w-3.5 h-3.5 text-zinc-400" />
178
+ </button>
179
+
180
+ {/* Progress */}
181
+ <div className="flex items-center gap-2 ml-1">
182
+ <div className="w-16 h-1 bg-zinc-800 rounded-full overflow-hidden">
183
+ <div
184
+ className="h-full bg-indigo-500 transition-all duration-200"
185
+ style={{ width: `${progress}%` }}
186
+ />
187
+ </div>
188
+ <span className="text-[10px] text-zinc-500 font-mono tabular-nums w-8">
189
+ {currentNodeIndex}/{totalNodes}
190
+ </span>
191
+ </div>
192
+
193
+ {/* Speed */}
194
+ <select
195
+ value={playbackSpeed}
196
+ onChange={(e) => onSpeedChange(Number(e.target.value))}
197
+ className="h-6 bg-zinc-800/80 border-0 rounded text-[10px] text-zinc-400 focus:ring-1 focus:ring-indigo-500/50 cursor-pointer px-1.5"
198
+ >
199
+ <option value={2000}>0.5x</option>
200
+ <option value={1000}>1x</option>
201
+ <option value={500}>2x</option>
202
+ <option value={250}>4x</option>
203
+ </select>
204
+ </div>
205
+ );
206
+ }
207
+
208
+ // Compact legend
209
+ function OpsLegend() {
210
+ const items: { type: OpsNodeType; label: string }[] = [
211
+ { type: 'user_prompt', label: 'Prompt' },
212
+ { type: 'llm_call', label: 'LLM' },
213
+ { type: 'tool_call', label: 'Tool' },
214
+ { type: 'tool_result', label: 'Result' },
215
+ { type: 'widget_render', label: 'Widget' },
216
+ { type: 'llm_response', label: 'Response' },
217
+ ];
218
+
219
+ return (
220
+ <div className="flex items-center gap-3">
221
+ {items.map(({ type, label }) => (
222
+ <div key={type} className="flex items-center gap-1.5">
223
+ <div
224
+ className="w-2 h-2 rounded-sm"
225
+ style={{ backgroundColor: OPS_NODE_HEX_COLORS[type] }}
226
+ />
227
+ <span className="text-[10px] text-zinc-500">{label}</span>
228
+ </div>
229
+ ))}
230
+ </div>
231
+ );
232
+ }
233
+
234
+ // Empty state
235
+ function OpsEmptyState({ className = '' }: { className?: string }) {
236
+ return (
237
+ <div className={`flex items-center justify-center h-full ${className}`}>
238
+ <div className="text-center">
239
+ <div className="w-10 h-10 mx-auto mb-3 rounded-xl bg-zinc-800/50 flex items-center justify-center">
240
+ <BoltIcon className="w-5 h-5 text-zinc-600" />
241
+ </div>
242
+ <p className="text-sm text-zinc-500">No operations yet</p>
243
+ <p className="text-xs text-zinc-600 mt-1">Start a conversation to see the workflow</p>
244
+ </div>
245
+ </div>
246
+ );
247
+ }
248
+
249
+ // Hook for progressive playback
250
+ function useProgressivePlayback(
251
+ allNodes: OpsNode[],
252
+ allEdges: OpsEdge[],
253
+ selectedTurnId: string | null
254
+ ) {
255
+ const [isPlaying, setIsPlaying] = useState(false);
256
+ const [visibleNodeCount, setVisibleNodeCount] = useState<number | null>(null);
257
+ const [playbackSpeed, setPlaybackSpeed] = useState(1000);
258
+ const [lastAnimatedIndex, setLastAnimatedIndex] = useState(-1);
259
+ const playbackTimerRef = useRef<NodeJS.Timeout | null>(null);
260
+
261
+ const filteredNodes = useMemo(() => {
262
+ if (selectedTurnId === null) return allNodes;
263
+ return allNodes.filter(n => n.turnId === selectedTurnId);
264
+ }, [allNodes, selectedTurnId]);
265
+
266
+ const filteredEdges = useMemo(() => {
267
+ if (selectedTurnId === null) return allEdges;
268
+ return allEdges.filter(e => e.turnId === selectedTurnId);
269
+ }, [allEdges, selectedTurnId]);
270
+
271
+ const visibleNodes = useMemo(() => {
272
+ if (visibleNodeCount === null) {
273
+ return filteredNodes.map((node, idx) => convertToFlowNode(node, idx, false));
274
+ }
275
+ return filteredNodes
276
+ .slice(0, visibleNodeCount)
277
+ .map((node, idx) => convertToFlowNode(node, idx, idx === lastAnimatedIndex));
278
+ }, [filteredNodes, visibleNodeCount, lastAnimatedIndex]);
279
+
280
+ const visibleEdges = useMemo(() => {
281
+ if (visibleNodeCount === null) {
282
+ return filteredEdges.map(edge => convertToFlowEdge(edge, false));
283
+ }
284
+
285
+ const visibleNodeIds = new Set(visibleNodes.map(n => n.id));
286
+ return filteredEdges
287
+ .filter(edge => visibleNodeIds.has(edge.source) && visibleNodeIds.has(edge.target))
288
+ .map((edge) => convertToFlowEdge(edge, true));
289
+ }, [filteredEdges, visibleNodes, visibleNodeCount]);
290
+
291
+ const startPlayback = useCallback(() => {
292
+ setVisibleNodeCount(0);
293
+ setLastAnimatedIndex(-1);
294
+ setIsPlaying(true);
295
+ }, []);
296
+
297
+ const pausePlayback = useCallback(() => {
298
+ setIsPlaying(false);
299
+ if (playbackTimerRef.current) {
300
+ clearTimeout(playbackTimerRef.current);
301
+ playbackTimerRef.current = null;
302
+ }
303
+ }, []);
304
+
305
+ const resetPlayback = useCallback(() => {
306
+ pausePlayback();
307
+ setVisibleNodeCount(null);
308
+ setLastAnimatedIndex(-1);
309
+ }, [pausePlayback]);
310
+
311
+ useEffect(() => {
312
+ if (!isPlaying || visibleNodeCount === null) return;
313
+
314
+ if (visibleNodeCount >= filteredNodes.length) {
315
+ setIsPlaying(false);
316
+ return;
317
+ }
318
+
319
+ playbackTimerRef.current = setTimeout(() => {
320
+ setLastAnimatedIndex(visibleNodeCount);
321
+ setVisibleNodeCount(prev => (prev ?? 0) + 1);
322
+ }, playbackSpeed);
323
+
324
+ return () => {
325
+ if (playbackTimerRef.current) {
326
+ clearTimeout(playbackTimerRef.current);
327
+ }
328
+ };
329
+ }, [isPlaying, visibleNodeCount, filteredNodes.length, playbackSpeed]);
330
+
331
+ useEffect(() => {
332
+ resetPlayback();
333
+ }, [selectedTurnId, resetPlayback]);
334
+
335
+ return {
336
+ isPlaying,
337
+ visibleNodes,
338
+ visibleEdges,
339
+ visibleNodeCount: visibleNodeCount ?? filteredNodes.length,
340
+ totalNodes: filteredNodes.length,
341
+ playbackSpeed,
342
+ startPlayback,
343
+ pausePlayback,
344
+ resetPlayback,
345
+ setPlaybackSpeed,
346
+ isInPlaybackMode: visibleNodeCount !== null,
347
+ };
348
+ }
349
+
350
+ // Main canvas component
351
+ interface OpsFlowCanvasProps {
352
+ nodes: Node[];
353
+ edges: Edge[];
354
+ onNodesChange: ReturnType<typeof useNodesState>[2];
355
+ onEdgesChange: ReturnType<typeof useEdgesState>[2];
356
+ onNodeClick: (event: React.MouseEvent, node: Node) => void;
357
+ onPaneClick: () => void;
358
+ selectedNode: OpsNode | undefined;
359
+ onCloseDetail: () => void;
360
+ turns: OpsTurn[];
361
+ selectedTurnId: string | null;
362
+ onSelectTurn: (turnId: string | null) => void;
363
+ isPlaying: boolean;
364
+ visibleNodeCount: number;
365
+ totalNodes: number;
366
+ playbackSpeed: number;
367
+ onPlay: () => void;
368
+ onPause: () => void;
369
+ onReset: () => void;
370
+ onSpeedChange: (speed: number) => void;
371
+ onExpand?: () => void;
372
+ isCompact?: boolean;
373
+ isExpanded?: boolean;
374
+ }
375
+
376
+ function OpsFlowCanvas({
377
+ nodes,
378
+ edges,
379
+ onNodesChange,
380
+ onEdgesChange,
381
+ onNodeClick,
382
+ onPaneClick,
383
+ selectedNode,
384
+ onCloseDetail,
385
+ turns,
386
+ selectedTurnId,
387
+ onSelectTurn,
388
+ isPlaying,
389
+ visibleNodeCount,
390
+ totalNodes,
391
+ playbackSpeed,
392
+ onPlay,
393
+ onPause,
394
+ onReset,
395
+ onSpeedChange,
396
+ onExpand,
397
+ isCompact = false,
398
+ isExpanded = false,
399
+ }: OpsFlowCanvasProps) {
400
+ // Different zoom settings for split view vs expanded view
401
+ const fitViewOptions = isExpanded
402
+ ? { padding: 0.2, minZoom: 1.2, maxZoom: 1.5 } // Expanded: zoomed in
403
+ : { padding: 0.3, minZoom: 0.5, maxZoom: 0.8 }; // Split: zoomed out to see more
404
+
405
+ const defaultZoom = isExpanded ? 1.5 : 0.6;
406
+
407
+ return (
408
+ <div className="flex-1 relative bg-zinc-950">
409
+ <style jsx global>{`
410
+ .ops-node-animate {
411
+ animation: opsNodeIn 0.3s ease-out forwards;
412
+ }
413
+ @keyframes opsNodeIn {
414
+ from { opacity: 0; transform: scale(0.8) translateY(-8px); }
415
+ to { opacity: 1; transform: scale(1) translateY(0); }
416
+ }
417
+ .react-flow__controls {
418
+ background: #18181b !important;
419
+ border: 1px solid #27272a !important;
420
+ border-radius: 8px !important;
421
+ box-shadow: 0 4px 12px rgba(0,0,0,0.4) !important;
422
+ }
423
+ .react-flow__controls-button {
424
+ background: transparent !important;
425
+ border: none !important;
426
+ color: #71717a !important;
427
+ }
428
+ .react-flow__controls-button:hover {
429
+ background: #27272a !important;
430
+ color: #a1a1aa !important;
431
+ }
432
+ .react-flow__controls-button svg {
433
+ fill: currentColor !important;
434
+ }
435
+ `}</style>
436
+
437
+ <ReactFlow
438
+ nodes={nodes}
439
+ edges={edges}
440
+ onNodesChange={onNodesChange}
441
+ onEdgesChange={onEdgesChange}
442
+ onNodeClick={onNodeClick}
443
+ onPaneClick={onPaneClick}
444
+ nodeTypes={nodeTypes}
445
+ fitView
446
+ fitViewOptions={fitViewOptions}
447
+ minZoom={0.3}
448
+ maxZoom={2.5}
449
+ defaultViewport={{ x: 0, y: 0, zoom: defaultZoom }}
450
+ proOptions={{ hideAttribution: true }}
451
+ >
452
+ <Background
453
+ variant={BackgroundVariant.Dots}
454
+ gap={20}
455
+ size={1}
456
+ color="#27272a"
457
+ />
458
+ <Controls
459
+ showInteractive={false}
460
+ position="bottom-right"
461
+ />
462
+
463
+ {/* Top controls */}
464
+ <Panel position="top-left" className="!m-3 flex items-center gap-3">
465
+ <TurnSelector
466
+ turns={turns}
467
+ selectedTurnId={selectedTurnId}
468
+ onSelect={onSelectTurn}
469
+ disabled={isPlaying}
470
+ />
471
+ <div className="h-5 w-px bg-zinc-800" />
472
+ <PlaybackControls
473
+ isPlaying={isPlaying}
474
+ onPlay={onPlay}
475
+ onPause={onPause}
476
+ onReset={onReset}
477
+ currentNodeIndex={visibleNodeCount}
478
+ totalNodes={totalNodes}
479
+ playbackSpeed={playbackSpeed}
480
+ onSpeedChange={onSpeedChange}
481
+ />
482
+ </Panel>
483
+
484
+ {/* Bottom left - Legend */}
485
+ <Panel position="bottom-left" className="!m-3">
486
+ <OpsLegend />
487
+ </Panel>
488
+
489
+ {/* Expand button for compact view */}
490
+ {isCompact && onExpand && (
491
+ <Panel position="top-right" className="!m-3">
492
+ <button
493
+ onClick={onExpand}
494
+ className="h-7 w-7 rounded-lg bg-zinc-800/80 hover:bg-zinc-700/80 flex items-center justify-center transition-colors"
495
+ title="Expand"
496
+ >
497
+ <ArrowsPointingOutIcon className="w-3.5 h-3.5 text-zinc-400" />
498
+ </button>
499
+ </Panel>
500
+ )}
501
+ </ReactFlow>
502
+
503
+ {/* Detail Panel */}
504
+ {selectedNode && (
505
+ <OpsNodeDetailPanel node={selectedNode} onClose={onCloseDetail} />
506
+ )}
507
+ </div>
508
+ );
509
+ }
510
+
511
+ // Modal component
512
+ interface OpsModalProps {
513
+ isOpen: boolean;
514
+ onClose: () => void;
515
+ allNodes: OpsNode[];
516
+ allEdges: OpsEdge[];
517
+ selectedNodeId: string | null;
518
+ setSelectedNode: (nodeId: string | null) => void;
519
+ turns: OpsTurn[];
520
+ selectedTurnId: string | null;
521
+ onSelectTurn: (turnId: string | null) => void;
522
+ summary: ReturnType<typeof useOpsStore.getState>['getSummary'] extends () => infer R ? R : never;
523
+ }
524
+
525
+ function OpsModal({
526
+ isOpen,
527
+ onClose,
528
+ allNodes,
529
+ allEdges,
530
+ selectedNodeId,
531
+ setSelectedNode,
532
+ turns,
533
+ selectedTurnId,
534
+ onSelectTurn,
535
+ summary,
536
+ }: OpsModalProps) {
537
+ const [mounted, setMounted] = useState(false);
538
+
539
+ const {
540
+ isPlaying,
541
+ visibleNodes,
542
+ visibleEdges,
543
+ visibleNodeCount,
544
+ totalNodes,
545
+ playbackSpeed,
546
+ startPlayback,
547
+ pausePlayback,
548
+ resetPlayback,
549
+ setPlaybackSpeed,
550
+ } = useProgressivePlayback(allNodes, allEdges, selectedTurnId);
551
+
552
+ const [nodes, setNodes, onNodesChange] = useNodesState(visibleNodes);
553
+ const [edges, setEdges, onEdgesChange] = useEdgesState(visibleEdges);
554
+
555
+ useEffect(() => {
556
+ setNodes(visibleNodes);
557
+ setEdges(visibleEdges);
558
+ }, [visibleNodes, visibleEdges, setNodes, setEdges]);
559
+
560
+ const onNodeClick = useCallback((_: React.MouseEvent, node: Node) => {
561
+ setSelectedNode(node.id);
562
+ }, [setSelectedNode]);
563
+
564
+ const onPaneClick = useCallback(() => {
565
+ setSelectedNode(null);
566
+ }, [setSelectedNode]);
567
+
568
+ const selectedNode = allNodes.find(n => n.id === selectedNodeId);
569
+
570
+ useEffect(() => {
571
+ setMounted(true);
572
+ return () => setMounted(false);
573
+ }, []);
574
+
575
+ // Handle escape key
576
+ useEffect(() => {
577
+ const handleEsc = (e: KeyboardEvent) => {
578
+ if (e.key === 'Escape') onClose();
579
+ };
580
+ if (isOpen) {
581
+ window.addEventListener('keydown', handleEsc);
582
+ return () => window.removeEventListener('keydown', handleEsc);
583
+ }
584
+ }, [isOpen, onClose]);
585
+
586
+ if (!mounted || !isOpen) return null;
587
+
588
+ const modalContent = (
589
+ <div
590
+ className="fixed inset-0 z-[9999] bg-black/95"
591
+ onClick={onClose}
592
+ >
593
+ <div
594
+ className="absolute inset-4 bg-zinc-900 rounded-2xl border border-zinc-800 shadow-2xl overflow-hidden flex flex-col"
595
+ onClick={(e) => e.stopPropagation()}
596
+ >
597
+ {/* Header */}
598
+ <div className="flex items-center justify-between px-5 py-4 border-b border-zinc-800/80">
599
+ <div className="flex items-center gap-3">
600
+ <div className="w-8 h-8 rounded-lg bg-indigo-500/10 flex items-center justify-center">
601
+ <Squares2X2Icon className="w-4 h-4 text-indigo-400" />
602
+ </div>
603
+ <div>
604
+ <h2 className="text-sm font-semibold text-zinc-100">Operations</h2>
605
+ <p className="text-xs text-zinc-500">{allNodes.length} steps across {turns.length} turns</p>
606
+ </div>
607
+ </div>
608
+ <button
609
+ onClick={onClose}
610
+ className="w-8 h-8 rounded-lg hover:bg-zinc-800 flex items-center justify-center transition-colors"
611
+ >
612
+ <XMarkIcon className="w-4 h-4 text-zinc-500" />
613
+ </button>
614
+ </div>
615
+
616
+ {/* Canvas */}
617
+ <OpsFlowCanvas
618
+ nodes={nodes}
619
+ edges={edges}
620
+ onNodesChange={onNodesChange}
621
+ onEdgesChange={onEdgesChange}
622
+ onNodeClick={onNodeClick}
623
+ onPaneClick={onPaneClick}
624
+ selectedNode={selectedNode}
625
+ onCloseDetail={() => setSelectedNode(null)}
626
+ turns={turns}
627
+ selectedTurnId={selectedTurnId}
628
+ onSelectTurn={onSelectTurn}
629
+ isPlaying={isPlaying}
630
+ visibleNodeCount={visibleNodeCount}
631
+ totalNodes={totalNodes}
632
+ playbackSpeed={playbackSpeed}
633
+ onPlay={startPlayback}
634
+ onPause={pausePlayback}
635
+ onReset={resetPlayback}
636
+ onSpeedChange={setPlaybackSpeed}
637
+ isExpanded
638
+ />
639
+
640
+ {/* Footer */}
641
+ <OpsSummaryBar summary={summary} />
642
+ </div>
643
+ </div>
644
+ );
645
+
646
+ return createPortal(modalContent, document.body);
647
+ }
648
+
649
+ interface OpsCanvasProps {
650
+ className?: string;
651
+ }
652
+
653
+ export function OpsCanvas({ className = '' }: OpsCanvasProps) {
654
+ const {
655
+ currentSession,
656
+ selectedNodeId,
657
+ setSelectedNode,
658
+ getSummary,
659
+ selectedTurnId,
660
+ setSelectedTurn,
661
+ } = useOpsStore();
662
+
663
+ const [isModalOpen, setIsModalOpen] = useState(false);
664
+
665
+ const allNodes = currentSession?.nodes || [];
666
+ const allEdges = currentSession?.edges || [];
667
+ const turns = currentSession?.turns || [];
668
+
669
+ const {
670
+ isPlaying,
671
+ visibleNodes,
672
+ visibleEdges,
673
+ visibleNodeCount,
674
+ totalNodes,
675
+ playbackSpeed,
676
+ startPlayback,
677
+ pausePlayback,
678
+ resetPlayback,
679
+ setPlaybackSpeed,
680
+ } = useProgressivePlayback(allNodes, allEdges, selectedTurnId);
681
+
682
+ const [nodes, setNodes, onNodesChange] = useNodesState(visibleNodes);
683
+ const [edges, setEdges, onEdgesChange] = useEdgesState(visibleEdges);
684
+
685
+ useEffect(() => {
686
+ setNodes(visibleNodes);
687
+ setEdges(visibleEdges);
688
+ }, [visibleNodes, visibleEdges, setNodes, setEdges]);
689
+
690
+ const onNodeClick = useCallback((_: React.MouseEvent, node: Node) => {
691
+ setSelectedNode(node.id);
692
+ }, [setSelectedNode]);
693
+
694
+ const onPaneClick = useCallback(() => {
695
+ setSelectedNode(null);
696
+ }, [setSelectedNode]);
697
+
698
+ const summary = getSummary();
699
+ const selectedNode = allNodes.find(n => n.id === selectedNodeId);
700
+
701
+ if (!currentSession || allNodes.length === 0) {
702
+ return <OpsEmptyState className={className} />;
703
+ }
704
+
705
+ return (
706
+ <>
707
+ <div className={`flex flex-col h-full bg-zinc-950 ${className}`}>
708
+ <OpsFlowCanvas
709
+ nodes={nodes}
710
+ edges={edges}
711
+ onNodesChange={onNodesChange}
712
+ onEdgesChange={onEdgesChange}
713
+ onNodeClick={onNodeClick}
714
+ onPaneClick={onPaneClick}
715
+ selectedNode={selectedNode}
716
+ onCloseDetail={() => setSelectedNode(null)}
717
+ turns={turns}
718
+ selectedTurnId={selectedTurnId}
719
+ onSelectTurn={setSelectedTurn}
720
+ isPlaying={isPlaying}
721
+ visibleNodeCount={visibleNodeCount}
722
+ totalNodes={totalNodes}
723
+ playbackSpeed={playbackSpeed}
724
+ onPlay={startPlayback}
725
+ onPause={pausePlayback}
726
+ onReset={resetPlayback}
727
+ onSpeedChange={setPlaybackSpeed}
728
+ onExpand={() => setIsModalOpen(true)}
729
+ isCompact
730
+ />
731
+ <OpsSummaryBar summary={summary} compact />
732
+ </div>
733
+
734
+ <OpsModal
735
+ isOpen={isModalOpen}
736
+ onClose={() => setIsModalOpen(false)}
737
+ allNodes={allNodes}
738
+ allEdges={allEdges}
739
+ selectedNodeId={selectedNodeId}
740
+ setSelectedNode={setSelectedNode}
741
+ turns={turns}
742
+ selectedTurnId={selectedTurnId}
743
+ onSelectTurn={setSelectedTurn}
744
+ summary={summary}
745
+ />
746
+ </>
747
+ );
748
+ }