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.
- package/dist/auth/api-key.js.map +1 -1
- package/dist/auth/client.js.map +1 -1
- package/dist/auth/index.d.ts +2 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +3 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/middleware.d.ts +1 -1
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js.map +1 -1
- package/dist/auth/secure-secret.d.ts +136 -0
- package/dist/auth/secure-secret.d.ts.map +1 -0
- package/dist/auth/secure-secret.js +182 -0
- package/dist/auth/secure-secret.js.map +1 -0
- package/dist/auth/server-metadata.d.ts.map +1 -1
- package/dist/auth/server-metadata.js.map +1 -1
- package/dist/auth/simple-jwt.d.ts +100 -14
- package/dist/auth/simple-jwt.d.ts.map +1 -1
- package/dist/auth/simple-jwt.js +19 -9
- package/dist/auth/simple-jwt.js.map +1 -1
- package/dist/auth/token-store.js +1 -1
- package/dist/auth/token-store.js.map +1 -1
- package/dist/auth/token-validation.js +1 -1
- package/dist/auth/token-validation.js.map +1 -1
- package/dist/cli/commands/build.js +1 -1
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/generate-types.js +12 -12
- package/dist/cli/commands/generate-types.js.map +1 -1
- package/dist/cli/commands/generate.d.ts +8 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +13 -12
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/init.js +1 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/upgrade.d.ts +10 -0
- package/dist/cli/commands/upgrade.d.ts.map +1 -0
- package/dist/cli/commands/upgrade.js +221 -0
- package/dist/cli/commands/upgrade.js.map +1 -0
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/app-decorator.d.ts +4 -3
- package/dist/core/app-decorator.d.ts.map +1 -1
- package/dist/core/app-decorator.js +67 -28
- package/dist/core/app-decorator.js.map +1 -1
- package/dist/core/builders.d.ts +19 -7
- package/dist/core/builders.d.ts.map +1 -1
- package/dist/core/builders.js +15 -8
- package/dist/core/builders.js.map +1 -1
- package/dist/core/component.d.ts +8 -8
- package/dist/core/component.d.ts.map +1 -1
- package/dist/core/component.js +3 -2
- package/dist/core/component.js.map +1 -1
- package/dist/core/config-module.d.ts +11 -4
- package/dist/core/config-module.d.ts.map +1 -1
- package/dist/core/config-module.js +1 -1
- package/dist/core/config-module.js.map +1 -1
- package/dist/core/decorators/cache.decorator.d.ts +9 -9
- package/dist/core/decorators/cache.decorator.d.ts.map +1 -1
- package/dist/core/decorators/cache.decorator.js +3 -3
- package/dist/core/decorators/cache.decorator.js.map +1 -1
- package/dist/core/decorators/health-check.decorator.d.ts +3 -3
- package/dist/core/decorators/health-check.decorator.d.ts.map +1 -1
- package/dist/core/decorators/health-check.decorator.js +2 -2
- package/dist/core/decorators/health-check.decorator.js.map +1 -1
- package/dist/core/decorators/rate-limit.decorator.d.ts +5 -4
- package/dist/core/decorators/rate-limit.decorator.d.ts.map +1 -1
- package/dist/core/decorators/rate-limit.decorator.js +3 -3
- package/dist/core/decorators/rate-limit.decorator.js.map +1 -1
- package/dist/core/decorators.d.ts +47 -29
- package/dist/core/decorators.d.ts.map +1 -1
- package/dist/core/decorators.js +9 -9
- package/dist/core/decorators.js.map +1 -1
- package/dist/core/di/container.d.ts +21 -4
- package/dist/core/di/container.d.ts.map +1 -1
- package/dist/core/di/container.js +11 -7
- package/dist/core/di/container.js.map +1 -1
- package/dist/core/di/injectable.decorator.d.ts +5 -3
- package/dist/core/di/injectable.decorator.d.ts.map +1 -1
- package/dist/core/di/injectable.decorator.js.map +1 -1
- package/dist/core/errors.d.ts +4 -4
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/events/event-emitter.d.ts +3 -3
- package/dist/core/events/event-emitter.d.ts.map +1 -1
- package/dist/core/events/event-emitter.js.map +1 -1
- package/dist/core/events/event.decorator.d.ts +5 -5
- package/dist/core/events/event.decorator.d.ts.map +1 -1
- package/dist/core/events/event.decorator.js +10 -6
- package/dist/core/events/event.decorator.js.map +1 -1
- package/dist/core/events/log-emitter.d.ts +7 -1
- package/dist/core/events/log-emitter.d.ts.map +1 -1
- package/dist/core/events/log-emitter.js.map +1 -1
- package/dist/core/filters/exception-filter.decorator.d.ts +5 -5
- package/dist/core/filters/exception-filter.decorator.d.ts.map +1 -1
- package/dist/core/filters/exception-filter.decorator.js +3 -3
- package/dist/core/filters/exception-filter.decorator.js.map +1 -1
- package/dist/core/filters/exception-filter.interface.d.ts +14 -5
- package/dist/core/filters/exception-filter.interface.d.ts.map +1 -1
- package/dist/core/guards/apikey.guard.d.ts +1 -1
- package/dist/core/guards/apikey.guard.d.ts.map +1 -1
- package/dist/core/guards/guard.interface.d.ts +1 -1
- package/dist/core/guards/guard.interface.d.ts.map +1 -1
- package/dist/core/guards/jwt.guard.d.ts +1 -1
- package/dist/core/guards/jwt.guard.d.ts.map +1 -1
- package/dist/core/guards/oauth.guard.d.ts +1 -1
- package/dist/core/guards/oauth.guard.d.ts.map +1 -1
- package/dist/core/guards/use-guards.decorator.d.ts +3 -3
- package/dist/core/guards/use-guards.decorator.d.ts.map +1 -1
- package/dist/core/guards/use-guards.decorator.js +1 -1
- package/dist/core/guards/use-guards.decorator.js.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/interceptors/interceptor.decorator.d.ts +4 -4
- package/dist/core/interceptors/interceptor.decorator.d.ts.map +1 -1
- package/dist/core/interceptors/interceptor.decorator.js +2 -2
- package/dist/core/interceptors/interceptor.decorator.js.map +1 -1
- package/dist/core/interceptors/interceptor.interface.d.ts +3 -3
- package/dist/core/interceptors/interceptor.interface.d.ts.map +1 -1
- package/dist/core/logger.d.ts.map +1 -1
- package/dist/core/logger.js.map +1 -1
- package/dist/core/middleware/middleware.decorator.d.ts +4 -4
- package/dist/core/middleware/middleware.decorator.d.ts.map +1 -1
- package/dist/core/middleware/middleware.decorator.js +2 -2
- package/dist/core/middleware/middleware.decorator.js.map +1 -1
- package/dist/core/middleware/middleware.interface.d.ts +3 -3
- package/dist/core/middleware/middleware.interface.d.ts.map +1 -1
- package/dist/core/module.d.ts +33 -14
- package/dist/core/module.d.ts.map +1 -1
- package/dist/core/module.js +11 -6
- package/dist/core/module.js.map +1 -1
- package/dist/core/oauth-module.d.ts +9 -3
- package/dist/core/oauth-module.d.ts.map +1 -1
- package/dist/core/oauth-module.js +4 -3
- package/dist/core/oauth-module.js.map +1 -1
- package/dist/core/pipes/pipe.decorator.d.ts +14 -5
- package/dist/core/pipes/pipe.decorator.d.ts.map +1 -1
- package/dist/core/pipes/pipe.decorator.js +2 -2
- package/dist/core/pipes/pipe.decorator.js.map +1 -1
- package/dist/core/pipes/pipe.interface.d.ts +9 -4
- package/dist/core/pipes/pipe.interface.d.ts.map +1 -1
- package/dist/core/prompt.d.ts +13 -4
- package/dist/core/prompt.d.ts.map +1 -1
- package/dist/core/prompt.js +2 -2
- package/dist/core/prompt.js.map +1 -1
- package/dist/core/resource.d.ts +7 -2
- package/dist/core/resource.d.ts.map +1 -1
- package/dist/core/resource.js +2 -2
- package/dist/core/resource.js.map +1 -1
- package/dist/core/server.d.ts +49 -3
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +61 -34
- package/dist/core/server.js.map +1 -1
- package/dist/core/tool.d.ts +44 -16
- package/dist/core/tool.d.ts.map +1 -1
- package/dist/core/tool.js +19 -6
- package/dist/core/tool.js.map +1 -1
- package/dist/core/transports/discovery-http-server.d.ts +7 -1
- package/dist/core/transports/discovery-http-server.d.ts.map +1 -1
- package/dist/core/transports/discovery-http-server.js.map +1 -1
- package/dist/core/transports/http-server.d.ts +2 -2
- package/dist/core/transports/http-server.d.ts.map +1 -1
- package/dist/core/transports/http-server.js +1 -1
- package/dist/core/transports/http-server.js.map +1 -1
- package/dist/core/transports/streamable-http.d.ts +4 -4
- package/dist/core/transports/streamable-http.d.ts.map +1 -1
- package/dist/core/transports/streamable-http.js +1 -1
- package/dist/core/transports/streamable-http.js.map +1 -1
- package/dist/core/types.d.ts +87 -15
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/widgets/widget-registry.d.ts +2 -2
- package/dist/core/widgets/widget-registry.d.ts.map +1 -1
- package/dist/core/widgets/widget-registry.js +1 -1
- package/dist/core/widgets/widget-registry.js.map +1 -1
- package/dist/testing/index.d.ts +44 -17
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +5 -8
- package/dist/testing/index.js.map +1 -1
- package/dist/ui-next/index.d.ts +1 -1
- package/dist/ui-next/index.d.ts.map +1 -1
- package/dist/ui-next/index.js.map +1 -1
- package/dist/widgets/hooks/useWidgetSDK.d.ts +5 -5
- package/dist/widgets/runtime/WidgetLayout.js.map +1 -1
- package/dist/widgets/sdk.d.ts +5 -5
- package/dist/widgets/sdk.d.ts.map +1 -1
- package/dist/widgets/sdk.js.map +1 -1
- package/package.json +1 -1
- package/src/studio/app/api/auth/fetch-metadata/route.ts +3 -2
- package/src/studio/app/api/auth/register-client/route.ts +3 -2
- package/src/studio/app/api/chat/route.ts +33 -17
- package/src/studio/app/api/health/checks/route.ts +5 -4
- package/src/studio/app/api/init/route.ts +3 -2
- package/src/studio/app/api/ping/route.ts +3 -2
- package/src/studio/app/api/prompts/[name]/route.ts +4 -3
- package/src/studio/app/api/prompts/route.ts +3 -2
- package/src/studio/app/api/resources/[...uri]/route.ts +3 -2
- package/src/studio/app/api/resources/route.ts +3 -2
- package/src/studio/app/api/roots/route.ts +3 -2
- package/src/studio/app/api/sampling/route.ts +3 -2
- package/src/studio/app/api/tools/[name]/call/route.ts +3 -2
- package/src/studio/app/api/tools/route.ts +4 -3
- package/src/studio/app/api/widget-examples/route.ts +5 -4
- package/src/studio/app/auth/callback/page.tsx +9 -8
- package/src/studio/app/chat/page.tsx +1535 -468
- package/src/studio/app/chat/page.tsx.backup +1046 -187
- package/src/studio/app/globals.css +361 -191
- package/src/studio/app/health/page.tsx +73 -77
- package/src/studio/app/layout.tsx +9 -11
- package/src/studio/app/logs/page.tsx +31 -32
- package/src/studio/app/page.tsx +136 -232
- package/src/studio/app/prompts/page.tsx +115 -97
- package/src/studio/app/resources/page.tsx +115 -124
- package/src/studio/app/settings/page.tsx +1083 -127
- package/src/studio/app/tools/page.tsx +343 -0
- package/src/studio/components/EnlargeModal.tsx +76 -65
- package/src/studio/components/LogMessage.tsx +6 -6
- package/src/studio/components/MarkdownRenderer.tsx +246 -349
- package/src/studio/components/Sidebar.tsx +165 -210
- package/src/studio/components/SplashScreen.tsx +109 -0
- package/src/studio/components/ToolCard.tsx +50 -41
- package/src/studio/components/VoiceOrbOverlay.tsx +475 -0
- package/src/studio/components/WidgetErrorBoundary.tsx +48 -0
- package/src/studio/components/WidgetRenderer.tsx +169 -211
- package/src/studio/components/ops/OpsCanvas.tsx +748 -0
- package/src/studio/components/ops/OpsNodeDetailPanel.tsx +150 -0
- package/src/studio/components/ops/OpsSummaryBar.tsx +90 -0
- package/src/studio/components/ops/index.ts +5 -0
- package/src/studio/components/ops/nodes/BaseNode.tsx +65 -0
- package/src/studio/components/ops/nodes/LLMCallNode.tsx +34 -0
- package/src/studio/components/ops/nodes/LLMResponseNode.tsx +33 -0
- package/src/studio/components/ops/nodes/ToolCallNode.tsx +30 -0
- package/src/studio/components/ops/nodes/ToolResultNode.tsx +43 -0
- package/src/studio/components/ops/nodes/UserPromptNode.tsx +34 -0
- package/src/studio/components/ops/nodes/WidgetRenderNode.tsx +23 -0
- package/src/studio/components/ops/nodes/index.ts +8 -0
- package/src/studio/components/tools/ToolsCanvas.tsx +327 -0
- package/src/studio/lib/api.ts +61 -42
- package/src/studio/lib/http-client-transport.ts +2 -2
- package/src/studio/lib/llm-service.ts +126 -47
- package/src/studio/lib/mcp-client.ts +9 -6
- package/src/studio/lib/ops-store.ts +427 -0
- package/src/studio/lib/ops-tracker.ts +416 -0
- package/src/studio/lib/ops-types.ts +164 -0
- package/src/studio/lib/store.ts +23 -11
- package/src/studio/lib/types.ts +228 -38
- package/src/studio/lib/widget-loader.ts +2 -2
- package/src/studio/package-lock.json +3303 -0
- package/src/studio/package.json +3 -1
- package/src/studio/public/NitroStudio Isotype Color.png +0 -0
- package/src/studio/tailwind.config.ts +63 -17
- package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +19 -22
- package/dist/cli/build-widgets.mjs +0 -165
- package/src/studio/app/auth/page.tsx +0 -560
- 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 {
|
|
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:
|
|
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>(
|
|
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
|
|
25
|
-
|
|
43
|
+
const iframe = iframeRef.current;
|
|
44
|
+
const timeoutIds: NodeJS.Timeout[] = [];
|
|
26
45
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
64
|
+
// Handle widget ready message
|
|
65
|
+
if (event.data?.type === 'NITRO_WIDGET_READY') {
|
|
66
|
+
setIsLoaded(true);
|
|
67
|
+
}
|
|
151
68
|
|
|
152
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
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
|
-
|
|
326
|
-
|
|
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:
|
|
336
|
-
|
|
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
|
-
|