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
@@ -1,164 +1,77 @@
1
1
  'use client';
2
2
 
3
- import { useEffect, useRef, useState } from 'react';
4
- import { getWidgetUrl, createWidgetHTML, postMessageToWidget } from '@/lib/widget-loader';
3
+ import { useEffect, useRef, useState, useCallback } from 'react';
4
+ import { createWidgetHTML } from '@/lib/widget-loader';
5
5
 
6
6
  interface WidgetRendererProps {
7
7
  uri: string;
8
- data: any;
8
+ data: unknown;
9
9
  className?: string;
10
10
  }
11
11
 
12
12
  export function WidgetRenderer({ uri, data, className = '' }: WidgetRendererProps) {
13
13
  const iframeRef = useRef<HTMLIFrameElement>(null);
14
- const [contentHeight, setContentHeight] = useState<number>(200); // Start with smaller default
14
+ const [contentHeight, setContentHeight] = useState<number>(100); // Start small, let widget report its actual size
15
+ const [hasError, setHasError] = useState(false);
16
+ const [isLoaded, setIsLoaded] = useState(false);
17
+ const messageHandlerRef = useRef<((event: MessageEvent) => void) | null>(null);
18
+ const mountedRef = useRef(true);
15
19
 
16
20
  // Check if we're in dev mode (localhost)
17
21
  const isDevMode =
18
22
  typeof window !== 'undefined' &&
19
23
  (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');
20
24
 
25
+ // Minimum height to prevent collapse
26
+ const MIN_HEIGHT = 50;
27
+
28
+ // Stable handler for resize messages - no max height limit
29
+ const handleResize = useCallback((height: number) => {
30
+ if (!mountedRef.current) return;
31
+ if (height && typeof height === 'number' && height > 0) {
32
+ // Allow widget to be any height it needs
33
+ const newHeight = Math.max(MIN_HEIGHT, height);
34
+ setContentHeight(newHeight);
35
+ }
36
+ }, []);
37
+
21
38
  useEffect(() => {
39
+ mountedRef.current = true;
40
+
22
41
  if (!iframeRef.current) return;
23
42
 
24
- const injectOpenAiRuntime = (iframe: HTMLIFrameElement) => {
25
- if (!iframe.contentWindow) return;
43
+ const iframe = iframeRef.current;
44
+ const timeoutIds: NodeJS.Timeout[] = [];
26
45
 
27
- // Detect system theme
28
- const getSystemTheme = (): 'light' | 'dark' => {
29
- if (typeof window === 'undefined') return 'light';
30
- return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
31
- };
46
+ // Create message handler that checks if component is still mounted
47
+ const handleMessage = (event: MessageEvent) => {
48
+ if (!mountedRef.current) return;
32
49
 
33
- // Detect device capabilities
34
- const getUserAgent = () => ({
35
- device: {
36
- type: (window.matchMedia('(max-width: 768px)').matches
37
- ? 'mobile'
38
- : window.matchMedia('(max-width: 1024px)').matches
39
- ? 'tablet'
40
- : 'desktop') as 'mobile' | 'tablet' | 'desktop',
41
- },
42
- capabilities: {
43
- hover: window.matchMedia('(hover: hover)').matches,
44
- touch: window.matchMedia('(pointer: coarse)').matches,
45
- },
46
- });
47
-
48
- // Create window.openai polyfill
49
- const openaiRuntime = {
50
- // Data
51
- toolInput: {},
52
- toolOutput: data,
53
- toolResponseMetadata: null,
54
- widgetState: null,
55
-
56
- // Visuals
57
- theme: getSystemTheme(),
58
- locale: navigator.language || 'en-US',
59
- userAgent: getUserAgent(),
60
-
61
- // Layout
62
- maxHeight: 450, // Match iframe height
63
- displayMode: 'inline' as const,
64
- safeArea: {
65
- insets: { top: 0, bottom: 0, left: 0, right: 0 },
66
- },
67
-
68
- // State management
69
- setWidgetState: async (state: any) => {
70
- console.log('📦 Widget state updated:', state);
71
- openaiRuntime.widgetState = state;
72
-
73
- // Dispatch event for hooks to react
74
- const event = new CustomEvent('openai:set_globals', {
75
- detail: { globals: { widgetState: state } },
76
- });
77
- iframe.contentWindow?.dispatchEvent(event);
78
-
79
- // TODO: Persist to chat message context
80
- },
81
-
82
- // Actions
83
- callTool: async (name: string, args: Record<string, unknown>) => {
84
- console.log('🔧 Widget calling tool:', name, args);
85
- // TODO: Implement tool call via studio API
86
- return { result: 'Tool call not yet implemented' };
87
- },
88
-
89
- sendFollowUpMessage: async ({ prompt }: { prompt: string }) => {
90
- console.log('💬 Widget sending follow-up:', prompt);
91
- // TODO: Implement follow-up message
92
- },
93
-
94
- openExternal: ({ href }: { href: string }) => {
95
- window.open(href, '_blank', 'noopener,noreferrer');
96
- },
97
-
98
- requestClose: () => {
99
- console.log('❌ Widget requested close');
100
- // TODO: Implement widget close
101
- },
102
-
103
- requestDisplayMode: async ({ mode }: { mode: 'inline' | 'pip' | 'fullscreen' }) => {
104
- console.log('🖼️ Widget requested display mode:', mode);
105
- // TODO: Implement display mode change
106
- return { mode: 'inline' as const };
107
- },
108
- };
109
-
110
- // Prepare serializable data (no functions)
111
- const openaiData = {
112
- // Data
113
- toolInput: {},
114
- toolOutput: data,
115
- toolResponseMetadata: null,
116
- widgetState: null,
117
-
118
- // Visuals
119
- theme: getSystemTheme(),
120
- locale: navigator.language || 'en-US',
121
- userAgent: getUserAgent(),
122
-
123
- // Layout
124
- maxHeight: 450,
125
- displayMode: 'inline' as const,
126
- safeArea: {
127
- insets: { top: 0, bottom: 0, left: 0, right: 0 },
128
- },
129
- };
130
-
131
- // Send openai data to iframe via postMessage (cross-origin safe)
132
- const sendData = () => {
133
- try {
134
- iframe.contentWindow?.postMessage({
135
- type: 'NITRO_INJECT_OPENAI',
136
- data: openaiData
137
- }, '*');
138
- console.log('✅ window.openai data sent to widget via postMessage');
139
- } catch (e) {
140
- console.error('❌ Failed to send window.openai:', e);
50
+ try {
51
+ // Handle widget error messages
52
+ if (event.data?.type === 'NITRO_WIDGET_ERROR') {
53
+ console.error('❌ Widget reported error:', event.data.error);
54
+ setHasError(true);
55
+ return;
141
56
  }
142
- };
143
57
 
144
- // Send immediately
145
- sendData();
58
+ // Handle resize messages from widget
59
+ if (event.data?.type === 'NITRO_WIDGET_RESIZE') {
60
+ const { height } = event.data;
61
+ handleResize(height);
62
+ }
146
63
 
147
- // Retry after delays to ensure widget React app has mounted
148
- setTimeout(sendData, 100);
149
- setTimeout(sendData, 300);
150
- setTimeout(sendData, 500);
64
+ // Handle widget ready message
65
+ if (event.data?.type === 'NITRO_WIDGET_READY') {
66
+ setIsLoaded(true);
67
+ }
151
68
 
152
- // Set up message listener for widget RPC calls
153
- const handleWidgetMessage = (event: MessageEvent) => {
69
+ // Handle RPC calls
154
70
  if (event.data?.type === 'NITRO_WIDGET_RPC') {
155
71
  const { method, args, id } = event.data;
156
72
 
157
73
  switch (method) {
158
74
  case 'setWidgetState':
159
- console.log('📦 Widget state updated:', args[0]);
160
- openaiData.widgetState = args[0];
161
- // Send response
162
75
  iframe.contentWindow?.postMessage({
163
76
  type: 'NITRO_WIDGET_RPC_RESPONSE',
164
77
  id,
@@ -167,8 +80,6 @@ export function WidgetRenderer({ uri, data, className = '' }: WidgetRendererProp
167
80
  break;
168
81
 
169
82
  case 'callTool':
170
- console.log('🔧 Widget calling tool:', args[0], args[1]);
171
- // Dispatch event for chat page to handle
172
83
  window.dispatchEvent(new CustomEvent('widget-tool-call', {
173
84
  detail: { toolName: args[0], toolArgs: args[1] }
174
85
  }));
@@ -189,9 +100,7 @@ export function WidgetRenderer({ uri, data, className = '' }: WidgetRendererProp
189
100
  break;
190
101
 
191
102
  case 'requestDisplayMode':
192
- console.log('🖼️ Widget requested display mode:', args[0].mode);
193
103
  if (args[0].mode === 'fullscreen') {
194
- // Dispatch custom event for chat page to handle
195
104
  window.dispatchEvent(new CustomEvent('widget-fullscreen-request', {
196
105
  detail: { uri, data }
197
106
  }));
@@ -204,126 +113,176 @@ export function WidgetRenderer({ uri, data, className = '' }: WidgetRendererProp
204
113
  break;
205
114
  }
206
115
  }
116
+ } catch (e) {
117
+ console.error('❌ Error handling widget message:', e);
118
+ }
119
+ };
207
120
 
208
- // Handle resize messages from widget
209
- if (event.data?.type === 'NITRO_WIDGET_RESIZE') {
210
- const { height } = event.data;
211
- console.log('📏 Received widget resize:', height);
212
- if (height && typeof height === 'number') {
213
- const newHeight = Math.min(height, 400);
214
- console.log('📏 Setting content height to:', newHeight);
215
- setContentHeight(newHeight); // Cap at 400px max
216
- }
217
- }
218
- };
121
+ messageHandlerRef.current = handleMessage;
122
+ window.addEventListener('message', handleMessage);
123
+
124
+ // Function to send data to iframe
125
+ const sendDataToIframe = () => {
126
+ if (!mountedRef.current || !iframe.contentWindow) return;
127
+
128
+ try {
129
+ const getSystemTheme = (): 'light' | 'dark' => {
130
+ if (typeof window === 'undefined') return 'light';
131
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
132
+ };
133
+
134
+ const getUserAgent = () => ({
135
+ device: {
136
+ type: (window.matchMedia('(max-width: 768px)').matches
137
+ ? 'mobile'
138
+ : window.matchMedia('(max-width: 1024px)').matches
139
+ ? 'tablet'
140
+ : 'desktop') as 'mobile' | 'tablet' | 'desktop',
141
+ },
142
+ capabilities: {
143
+ hover: window.matchMedia('(hover: hover)').matches,
144
+ touch: window.matchMedia('(pointer: coarse)').matches,
145
+ },
146
+ });
147
+
148
+ const openaiData = {
149
+ toolInput: {},
150
+ toolOutput: data,
151
+ toolResponseMetadata: null,
152
+ widgetState: null,
153
+ theme: getSystemTheme(),
154
+ locale: navigator.language || 'en-US',
155
+ userAgent: getUserAgent(),
156
+ maxHeight: undefined, // No height limit - widget determines its own height
157
+ displayMode: 'inline' as const,
158
+ safeArea: {
159
+ insets: { top: 0, bottom: 0, left: 0, right: 0 },
160
+ },
161
+ };
162
+
163
+ iframe.contentWindow.postMessage({
164
+ type: 'NITRO_INJECT_OPENAI',
165
+ data: openaiData
166
+ }, '*');
167
+
168
+ // Also send legacy format
169
+ iframe.contentWindow.postMessage({
170
+ type: 'toolOutput',
171
+ data,
172
+ }, '*');
173
+ } catch (e) {
174
+ console.error('❌ Failed to send data to widget:', e);
175
+ }
176
+ };
177
+
178
+ const handleIframeLoad = () => {
179
+ if (!mountedRef.current) return;
180
+ setIsLoaded(true);
219
181
 
220
- window.addEventListener('message', handleWidgetMessage);
182
+ // Send data with retries
183
+ sendDataToIframe();
184
+ const t1 = setTimeout(sendDataToIframe, 100);
185
+ const t2 = setTimeout(sendDataToIframe, 300);
186
+ const t3 = setTimeout(sendDataToIframe, 500);
187
+ timeoutIds.push(t1, t2, t3);
221
188
  };
222
189
 
190
+ const handleIframeError = () => {
191
+ if (!mountedRef.current) return;
192
+ console.error('❌ Widget iframe error:', uri);
193
+ setHasError(true);
194
+ };
195
+
196
+ iframe.onload = handleIframeLoad;
197
+ iframe.onerror = handleIframeError;
198
+
223
199
  if (isDevMode) {
224
200
  // Dev mode: load from widget dev server (port 3001)
225
201
  let widgetPath = uri;
226
202
 
227
- // Handle ui://widget/ URIs
228
203
  if (widgetPath.startsWith('ui://widget/')) {
229
204
  widgetPath = widgetPath.replace('ui://widget/', '');
230
205
  widgetPath = widgetPath.replace(/\.html$/, '');
231
- }
232
- // Handle /widgets/ prefix (legacy)
233
- else if (widgetPath.startsWith('/widgets/')) {
206
+ } else if (widgetPath.startsWith('/widgets/')) {
234
207
  widgetPath = widgetPath.replace('/widgets/', '');
235
- }
236
- // Handle leading slash
237
- else if (widgetPath.startsWith('/')) {
208
+ } else if (widgetPath.startsWith('/')) {
238
209
  widgetPath = widgetPath.substring(1);
239
210
  }
240
211
 
241
212
  const widgetUrl = `http://localhost:3001/${widgetPath}`;
242
-
243
- console.log('Loading widget in dev mode:', { uri, widgetPath, widgetUrl, data });
244
-
245
- // Set up onload handler BEFORE setting src
246
- iframeRef.current.onload = () => {
247
- console.log('Widget iframe loaded, injecting window.openai...');
248
-
249
- // Inject window.openai runtime
250
- if (iframeRef.current) {
251
- injectOpenAiRuntime(iframeRef.current);
252
- }
253
-
254
- // Also send legacy postMessage for backward compatibility
255
- setTimeout(() => {
256
- try {
257
- iframeRef.current?.contentWindow?.postMessage(
258
- {
259
- type: 'toolOutput',
260
- data,
261
- },
262
- '*'
263
- );
264
- console.log('✅ Legacy data posted to widget:', data);
265
- } catch (e) {
266
- console.error('❌ Failed to post message to widget:', e);
267
- }
268
- }, 300);
269
- };
270
-
271
- // Set src AFTER onload handler is set
272
- iframeRef.current.src = widgetUrl;
213
+ console.log('Loading widget in dev mode:', { uri, widgetPath, widgetUrl });
214
+ iframe.src = widgetUrl;
273
215
  } else {
274
216
  // Production mode: fetch and render
275
217
  const loadProductionWidget = async () => {
218
+ if (!mountedRef.current) return;
219
+
276
220
  try {
277
- // Convert widget route/path to resource URI format if needed
278
221
  let resourceUri = uri;
279
222
  if (!uri.startsWith('widget://') && !uri.startsWith('http://') && !uri.startsWith('https://')) {
280
223
  const widgetPath = uri.startsWith('/') ? uri.substring(1) : uri;
281
224
  resourceUri = `widget://${widgetPath}`;
282
225
  }
283
226
 
284
- console.log('Loading widget in production mode:', { originalUri: uri, resourceUri, data });
285
-
286
227
  const response = await fetch(`/api/resources/${encodeURIComponent(resourceUri)}`);
228
+ if (!mountedRef.current) return;
229
+
287
230
  const result = await response.json();
288
231
 
289
232
  if (result.contents && result.contents.length > 0) {
290
233
  const html = result.contents[0].text || '';
291
234
  const completeHtml = createWidgetHTML(html, data);
292
-
293
- // Use Blob URL
294
235
  const blob = new Blob([completeHtml], { type: 'text/html' });
295
236
  const blobUrl = URL.createObjectURL(blob);
296
237
 
297
- if (iframeRef.current) {
298
- iframeRef.current.src = blobUrl;
299
- iframeRef.current.onload = () => {
300
- // Inject window.openai runtime
301
- if (iframeRef.current) {
302
- injectOpenAiRuntime(iframeRef.current);
303
- }
304
-
238
+ if (mountedRef.current && iframe) {
239
+ iframe.src = blobUrl;
240
+ // Revoke URL after load
241
+ const revokeTimeout = setTimeout(() => {
305
242
  URL.revokeObjectURL(blobUrl);
306
- };
243
+ }, 5000);
244
+ timeoutIds.push(revokeTimeout);
307
245
  }
308
- console.log('✅ Widget loaded successfully:', { resourceUri, hasHtml: !!html, hasData: !!data });
309
246
  } else {
310
247
  console.warn('⚠️ Widget resource found but no content:', { resourceUri, result });
248
+ if (mountedRef.current) setHasError(true);
311
249
  }
312
250
  } catch (error) {
313
- console.error('❌ Failed to load widget:', {
314
- originalUri: uri,
315
- error: error instanceof Error ? error.message : String(error),
316
- stack: error instanceof Error ? error.stack : undefined
317
- });
251
+ console.error('❌ Failed to load widget:', error);
252
+ if (mountedRef.current) setHasError(true);
318
253
  }
319
254
  };
320
255
 
321
256
  loadProductionWidget();
322
257
  }
323
- }, [uri, data, isDevMode]);
324
258
 
325
- const isInChat = className?.includes('widget-in-chat');
326
- const finalHeight = isInChat ? `${contentHeight}px` : '100%';
259
+ // Cleanup function
260
+ return () => {
261
+ mountedRef.current = false;
262
+
263
+ // Clear all timeouts
264
+ timeoutIds.forEach(id => clearTimeout(id));
265
+
266
+ // Remove message listener
267
+ if (messageHandlerRef.current) {
268
+ window.removeEventListener('message', messageHandlerRef.current);
269
+ messageHandlerRef.current = null;
270
+ }
271
+
272
+ // Clear iframe handlers
273
+ if (iframe) {
274
+ iframe.onload = null;
275
+ iframe.onerror = null;
276
+ }
277
+ };
278
+ }, [uri, data, isDevMode, handleResize]);
279
+
280
+ // If there's an error, don't render the widget at all
281
+ if (hasError) {
282
+ return null;
283
+ }
284
+
285
+ const effectiveHeight = Math.max(contentHeight, MIN_HEIGHT);
327
286
 
328
287
  return (
329
288
  <iframe
@@ -332,8 +291,8 @@ export function WidgetRenderer({ uri, data, className = '' }: WidgetRendererProp
332
291
  sandbox="allow-scripts allow-same-origin"
333
292
  style={{
334
293
  width: '100%',
335
- height: finalHeight,
336
- maxHeight: isInChat ? '400px' : '100%',
294
+ height: `${effectiveHeight}px`,
295
+ minHeight: `${MIN_HEIGHT}px`,
337
296
  border: 'none',
338
297
  background: 'transparent',
339
298
  overflow: 'hidden',
@@ -343,4 +302,3 @@ export function WidgetRenderer({ uri, data, className = '' }: WidgetRendererProp
343
302
  />
344
303
  );
345
304
  }
346
-