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.
- 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 +31 -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 +3 -2
- package/src/studio/app/chat/page.tsx +481 -105
- package/src/studio/app/health/page.tsx +1 -1
- package/src/studio/app/logs/page.tsx +2 -2
- package/src/studio/app/page.tsx +5 -5
- package/src/studio/app/prompts/page.tsx +2 -2
- package/src/studio/app/settings/page.tsx +3 -2
- package/src/studio/app/tools/page.tsx +3 -3
- package/src/studio/components/LogMessage.tsx +1 -1
- package/src/studio/components/MarkdownRenderer.tsx +245 -348
- package/src/studio/components/Sidebar.tsx +18 -3
- package/src/studio/components/VoiceOrbOverlay.tsx +12 -6
- package/src/studio/components/WidgetErrorBoundary.tsx +48 -0
- package/src/studio/components/WidgetRenderer.tsx +168 -215
- 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 +2 -2
- 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 +8 -11
- package/src/studio/lib/types.ts +228 -38
- package/src/studio/lib/widget-loader.ts +2 -2
- package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +19 -22
- 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
|
-
|
|
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
|
|
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
|
|
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<
|
|
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
|
-
|
|
76
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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 {
|
|
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,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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
|
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:
|
|
341
|
-
|
|
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
|
-
|