nitrostack 1.0.72 → 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 (240) 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 +31 -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 +3 -2
  203. package/src/studio/app/chat/page.tsx +481 -105
  204. package/src/studio/app/health/page.tsx +1 -1
  205. package/src/studio/app/logs/page.tsx +2 -2
  206. package/src/studio/app/page.tsx +5 -5
  207. package/src/studio/app/prompts/page.tsx +2 -2
  208. package/src/studio/app/settings/page.tsx +3 -2
  209. package/src/studio/app/tools/page.tsx +3 -3
  210. package/src/studio/components/LogMessage.tsx +1 -1
  211. package/src/studio/components/MarkdownRenderer.tsx +245 -348
  212. package/src/studio/components/Sidebar.tsx +18 -3
  213. package/src/studio/components/VoiceOrbOverlay.tsx +12 -6
  214. package/src/studio/components/WidgetErrorBoundary.tsx +48 -0
  215. package/src/studio/components/WidgetRenderer.tsx +168 -215
  216. package/src/studio/components/ops/OpsCanvas.tsx +748 -0
  217. package/src/studio/components/ops/OpsNodeDetailPanel.tsx +150 -0
  218. package/src/studio/components/ops/OpsSummaryBar.tsx +90 -0
  219. package/src/studio/components/ops/index.ts +5 -0
  220. package/src/studio/components/ops/nodes/BaseNode.tsx +65 -0
  221. package/src/studio/components/ops/nodes/LLMCallNode.tsx +34 -0
  222. package/src/studio/components/ops/nodes/LLMResponseNode.tsx +33 -0
  223. package/src/studio/components/ops/nodes/ToolCallNode.tsx +30 -0
  224. package/src/studio/components/ops/nodes/ToolResultNode.tsx +43 -0
  225. package/src/studio/components/ops/nodes/UserPromptNode.tsx +34 -0
  226. package/src/studio/components/ops/nodes/WidgetRenderNode.tsx +23 -0
  227. package/src/studio/components/ops/nodes/index.ts +8 -0
  228. package/src/studio/components/tools/ToolsCanvas.tsx +2 -2
  229. package/src/studio/lib/api.ts +61 -42
  230. package/src/studio/lib/http-client-transport.ts +2 -2
  231. package/src/studio/lib/llm-service.ts +126 -47
  232. package/src/studio/lib/mcp-client.ts +9 -6
  233. package/src/studio/lib/ops-store.ts +427 -0
  234. package/src/studio/lib/ops-tracker.ts +416 -0
  235. package/src/studio/lib/ops-types.ts +164 -0
  236. package/src/studio/lib/store.ts +8 -11
  237. package/src/studio/lib/types.ts +228 -38
  238. package/src/studio/lib/widget-loader.ts +2 -2
  239. package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +19 -22
  240. package/dist/cli/build-widgets.mjs +0 -165
@@ -93,9 +93,24 @@ export function Sidebar() {
93
93
  window.dispatchEvent(new Event('sidebar-toggle'));
94
94
  };
95
95
 
