nitrostack 1.0.71 → 1.0.73

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 (253) hide show
  1. package/dist/auth/api-key.js.map +1 -1
  2. package/dist/auth/client.js.map +1 -1
  3. package/dist/auth/index.d.ts +2 -1
  4. package/dist/auth/index.d.ts.map +1 -1
  5. package/dist/auth/index.js +3 -0
  6. package/dist/auth/index.js.map +1 -1
  7. package/dist/auth/middleware.d.ts +1 -1
  8. package/dist/auth/middleware.d.ts.map +1 -1
  9. package/dist/auth/middleware.js.map +1 -1
  10. package/dist/auth/secure-secret.d.ts +136 -0
  11. package/dist/auth/secure-secret.d.ts.map +1 -0
  12. package/dist/auth/secure-secret.js +182 -0
  13. package/dist/auth/secure-secret.js.map +1 -0
  14. package/dist/auth/server-metadata.d.ts.map +1 -1
  15. package/dist/auth/server-metadata.js.map +1 -1
  16. package/dist/auth/simple-jwt.d.ts +100 -14
  17. package/dist/auth/simple-jwt.d.ts.map +1 -1
  18. package/dist/auth/simple-jwt.js +19 -9
  19. package/dist/auth/simple-jwt.js.map +1 -1
  20. package/dist/auth/token-store.js +1 -1
  21. package/dist/auth/token-store.js.map +1 -1
  22. package/dist/auth/token-validation.js +1 -1
  23. package/dist/auth/token-validation.js.map +1 -1
  24. package/dist/cli/commands/build.js +1 -1
  25. package/dist/cli/commands/build.js.map +1 -1
  26. package/dist/cli/commands/generate-types.js +12 -12
  27. package/dist/cli/commands/generate-types.js.map +1 -1
  28. package/dist/cli/commands/generate.d.ts +8 -1
  29. package/dist/cli/commands/generate.d.ts.map +1 -1
  30. package/dist/cli/commands/generate.js +13 -12
  31. package/dist/cli/commands/generate.js.map +1 -1
  32. package/dist/cli/commands/init.js +1 -1
  33. package/dist/cli/commands/init.js.map +1 -1
  34. package/dist/cli/commands/upgrade.d.ts +10 -0
  35. package/dist/cli/commands/upgrade.d.ts.map +1 -0
  36. package/dist/cli/commands/upgrade.js +221 -0
  37. package/dist/cli/commands/upgrade.js.map +1 -0
  38. package/dist/cli/index.js +7 -0
  39. package/dist/cli/index.js.map +1 -1
  40. package/dist/core/app-decorator.d.ts +4 -3
  41. package/dist/core/app-decorator.d.ts.map +1 -1
  42. package/dist/core/app-decorator.js +67 -28
  43. package/dist/core/app-decorator.js.map +1 -1
  44. package/dist/core/builders.d.ts +19 -7
  45. package/dist/core/builders.d.ts.map +1 -1
  46. package/dist/core/builders.js +15 -8
  47. package/dist/core/builders.js.map +1 -1
  48. package/dist/core/component.d.ts +8 -8
  49. package/dist/core/component.d.ts.map +1 -1
  50. package/dist/core/component.js +3 -2
  51. package/dist/core/component.js.map +1 -1
  52. package/dist/core/config-module.d.ts +11 -4
  53. package/dist/core/config-module.d.ts.map +1 -1
  54. package/dist/core/config-module.js +1 -1
  55. package/dist/core/config-module.js.map +1 -1
  56. package/dist/core/decorators/cache.decorator.d.ts +9 -9
  57. package/dist/core/decorators/cache.decorator.d.ts.map +1 -1
  58. package/dist/core/decorators/cache.decorator.js +3 -3
  59. package/dist/core/decorators/cache.decorator.js.map +1 -1
  60. package/dist/core/decorators/health-check.decorator.d.ts +3 -3
  61. package/dist/core/decorators/health-check.decorator.d.ts.map +1 -1
  62. package/dist/core/decorators/health-check.decorator.js +2 -2
  63. package/dist/core/decorators/health-check.decorator.js.map +1 -1
  64. package/dist/core/decorators/rate-limit.decorator.d.ts +5 -4
  65. package/dist/core/decorators/rate-limit.decorator.d.ts.map +1 -1
  66. package/dist/core/decorators/rate-limit.decorator.js +3 -3
  67. package/dist/core/decorators/rate-limit.decorator.js.map +1 -1
  68. package/dist/core/decorators.d.ts +47 -29
  69. package/dist/core/decorators.d.ts.map +1 -1
  70. package/dist/core/decorators.js +9 -9
  71. package/dist/core/decorators.js.map +1 -1
  72. package/dist/core/di/container.d.ts +21 -4
  73. package/dist/core/di/container.d.ts.map +1 -1
  74. package/dist/core/di/container.js +11 -7
  75. package/dist/core/di/container.js.map +1 -1
  76. package/dist/core/di/injectable.decorator.d.ts +5 -3
  77. package/dist/core/di/injectable.decorator.d.ts.map +1 -1
  78. package/dist/core/di/injectable.decorator.js.map +1 -1
  79. package/dist/core/errors.d.ts +4 -4
  80. package/dist/core/errors.d.ts.map +1 -1
  81. package/dist/core/errors.js.map +1 -1
  82. package/dist/core/events/event-emitter.d.ts +3 -3
  83. package/dist/core/events/event-emitter.d.ts.map +1 -1
  84. package/dist/core/events/event-emitter.js.map +1 -1
  85. package/dist/core/events/event.decorator.d.ts +5 -5
  86. package/dist/core/events/event.decorator.d.ts.map +1 -1
  87. package/dist/core/events/event.decorator.js +10 -6
  88. package/dist/core/events/event.decorator.js.map +1 -1
  89. package/dist/core/events/log-emitter.d.ts +7 -1
  90. package/dist/core/events/log-emitter.d.ts.map +1 -1
  91. package/dist/core/events/log-emitter.js.map +1 -1
  92. package/dist/core/filters/exception-filter.decorator.d.ts +5 -5
  93. package/dist/core/filters/exception-filter.decorator.d.ts.map +1 -1
  94. package/dist/core/filters/exception-filter.decorator.js +3 -3
  95. package/dist/core/filters/exception-filter.decorator.js.map +1 -1
  96. package/dist/core/filters/exception-filter.interface.d.ts +14 -5
  97. package/dist/core/filters/exception-filter.interface.d.ts.map +1 -1
  98. package/dist/core/guards/apikey.guard.d.ts +1 -1
  99. package/dist/core/guards/apikey.guard.d.ts.map +1 -1
  100. package/dist/core/guards/guard.interface.d.ts +1 -1
  101. package/dist/core/guards/guard.interface.d.ts.map +1 -1
  102. package/dist/core/guards/jwt.guard.d.ts +1 -1
  103. package/dist/core/guards/jwt.guard.d.ts.map +1 -1
  104. package/dist/core/guards/oauth.guard.d.ts +1 -1
  105. package/dist/core/guards/oauth.guard.d.ts.map +1 -1
  106. package/dist/core/guards/use-guards.decorator.d.ts +3 -3
  107. package/dist/core/guards/use-guards.decorator.d.ts.map +1 -1
  108. package/dist/core/guards/use-guards.decorator.js +1 -1
  109. package/dist/core/guards/use-guards.decorator.js.map +1 -1
  110. package/dist/core/index.d.ts +2 -2
  111. package/dist/core/index.d.ts.map +1 -1
  112. package/dist/core/index.js.map +1 -1
  113. package/dist/core/interceptors/interceptor.decorator.d.ts +4 -4
  114. package/dist/core/interceptors/interceptor.decorator.d.ts.map +1 -1
  115. package/dist/core/interceptors/interceptor.decorator.js +2 -2
  116. package/dist/core/interceptors/interceptor.decorator.js.map +1 -1
  117. package/dist/core/interceptors/interceptor.interface.d.ts +3 -3
  118. package/dist/core/interceptors/interceptor.interface.d.ts.map +1 -1
  119. package/dist/core/logger.d.ts.map +1 -1
  120. package/dist/core/logger.js.map +1 -1
  121. package/dist/core/middleware/middleware.decorator.d.ts +4 -4
  122. package/dist/core/middleware/middleware.decorator.d.ts.map +1 -1
  123. package/dist/core/middleware/middleware.decorator.js +2 -2
  124. package/dist/core/middleware/middleware.decorator.js.map +1 -1
  125. package/dist/core/middleware/middleware.interface.d.ts +3 -3
  126. package/dist/core/middleware/middleware.interface.d.ts.map +1 -1
  127. package/dist/core/module.d.ts +33 -14
  128. package/dist/core/module.d.ts.map +1 -1
  129. package/dist/core/module.js +11 -6
  130. package/dist/core/module.js.map +1 -1
  131. package/dist/core/oauth-module.d.ts +9 -3
  132. package/dist/core/oauth-module.d.ts.map +1 -1
  133. package/dist/core/oauth-module.js +4 -3
  134. package/dist/core/oauth-module.js.map +1 -1
  135. package/dist/core/pipes/pipe.decorator.d.ts +14 -5
  136. package/dist/core/pipes/pipe.decorator.d.ts.map +1 -1
  137. package/dist/core/pipes/pipe.decorator.js +2 -2
  138. package/dist/core/pipes/pipe.decorator.js.map +1 -1
  139. package/dist/core/pipes/pipe.interface.d.ts +9 -4
  140. package/dist/core/pipes/pipe.interface.d.ts.map +1 -1
  141. package/dist/core/prompt.d.ts +13 -4
  142. package/dist/core/prompt.d.ts.map +1 -1
  143. package/dist/core/prompt.js +2 -2
  144. package/dist/core/prompt.js.map +1 -1
  145. package/dist/core/resource.d.ts +7 -2
  146. package/dist/core/resource.d.ts.map +1 -1
  147. package/dist/core/resource.js +2 -2
  148. package/dist/core/resource.js.map +1 -1
  149. package/dist/core/server.d.ts +49 -3
  150. package/dist/core/server.d.ts.map +1 -1
  151. package/dist/core/server.js +61 -34
  152. package/dist/core/server.js.map +1 -1
  153. package/dist/core/tool.d.ts +44 -16
  154. package/dist/core/tool.d.ts.map +1 -1
  155. package/dist/core/tool.js +19 -6
  156. package/dist/core/tool.js.map +1 -1
  157. package/dist/core/transports/discovery-http-server.d.ts +7 -1
  158. package/dist/core/transports/discovery-http-server.d.ts.map +1 -1
  159. package/dist/core/transports/discovery-http-server.js.map +1 -1
  160. package/dist/core/transports/http-server.d.ts +2 -2
  161. package/dist/core/transports/http-server.d.ts.map +1 -1
  162. package/dist/core/transports/http-server.js +1 -1
  163. package/dist/core/transports/http-server.js.map +1 -1
  164. package/dist/core/transports/streamable-http.d.ts +4 -4
  165. package/dist/core/transports/streamable-http.d.ts.map +1 -1
  166. package/dist/core/transports/streamable-http.js +1 -1
  167. package/dist/core/transports/streamable-http.js.map +1 -1
  168. package/dist/core/types.d.ts +87 -15
  169. package/dist/core/types.d.ts.map +1 -1
  170. package/dist/core/widgets/widget-registry.d.ts +2 -2
  171. package/dist/core/widgets/widget-registry.d.ts.map +1 -1
  172. package/dist/core/widgets/widget-registry.js +1 -1
  173. package/dist/core/widgets/widget-registry.js.map +1 -1
  174. package/dist/testing/index.d.ts +44 -17
  175. package/dist/testing/index.d.ts.map +1 -1
  176. package/dist/testing/index.js +5 -8
  177. package/dist/testing/index.js.map +1 -1
  178. package/dist/ui-next/index.d.ts +1 -1
  179. package/dist/ui-next/index.d.ts.map +1 -1
  180. package/dist/ui-next/index.js.map +1 -1
  181. package/dist/widgets/hooks/useWidgetSDK.d.ts +5 -5
  182. package/dist/widgets/runtime/WidgetLayout.js.map +1 -1
  183. package/dist/widgets/sdk.d.ts +5 -5
  184. package/dist/widgets/sdk.d.ts.map +1 -1
  185. package/dist/widgets/sdk.js.map +1 -1
  186. package/package.json +1 -1
  187. package/src/studio/app/api/auth/fetch-metadata/route.ts +3 -2
  188. package/src/studio/app/api/auth/register-client/route.ts +3 -2
  189. package/src/studio/app/api/chat/route.ts +33 -17
  190. package/src/studio/app/api/health/checks/route.ts +5 -4
  191. package/src/studio/app/api/init/route.ts +3 -2
  192. package/src/studio/app/api/ping/route.ts +3 -2
  193. package/src/studio/app/api/prompts/[name]/route.ts +4 -3
  194. package/src/studio/app/api/prompts/route.ts +3 -2
  195. package/src/studio/app/api/resources/[...uri]/route.ts +3 -2
  196. package/src/studio/app/api/resources/route.ts +3 -2
  197. package/src/studio/app/api/roots/route.ts +3 -2
  198. package/src/studio/app/api/sampling/route.ts +3 -2
  199. package/src/studio/app/api/tools/[name]/call/route.ts +3 -2
  200. package/src/studio/app/api/tools/route.ts +4 -3
  201. package/src/studio/app/api/widget-examples/route.ts +5 -4
  202. package/src/studio/app/auth/callback/page.tsx +9 -8
  203. package/src/studio/app/chat/page.tsx +1535 -468
  204. package/src/studio/app/chat/page.tsx.backup +1046 -187
  205. package/src/studio/app/globals.css +361 -191
  206. package/src/studio/app/health/page.tsx +73 -77
  207. package/src/studio/app/layout.tsx +9 -11
  208. package/src/studio/app/logs/page.tsx +31 -32
  209. package/src/studio/app/page.tsx +136 -232
  210. package/src/studio/app/prompts/page.tsx +115 -97
  211. package/src/studio/app/resources/page.tsx +115 -124
  212. package/src/studio/app/settings/page.tsx +1083 -127
  213. package/src/studio/app/tools/page.tsx +343 -0
  214. package/src/studio/components/EnlargeModal.tsx +76 -65
  215. package/src/studio/components/LogMessage.tsx +6 -6
  216. package/src/studio/components/MarkdownRenderer.tsx +246 -349
  217. package/src/studio/components/Sidebar.tsx +165 -210
  218. package/src/studio/components/SplashScreen.tsx +109 -0
  219. package/src/studio/components/ToolCard.tsx +50 -41
  220. package/src/studio/components/VoiceOrbOverlay.tsx +475 -0
  221. package/src/studio/components/WidgetErrorBoundary.tsx +48 -0
  222. package/src/studio/components/WidgetRenderer.tsx +169 -211
  223. package/src/studio/components/ops/OpsCanvas.tsx +748 -0
  224. package/src/studio/components/ops/OpsNodeDetailPanel.tsx +150 -0
  225. package/src/studio/components/ops/OpsSummaryBar.tsx +90 -0
  226. package/src/studio/components/ops/index.ts +5 -0
  227. package/src/studio/components/ops/nodes/BaseNode.tsx +65 -0
  228. package/src/studio/components/ops/nodes/LLMCallNode.tsx +34 -0
  229. package/src/studio/components/ops/nodes/LLMResponseNode.tsx +33 -0
  230. package/src/studio/components/ops/nodes/ToolCallNode.tsx +30 -0
  231. package/src/studio/components/ops/nodes/ToolResultNode.tsx +43 -0
  232. package/src/studio/components/ops/nodes/UserPromptNode.tsx +34 -0
  233. package/src/studio/components/ops/nodes/WidgetRenderNode.tsx +23 -0
  234. package/src/studio/components/ops/nodes/index.ts +8 -0
  235. package/src/studio/components/tools/ToolsCanvas.tsx +327 -0
  236. package/src/studio/lib/api.ts +61 -42
  237. package/src/studio/lib/http-client-transport.ts +2 -2
  238. package/src/studio/lib/llm-service.ts +126 -47
  239. package/src/studio/lib/mcp-client.ts +9 -6
  240. package/src/studio/lib/ops-store.ts +427 -0
  241. package/src/studio/lib/ops-tracker.ts +416 -0
  242. package/src/studio/lib/ops-types.ts +164 -0
  243. package/src/studio/lib/store.ts +23 -11
  244. package/src/studio/lib/types.ts +228 -38
  245. package/src/studio/lib/widget-loader.ts +2 -2
  246. package/src/studio/package-lock.json +3303 -0
  247. package/src/studio/package.json +3 -1
  248. package/src/studio/public/NitroStudio Isotype Color.png +0 -0
  249. package/src/studio/tailwind.config.ts +63 -17
  250. package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +19 -22
  251. package/dist/cli/build-widgets.mjs +0 -165
  252. package/src/studio/app/auth/page.tsx +0 -560
  253. package/src/studio/app/ping/page.tsx +0 -209
@@ -0,0 +1,150 @@
1
+ 'use client';
2
+
3
+ import type { OpsNode } from '@/lib/ops-types';
4
+ import { OPS_NODE_LABELS, OPS_NODE_HEX_COLORS } from '@/lib/ops-types';
5
+ import { XMarkIcon } from '@heroicons/react/24/outline';
6
+
7
+ interface OpsNodeDetailPanelProps {
8
+ node: OpsNode;
9
+ onClose: () => void;
10
+ }
11
+
12
+ export function OpsNodeDetailPanel({ node, onClose }: OpsNodeDetailPanelProps) {
13
+ const color = OPS_NODE_HEX_COLORS[node.type];
14
+ const label = OPS_NODE_LABELS[node.type];
15
+
16
+ const formatTime = (ts: number) => {
17
+ return new Date(ts).toLocaleTimeString('en-US', {
18
+ hour: '2-digit',
19
+ minute: '2-digit',
20
+ second: '2-digit',
21
+ hour12: false,
22
+ });
23
+ };
24
+
25
+ const formatDuration = (ms?: number) => {
26
+ if (!ms) return '-';
27
+ if (ms < 1000) return `${ms}ms`;
28
+ return `${(ms / 1000).toFixed(2)}s`;
29
+ };
30
+
31
+ return (
32
+ <div className="absolute top-3 right-3 w-72 bg-zinc-900 rounded-xl border border-zinc-800 shadow-xl overflow-hidden">
33
+ {/* Header */}
34
+ <div className="flex items-center justify-between px-4 py-3 border-b border-zinc-800/80">
35
+ <div className="flex items-center gap-2">
36
+ <div
37
+ className="w-2 h-2 rounded-sm"
38
+ style={{ backgroundColor: color }}
39
+ />
40
+ <span className="text-sm font-medium text-zinc-200">{label}</span>
41
+ </div>
42
+ <button
43
+ onClick={onClose}
44
+ className="w-6 h-6 rounded-md hover:bg-zinc-800 flex items-center justify-center transition-colors"
45
+ >
46
+ <XMarkIcon className="w-3.5 h-3.5 text-zinc-500" />
47
+ </button>
48
+ </div>
49
+
50
+ {/* Content */}
51
+ <div className="px-4 py-3 space-y-3 max-h-80 overflow-y-auto">
52
+ {/* Timing */}
53
+ <div className="grid grid-cols-2 gap-3">
54
+ <div>
55
+ <div className="text-[10px] text-zinc-600 uppercase tracking-wider mb-0.5">Time</div>
56
+ <div className="text-xs text-zinc-300 font-mono">{formatTime(node.timestamp)}</div>
57
+ </div>
58
+ <div>
59
+ <div className="text-[10px] text-zinc-600 uppercase tracking-wider mb-0.5">Duration</div>
60
+ <div className="text-xs text-zinc-300 font-mono">{formatDuration(node.duration)}</div>
61
+ </div>
62
+ </div>
63
+
64
+ {/* Status */}
65
+ <div>
66
+ <div className="text-[10px] text-zinc-600 uppercase tracking-wider mb-0.5">Status</div>
67
+ <div className={`text-xs ${
68
+ node.data.status === 'error' ? 'text-red-400' :
69
+ node.data.status === 'running' ? 'text-indigo-400' :
70
+ node.data.status === 'success' ? 'text-green-400' : 'text-zinc-400'
71
+ }`}>
72
+ {node.data.status}
73
+ </div>
74
+ </div>
75
+
76
+ {/* Type-specific data */}
77
+ {node.type === 'user_prompt' && 'content' in node.data && (
78
+ <div>
79
+ <div className="text-[10px] text-zinc-600 uppercase tracking-wider mb-1">Content</div>
80
+ <div className="text-xs text-zinc-400 bg-zinc-800/50 rounded-lg p-2.5 max-h-24 overflow-y-auto">
81
+ {node.data.content}
82
+ </div>
83
+ </div>
84
+ )}
85
+
86
+ {node.type === 'llm_call' && 'model' in node.data && (
87
+ <>
88
+ <div>
89
+ <div className="text-[10px] text-zinc-600 uppercase tracking-wider mb-0.5">Model</div>
90
+ <div className="text-xs text-zinc-300 font-mono">{node.data.model}</div>
91
+ </div>
92
+ {node.data.totalTokens && (
93
+ <div className="grid grid-cols-3 gap-2">
94
+ <div>
95
+ <div className="text-[10px] text-zinc-600 uppercase tracking-wider mb-0.5">Input</div>
96
+ <div className="text-xs text-zinc-300 font-mono tabular-nums">{node.data.inputTokens}</div>
97
+ </div>
98
+ <div>
99
+ <div className="text-[10px] text-zinc-600 uppercase tracking-wider mb-0.5">Output</div>
100
+ <div className="text-xs text-zinc-300 font-mono tabular-nums">{node.data.outputTokens}</div>
101
+ </div>
102
+ <div>
103
+ <div className="text-[10px] text-zinc-600 uppercase tracking-wider mb-0.5">Total</div>
104
+ <div className="text-xs text-zinc-300 font-mono tabular-nums">{node.data.totalTokens}</div>
105
+ </div>
106
+ </div>
107
+ )}
108
+ </>
109
+ )}
110
+
111
+ {node.type === 'tool_call' && 'toolName' in node.data && (
112
+ <>
113
+ <div>
114
+ <div className="text-[10px] text-zinc-600 uppercase tracking-wider mb-0.5">Tool</div>
115
+ <div className="text-xs text-zinc-300 font-mono">{node.data.toolName}</div>
116
+ </div>
117
+ {Object.keys(node.data.toolArgs || {}).length > 0 && (
118
+ <div>
119
+ <div className="text-[10px] text-zinc-600 uppercase tracking-wider mb-1">Arguments</div>
120
+ <pre className="text-[10px] text-zinc-400 bg-zinc-800/50 rounded-lg p-2.5 overflow-x-auto font-mono">
121
+ {JSON.stringify(node.data.toolArgs, null, 2)}
122
+ </pre>
123
+ </div>
124
+ )}
125
+ </>
126
+ )}
127
+
128
+ {node.type === 'tool_result' && 'result' in node.data && (
129
+ <div>
130
+ <div className="text-[10px] text-zinc-600 uppercase tracking-wider mb-1">Result</div>
131
+ <pre className="text-[10px] text-zinc-400 bg-zinc-800/50 rounded-lg p-2.5 max-h-32 overflow-auto font-mono">
132
+ {typeof node.data.result === 'string'
133
+ ? node.data.result
134
+ : JSON.stringify(node.data.result, null, 2)}
135
+ </pre>
136
+ </div>
137
+ )}
138
+
139
+ {node.type === 'llm_response' && 'content' in node.data && (
140
+ <div>
141
+ <div className="text-[10px] text-zinc-600 uppercase tracking-wider mb-1">Response</div>
142
+ <div className="text-xs text-zinc-400 bg-zinc-800/50 rounded-lg p-2.5 max-h-32 overflow-y-auto">
143
+ {node.data.content}
144
+ </div>
145
+ </div>
146
+ )}
147
+ </div>
148
+ </div>
149
+ );
150
+ }
@@ -0,0 +1,90 @@
1
+ 'use client';
2
+
3
+ import type { OpsSessionSummary } from '@/lib/ops-types';
4
+
5
+ interface OpsSummaryBarProps {
6
+ summary: OpsSessionSummary;
7
+ compact?: boolean;
8
+ }
9
+
10
+ function formatTokens(count: number): string {
11
+ if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`;
12
+ if (count >= 1000) return `${(count / 1000).toFixed(1)}k`;
13
+ return count.toString();
14
+ }
15
+
16
+ function formatDuration(ms: number): string {
17
+ if (ms < 1000) return `${ms}ms`;
18
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
19
+ return `${(ms / 60000).toFixed(1)}m`;
20
+ }
21
+
22
+ export function OpsSummaryBar({ summary, compact = false }: OpsSummaryBarProps) {
23
+ // Compact mode for split view - minimal info
24
+ if (compact) {
25
+ return (
26
+ <div className="flex items-center justify-between px-3 py-1.5 bg-zinc-900/80 border-t border-zinc-800/60">
27
+ <div className="flex items-center gap-3">
28
+ <span className="text-[9px] text-zinc-500">
29
+ <span className="text-zinc-400 font-medium tabular-nums">{formatTokens(summary.totalTokens)}</span> tokens
30
+ </span>
31
+ <span className="text-[9px] text-zinc-600">·</span>
32
+ <span className="text-[9px] text-zinc-500">
33
+ <span className="text-zinc-400 font-medium tabular-nums">{formatDuration(summary.totalDuration)}</span>
34
+ </span>
35
+ </div>
36
+ {summary.errors > 0 && (
37
+ <span className="text-[9px] text-red-400 tabular-nums">{summary.errors} err</span>
38
+ )}
39
+ </div>
40
+ );
41
+ }
42
+
43
+ // Full mode for expanded view
44
+ return (
45
+ <div className="flex items-center justify-between px-4 py-2.5 bg-zinc-900/80 border-t border-zinc-800/80">
46
+ {/* Tokens */}
47
+ <div className="flex items-center gap-4">
48
+ <div className="flex items-center gap-2">
49
+ <span className="text-[10px] uppercase tracking-wider text-zinc-600">Tokens</span>
50
+ <span className="text-xs font-medium text-zinc-300 tabular-nums">
51
+ {formatTokens(summary.totalTokens)}
52
+ </span>
53
+ <span className="text-[10px] text-zinc-600">
54
+ ({formatTokens(summary.inputTokens)} in / {formatTokens(summary.outputTokens)} out)
55
+ </span>
56
+ </div>
57
+ </div>
58
+
59
+ {/* Operations */}
60
+ <div className="flex items-center gap-4">
61
+ <div className="flex items-center gap-1.5">
62
+ <span className="text-[10px] text-zinc-600">LLM</span>
63
+ <span className="text-xs font-medium text-zinc-400 tabular-nums">{summary.llmCalls}</span>
64
+ </div>
65
+ <div className="flex items-center gap-1.5">
66
+ <span className="text-[10px] text-zinc-600">Tools</span>
67
+ <span className="text-xs font-medium text-zinc-400 tabular-nums">{summary.toolCalls}</span>
68
+ </div>
69
+ <div className="flex items-center gap-1.5">
70
+ <span className="text-[10px] text-zinc-600">Widgets</span>
71
+ <span className="text-xs font-medium text-zinc-400 tabular-nums">{summary.widgetRenders}</span>
72
+ </div>
73
+ {summary.errors > 0 && (
74
+ <div className="flex items-center gap-1.5">
75
+ <span className="text-[10px] text-red-500">Errors</span>
76
+ <span className="text-xs font-medium text-red-400 tabular-nums">{summary.errors}</span>
77
+ </div>
78
+ )}
79
+ </div>
80
+
81
+ {/* Duration */}
82
+ <div className="flex items-center gap-2">
83
+ <span className="text-[10px] uppercase tracking-wider text-zinc-600">Duration</span>
84
+ <span className="text-xs font-medium text-zinc-300 tabular-nums">
85
+ {formatDuration(summary.totalDuration)}
86
+ </span>
87
+ </div>
88
+ </div>
89
+ );
90
+ }
@@ -0,0 +1,5 @@
1
+ export { OpsCanvas } from './OpsCanvas';
2
+ export { OpsSummaryBar } from './OpsSummaryBar';
3
+ export { OpsNodeDetailPanel } from './OpsNodeDetailPanel';
4
+ export * from './nodes';
5
+
@@ -0,0 +1,65 @@
1
+ 'use client';
2
+
3
+ import { Handle, Position } from 'reactflow';
4
+ import type { OpsNodeStatus } from '@/lib/ops-types';
5
+
6
+ interface BaseNodeProps {
7
+ children: React.ReactNode;
8
+ color: string;
9
+ status?: OpsNodeStatus;
10
+ selected?: boolean;
11
+ }
12
+
13
+ const statusIndicators: Record<OpsNodeStatus, { color: string; animate: boolean }> = {
14
+ pending: { color: '#71717a', animate: false },
15
+ running: { color: '#6366f1', animate: true },
16
+ success: { color: '#22c55e', animate: false },
17
+ error: { color: '#ef4444', animate: false },
18
+ };
19
+
20
+ export function BaseNode({ children, color, status = 'success', selected }: BaseNodeProps) {
21
+ const indicator = statusIndicators[status];
22
+
23
+ return (
24
+ <div
25
+ className={`
26
+ relative min-w-[140px] bg-zinc-900 rounded-lg border transition-all
27
+ ${selected ? 'border-zinc-600 shadow-lg shadow-black/40' : 'border-zinc-800 hover:border-zinc-700'}
28
+ `}
29
+ style={{
30
+ boxShadow: selected ? `0 0 0 1px ${color}30` : undefined,
31
+ }}
32
+ >
33
+ {/* Left accent line */}
34
+ <div
35
+ className="absolute left-0 top-2 bottom-2 w-0.5 rounded-full"
36
+ style={{ backgroundColor: color }}
37
+ />
38
+
39
+ {/* Status indicator */}
40
+ <div className="absolute -top-1 -right-1">
41
+ <div
42
+ className={`w-2 h-2 rounded-full ${indicator.animate ? 'animate-pulse' : ''}`}
43
+ style={{ backgroundColor: indicator.color }}
44
+ />
45
+ </div>
46
+
47
+ {/* Content */}
48
+ <div className="px-3 py-2.5 pl-4">
49
+ {children}
50
+ </div>
51
+
52
+ {/* Handles */}
53
+ <Handle
54
+ type="target"
55
+ position={Position.Left}
56
+ className="!w-2 !h-2 !bg-zinc-700 !border-zinc-600"
57
+ />
58
+ <Handle
59
+ type="source"
60
+ position={Position.Right}
61
+ className="!w-2 !h-2 !bg-zinc-700 !border-zinc-600"
62
+ />
63
+ </div>
64
+ );
65
+ }
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+
3
+ import type { NodeProps } from 'reactflow';
4
+ import { BaseNode } from './BaseNode';
5
+ import { OPS_NODE_HEX_COLORS } from '@/lib/ops-types';
6
+ import type { LLMCallNodeData } from '@/lib/ops-types';
7
+ import { SparklesIcon } from '@heroicons/react/24/outline';
8
+
9
+ function formatTokens(count?: number): string {
10
+ if (!count) return '0';
11
+ if (count >= 1000) return `${(count / 1000).toFixed(1)}k`;
12
+ return count.toString();
13
+ }
14
+
15
+ export function LLMCallNode({ data, selected }: NodeProps<LLMCallNodeData>) {
16
+ return (
17
+ <BaseNode color={OPS_NODE_HEX_COLORS.llm_call} status={data.status} selected={selected}>
18
+ <div className="flex items-start gap-2">
19
+ <SparklesIcon className="w-3.5 h-3.5 text-purple-400 flex-shrink-0 mt-0.5" />
20
+ <div className="min-w-0">
21
+ <div className="text-[10px] font-medium text-zinc-500 uppercase tracking-wider mb-0.5">
22
+ LLM Call
23
+ </div>
24
+ <div className="text-xs text-zinc-300">{data.model}</div>
25
+ {data.totalTokens && (
26
+ <div className="text-[10px] text-zinc-500 mt-1 tabular-nums">
27
+ {formatTokens(data.totalTokens)} tokens
28
+ </div>
29
+ )}
30
+ </div>
31
+ </div>
32
+ </BaseNode>
33
+ );
34
+ }
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+
3
+ import type { NodeProps } from 'reactflow';
4
+ import { BaseNode } from './BaseNode';
5
+ import { OPS_NODE_HEX_COLORS } from '@/lib/ops-types';
6
+ import type { LLMResponseNodeData } from '@/lib/ops-types';
7
+ import { ChatBubbleLeftRightIcon } from '@heroicons/react/24/outline';
8
+
9
+ export function LLMResponseNode({ data, selected }: NodeProps<LLMResponseNodeData>) {
10
+ const content = data.content || '';
11
+ const preview = content.length > 50 ? content.substring(0, 50) + '...' : content;
12
+
13
+ return (
14
+ <BaseNode color={OPS_NODE_HEX_COLORS.llm_response} status={data.status} selected={selected}>
15
+ <div className="flex items-start gap-2">
16
+ <ChatBubbleLeftRightIcon className="w-3.5 h-3.5 text-amber-400 flex-shrink-0 mt-0.5" />
17
+ <div className="min-w-0">
18
+ <div className="text-[10px] font-medium text-zinc-500 uppercase tracking-wider mb-0.5">
19
+ Response
20
+ </div>
21
+ <div className="text-xs text-zinc-300 leading-relaxed">
22
+ {preview || 'Empty response'}
23
+ </div>
24
+ {data.tokenCount && (
25
+ <div className="text-[10px] text-zinc-500 mt-1 tabular-nums">
26
+ {data.tokenCount} tokens
27
+ </div>
28
+ )}
29
+ </div>
30
+ </div>
31
+ </BaseNode>
32
+ );
33
+ }
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+
3
+ import type { NodeProps } from 'reactflow';
4
+ import { BaseNode } from './BaseNode';
5
+ import { OPS_NODE_HEX_COLORS } from '@/lib/ops-types';
6
+ import type { ToolCallNodeData } from '@/lib/ops-types';
7
+ import { WrenchScrewdriverIcon } from '@heroicons/react/24/outline';
8
+
9
+ export function ToolCallNode({ data, selected }: NodeProps<ToolCallNodeData>) {
10
+ const argCount = Object.keys(data.toolArgs || {}).length;
11
+
12
+ return (
13
+ <BaseNode color={OPS_NODE_HEX_COLORS.tool_call} status={data.status} selected={selected}>
14
+ <div className="flex items-start gap-2">
15
+ <WrenchScrewdriverIcon className="w-3.5 h-3.5 text-orange-400 flex-shrink-0 mt-0.5" />
16
+ <div className="min-w-0">
17
+ <div className="text-[10px] font-medium text-zinc-500 uppercase tracking-wider mb-0.5">
18
+ Tool
19
+ </div>
20
+ <div className="text-xs text-zinc-300 font-mono">{data.toolName}</div>
21
+ {argCount > 0 && (
22
+ <div className="text-[10px] text-zinc-500 mt-1">
23
+ {argCount} {argCount === 1 ? 'arg' : 'args'}
24
+ </div>
25
+ )}
26
+ </div>
27
+ </div>
28
+ </BaseNode>
29
+ );
30
+ }
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+
3
+ import type { NodeProps } from 'reactflow';
4
+ import { BaseNode } from './BaseNode';
5
+ import { OPS_NODE_HEX_COLORS } from '@/lib/ops-types';
6
+ import type { ToolResultNodeData } from '@/lib/ops-types';
7
+ import { CheckCircleIcon, XCircleIcon } from '@heroicons/react/24/outline';
8
+
9
+ function formatSize(bytes?: number): string {
10
+ if (!bytes) return '0 B';
11
+ if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
12
+ if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
13
+ return `${bytes} B`;
14
+ }
15
+
16
+ export function ToolResultNode({ data, selected }: NodeProps<ToolResultNodeData>) {
17
+ const hasError = data.status === 'error' || !!data.error;
18
+
19
+ return (
20
+ <BaseNode color={OPS_NODE_HEX_COLORS.tool_result} status={data.status} selected={selected}>
21
+ <div className="flex items-start gap-2">
22
+ {hasError ? (
23
+ <XCircleIcon className="w-3.5 h-3.5 text-red-400 flex-shrink-0 mt-0.5" />
24
+ ) : (
25
+ <CheckCircleIcon className="w-3.5 h-3.5 text-green-400 flex-shrink-0 mt-0.5" />
26
+ )}
27
+ <div className="min-w-0">
28
+ <div className="text-[10px] font-medium text-zinc-500 uppercase tracking-wider mb-0.5">
29
+ Result
30
+ </div>
31
+ <div className={`text-xs ${hasError ? 'text-red-400' : 'text-zinc-300'}`}>
32
+ {hasError ? 'Error' : 'Success'}
33
+ </div>
34
+ {data.resultSize && (
35
+ <div className="text-[10px] text-zinc-500 mt-1 tabular-nums">
36
+ {formatSize(data.resultSize)}
37
+ </div>
38
+ )}
39
+ </div>
40
+ </div>
41
+ </BaseNode>
42
+ );
43
+ }
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+
3
+ import type { NodeProps } from 'reactflow';
4
+ import { BaseNode } from './BaseNode';
5
+ import { OPS_NODE_HEX_COLORS } from '@/lib/ops-types';
6
+ import type { UserPromptNodeData } from '@/lib/ops-types';
7
+ import { ChatBubbleLeftIcon, PhotoIcon } from '@heroicons/react/24/outline';
8
+
9
+ export function UserPromptNode({ data, selected }: NodeProps<UserPromptNodeData>) {
10
+ const content = data.content || '';
11
+ const preview = content.length > 40 ? content.substring(0, 40) + '...' : content;
12
+
13
+ return (
14
+ <BaseNode color={OPS_NODE_HEX_COLORS.user_prompt} status={data.status} selected={selected}>
15
+ <div className="flex items-start gap-2">
16
+ <ChatBubbleLeftIcon className="w-3.5 h-3.5 text-blue-400 flex-shrink-0 mt-0.5" />
17
+ <div className="min-w-0">
18
+ <div className="text-[10px] font-medium text-zinc-500 uppercase tracking-wider mb-0.5">
19
+ Prompt
20
+ </div>
21
+ <div className="text-xs text-zinc-300 leading-relaxed break-words">
22
+ {preview}
23
+ </div>
24
+ {data.hasImage && (
25
+ <div className="flex items-center gap-1 mt-1.5 text-zinc-500">
26
+ <PhotoIcon className="w-3 h-3" />
27
+ <span className="text-[10px]">Image attached</span>
28
+ </div>
29
+ )}
30
+ </div>
31
+ </div>
32
+ </BaseNode>
33
+ );
34
+ }
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+ import type { NodeProps } from 'reactflow';
4
+ import { BaseNode } from './BaseNode';
5
+ import { OPS_NODE_HEX_COLORS } from '@/lib/ops-types';
6
+ import type { WidgetRenderNodeData } from '@/lib/ops-types';
7
+ import { CubeIcon } from '@heroicons/react/24/outline';
8
+
9
+ export function WidgetRenderNode({ data, selected }: NodeProps<WidgetRenderNodeData>) {
10
+ return (
11
+ <BaseNode color={OPS_NODE_HEX_COLORS.widget_render} status={data.status} selected={selected}>
12
+ <div className="flex items-start gap-2">
13
+ <CubeIcon className="w-3.5 h-3.5 text-cyan-400 flex-shrink-0 mt-0.5" />
14
+ <div className="min-w-0">
15
+ <div className="text-[10px] font-medium text-zinc-500 uppercase tracking-wider mb-0.5">
16
+ Widget
17
+ </div>
18
+ <div className="text-xs text-zinc-300">{data.componentName}</div>
19
+ </div>
20
+ </div>
21
+ </BaseNode>
22
+ );
23
+ }
@@ -0,0 +1,8 @@
1
+ export { BaseNode } from './BaseNode';
2
+ export { UserPromptNode } from './UserPromptNode';
3
+ export { LLMCallNode } from './LLMCallNode';
4
+ export { ToolCallNode } from './ToolCallNode';
5
+ export { ToolResultNode } from './ToolResultNode';
6
+ export { WidgetRenderNode } from './WidgetRenderNode';
7
+ export { LLMResponseNode } from './LLMResponseNode';
8
+