96
- const renderNavItem = (item: any, depth = 0) => {
96
+ interface NavChild {
97
+ id: string;
98
+ label: string;
99
+ icon: React.ComponentType<{ className?: string }>;
100
+ path: string;
101
+ }
102
+
103
+ interface NavItem {
104
+ id: string;
105
+ label: string;
106
+ icon: React.ComponentType<{ className?: string }>;
107
+ path: string;
108
+ children?: NavChild[];
109
+ }
110
+
111
+ const renderNavItem = (item: NavItem, depth = 0) => {
97
112
  const Icon = item.icon;
98
- const isActive = pathname === item.path || (item.children && item.children.some((child: any) => pathname === child.path));
113
+ const isActive = pathname === item.path || (item.children && item.children.some((child) => pathname === child.path));
99
114
  const isExpanded = expandedItems.includes(item.id);
100
115
  const hasChildren = item.children && item.children.length > 0;
101
116
 
@@ -157,7 +172,7 @@ export function Sidebar() {
157
172
  {/* Render Children */}
158
173
  {!isCollapsed && hasChildren && isExpanded && (
159
174
  <div className="mt-0.5 space-y-0.5">
160
- {item.children.map((child: any) => renderNavItem(child, depth + 1))}
175
+ {item.children!.map((child) => renderNavItem(child as NavItem, depth + 1))}
161
176
  </div>
162
177
  )}
163
178
  </div>
@@ -46,7 +46,7 @@ export function VoiceOrbOverlay({
46
46
  const [transcript, setTranscript] = useState('');
47
47
  const [hasGreeted, setHasGreeted] = useState(false);
48
48
 
49
- const recognitionRef = useRef<any>(null);
49
+ const recognitionRef = useRef<SpeechRecognition | null>(null);
50
50
  const silenceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
51
51
  const isListeningRef = useRef(false);
52
52
 
@@ -72,20 +72,26 @@ export function VoiceOrbOverlay({
72
72
  useEffect(() => {
73
73
  if (typeof window === 'undefined') return;
74
74
 
75
- const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;
76
- if (!SpeechRecognition) {
75
+ // Web Speech API types
76
+ interface WebWindow extends Window {
77
+ SpeechRecognition?: typeof SpeechRecognition;
78
+ webkitSpeechRecognition?: typeof SpeechRecognition;
79
+ }
80
+ const webWindow = window as WebWindow;
81
+ const SpeechRecognitionCtor = webWindow.SpeechRecognition || webWindow.webkitSpeechRecognition;
82
+ if (!SpeechRecognitionCtor) {
77
83
  console.error('Speech Recognition not supported');
78
84
  return;
79
85
  }
80
86
 
81
- const recognition = new SpeechRecognition();
87
+ const recognition = new SpeechRecognitionCtor();
82
88
  recognition.continuous = false;
83
89
  recognition.interimResults = true;
84
90
  recognition.lang = inputLanguage; // Use configured input language
85
91
 
86
92
  let currentTranscript = '';
87
93
 
88
- recognition.onresult = (event: any) => {
94
+ recognition.onresult = (event: SpeechRecognitionEvent) => {
89
95
  let finalTranscript = '';
90
96
  let interimTranscript = '';
91
97
 
@@ -121,7 +127,7 @@ export function VoiceOrbOverlay({
121
127
  }
122
128
  };
123
129
 
124
- recognition.onerror = (event: any) => {
130
+ recognition.onerror = (event: SpeechRecognitionErrorEvent) => {
125
131
  console.error('Speech recognition error:', event.error);
126
132
  };
127
133
 
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+
3
+ import React, { Component, ErrorInfo, ReactNode } from 'react';
4
+
5
+ interface Props {
6
+ children: ReactNode;
7
+ fallback?: ReactNode;
8
+ }
9
+
10
+ interface State {
11
+ hasError: boolean;
12
+ error?: Error;
13
+ }
14
+
15
+ /**
16
+ * Error boundary for widgets - silently catches errors and renders nothing
17
+ * to prevent broken widgets from affecting the rest of the UI
18
+ */
19
+ export class WidgetErrorBoundary extends Component<Props, State> {
20
+ constructor(props: Props) {
21
+ super(props);
22
+ this.state = { hasError: false };
23
+ }
24
+
25
+ static getDerivedStateFromError(error: Error): State {
26
+ // Update state so the next render will show the fallback UI.
27
+ return { hasError: true, error };
28
+ }
29
+
30
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
31
+ // Log the error silently - don't show to user
32
+ console.error('🚫 Widget error caught by boundary:', {
33
+ error: error.message,
34
+ stack: error.stack,
35
+ componentStack: errorInfo.componentStack,
36
+ });
37
+ }
38
+
39
+ render() {
40
+ if (this.state.hasError) {
41
+ // Return fallback or nothing - don't show error UI
42
+ return this.props.fallback ?? null;
43
+ }
44
+
45
+ return this.props.children;
46
+ }
47
+ }
48
+
@@ -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,131 +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 isExpanded = className?.includes('widget-expanded');
214
- const max = isExpanded ? 800 : 400;
215
- const newHeight = Math.min(height, max);
216
- console.log('📏 Setting content height to:', newHeight);
217
- setContentHeight(newHeight); // Cap at max height
218
- }
219
- }
220
- };
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
+ };
221
177
 
222
- window.addEventListener('message', handleWidgetMessage);
178
+ const handleIframeLoad = () => {
179
+ if (!mountedRef.current) return;
180
+ setIsLoaded(true);
181
+
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);
188
+ };
189
+
190
+ const handleIframeError = () => {
191
+ if (!mountedRef.current) return;
192
+ console.error('❌ Widget iframe error:', uri);
193
+ setHasError(true);
223
194
  };
224
195
 
196
+ iframe.onload = handleIframeLoad;
197
+ iframe.onerror = handleIframeError;
198
+
225
199
  if (isDevMode) {
226
200
  // Dev mode: load from widget dev server (port 3001)
227
201
  let widgetPath = uri;
228
202
 
229
- // Handle ui://widget/ URIs
230
203
  if (widgetPath.startsWith('ui://widget/')) {
231
204
  widgetPath = widgetPath.replace('ui://widget/', '');
232
205
  widgetPath = widgetPath.replace(/\.html$/, '');
233
- }
234
- // Handle /widgets/ prefix (legacy)
235
- else if (widgetPath.startsWith('/widgets/')) {
206
+ } else if (widgetPath.startsWith('/widgets/')) {
236
207
  widgetPath = widgetPath.replace('/widgets/', '');
237
- }
238
- // Handle leading slash
239
- else if (widgetPath.startsWith('/')) {
208
+ } else if (widgetPath.startsWith('/')) {
240
209
  widgetPath = widgetPath.substring(1);
241
210
  }
242
211
 
243
212
  const widgetUrl = `http://localhost:3001/${widgetPath}`;
244
-
245
- console.log('Loading widget in dev mode:', { uri, widgetPath, widgetUrl, data });
246
-
247
- // Set up onload handler BEFORE setting src
248
- iframeRef.current.onload = () => {
249
- console.log('Widget iframe loaded, injecting window.openai...');
250
-
251
- // Inject window.openai runtime
252
- if (iframeRef.current) {
253
- injectOpenAiRuntime(iframeRef.current);
254
- }
255
-
256
- // Also send legacy postMessage for backward compatibility
257
- setTimeout(() => {
258
- try {
259
- iframeRef.current?.contentWindow?.postMessage(
260
- {
261
- type: 'toolOutput',
262
- data,
263
- },
264
- '*'
265
- );
266
- console.log('✅ Legacy data posted to widget:', data);
267
- } catch (e) {
268
- console.error('❌ Failed to post message to widget:', e);
269
- }
270
- }, 300);
271
- };
272
-
273
- // Set src AFTER onload handler is set
274
- iframeRef.current.src = widgetUrl;
213
+ console.log('Loading widget in dev mode:', { uri, widgetPath, widgetUrl });
214
+ iframe.src = widgetUrl;
275
215
  } else {
276
216
  // Production mode: fetch and render
277
217
  const loadProductionWidget = async () => {
218
+ if (!mountedRef.current) return;
219
+
278
220
  try {
279
- // Convert widget route/path to resource URI format if needed
280
221
  let resourceUri = uri;
281
222
  if (!uri.startsWith('widget://') && !uri.startsWith('http://') && !uri.startsWith('https://')) {
282
223
  const widgetPath = uri.startsWith('/') ? uri.substring(1) : uri;
283
224
  resourceUri = `widget://${widgetPath}`;
284
225
  }
285
226
 
286
- console.log('Loading widget in production mode:', { originalUri: uri, resourceUri, data });
287
-
288
227
  const response = await fetch(`/api/resources/${encodeURIComponent(resourceUri)}`);
228
+ if (!mountedRef.current) return;
229
+
289
230
  const result = await response.json();
290
231
 
291
232
  if (result.contents && result.contents.length > 0) {
292
233
  const html = result.contents[0].text || '';
293
234
  const completeHtml = createWidgetHTML(html, data);
294
-
295
- // Use Blob URL
296
235
  const blob = new Blob([completeHtml], { type: 'text/html' });
297
236
  const blobUrl = URL.createObjectURL(blob);
298
237
 
299
- if (iframeRef.current) {
300
- iframeRef.current.src = blobUrl;
301
- iframeRef.current.onload = () => {
302
- // Inject window.openai runtime
303
- if (iframeRef.current) {
304
- injectOpenAiRuntime(iframeRef.current);
305
- }
306
-
238
+ if (mountedRef.current && iframe) {
239
+ iframe.src = blobUrl;
240
+ // Revoke URL after load
241
+ const revokeTimeout = setTimeout(() => {
307
242
  URL.revokeObjectURL(blobUrl);
308
- };
243
+ }, 5000);
244
+ timeoutIds.push(revokeTimeout);
309
245
  }
310
- console.log('✅ Widget loaded successfully:', { resourceUri, hasHtml: !!html, hasData: !!data });
311
246
  } else {
312
247
  console.warn('⚠️ Widget resource found but no content:', { resourceUri, result });
248
+ if (mountedRef.current) setHasError(true);
313
249
  }
314
250
  } catch (error) {
315
- console.error('❌ Failed to load widget:', {
316
- originalUri: uri,
317
- error: error instanceof Error ? error.message : String(error),
318
- stack: error instanceof Error ? error.stack : undefined
319
- });
251
+ console.error('❌ Failed to load widget:', error);
252
+ if (mountedRef.current) setHasError(true);
320
253
  }
321
254
  };
322
255
 
323
256
  loadProductionWidget();
324
257
  }
325
- }, [uri, data, isDevMode]);
326
258
 
327
- const isInChat = className?.includes('widget-in-chat');
328
- const isExpanded = className?.includes('widget-expanded');
329
- const maxHeight = isExpanded ? 800 : 400;
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
+ }
330
284
 
331
- const finalHeight = isInChat ? `${contentHeight}px` : '100%';
285
+ const effectiveHeight = Math.max(contentHeight, MIN_HEIGHT);
332
286
 
333
287
  return (
334
288
  <iframe
@@ -337,8 +291,8 @@ export function WidgetRenderer({ uri, data, className = '' }: WidgetRendererProp
337
291
  sandbox="allow-scripts allow-same-origin"
338
292
  style={{
339
293
  width: '100%',
340
- height: finalHeight,
341
- maxHeight: isInChat ? `${maxHeight}px` : '100%',
294
+ height: `${effectiveHeight}px`,
295
+ minHeight: `${MIN_HEIGHT}px`,
342
296
  border: 'none',
343
297
  background: 'transparent',
344
298
  overflow: 'hidden',
@@ -348,4 +302,3 @@ export function WidgetRenderer({ uri, data, className = '' }: WidgetRendererProp
348
302
  />
349
303
  );
350
304
  }
351
-