dynim-react 1.0.37 → 1.0.39
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/builder/CodeChatPanel.js +2 -2
- package/dist/builder/index.d.ts +0 -2
- package/dist/builder/index.d.ts.map +1 -1
- package/dist/builder/index.js +0 -2
- package/dist/inference/index.d.ts +2 -3
- package/dist/inference/index.d.ts.map +1 -1
- package/dist/inference/index.js +1 -3
- package/dist/inference/types.d.ts +0 -43
- package/dist/inference/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/builder/BuilderProvider.d.ts +0 -78
- package/dist/builder/BuilderProvider.d.ts.map +0 -1
- package/dist/builder/BuilderProvider.js +0 -505
- package/dist/inference/InferenceProvider.d.ts +0 -4
- package/dist/inference/InferenceProvider.d.ts.map +0 -1
- package/dist/inference/InferenceProvider.js +0 -244
|
@@ -10,7 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
10
10
|
* Consuming app just drops this in and it works.
|
|
11
11
|
*/
|
|
12
12
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
13
|
-
import {
|
|
13
|
+
import { useDynim } from '../DynimProvider';
|
|
14
14
|
import { themeVars } from '../theme';
|
|
15
15
|
// Styles using CSS variables for easy theming
|
|
16
16
|
const styles = {
|
|
@@ -214,7 +214,7 @@ const styles = {
|
|
|
214
214
|
},
|
|
215
215
|
};
|
|
216
216
|
export function CodeChatPanel({ placeholder = 'Ask me to make changes to your code...', className = '', onSave, onAbandon, onError, }) {
|
|
217
|
-
const { sendCode, saveCode, abandonCode, codeMessage } =
|
|
217
|
+
const { sendCode, saveCode, abandonCode, codeMessage } = useDynim();
|
|
218
218
|
const [inputValue, setInputValue] = useState('');
|
|
219
219
|
const [thinkingExpanded, setThinkingExpanded] = useState(false);
|
|
220
220
|
const [isSaving, setIsSaving] = useState(false);
|
package/dist/builder/index.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Builder React exports
|
|
3
3
|
*/
|
|
4
|
-
export { BuilderProvider, useBuilder } from './BuilderProvider';
|
|
5
|
-
export type { BuilderConfig, BuilderContextValue, BuilderProviderProps } from './BuilderProvider';
|
|
6
4
|
export { ChatProvider, useChatContext } from './ChatContext';
|
|
7
5
|
export type { ChatProviderProps } from './ChatContext';
|
|
8
6
|
export { MessageList } from './MessageList';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/builder/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/builder/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC7D,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGvD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAG1D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/builder/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Inference exports
|
|
3
3
|
*/
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export type { DynimSDKConfig, InferenceProviderProps, InferenceState, DynimGlobal } from './types';
|
|
4
|
+
export { createSharedContext, updateSharedContext, getSharedContext, isSharedContextReady, } from './sharedContext';
|
|
5
|
+
export type { DynimSDKConfig, DynimGlobal } from './types';
|
|
7
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/inference/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/inference/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAGzB,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/inference/index.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Inference exports
|
|
3
3
|
*/
|
|
4
|
-
// Components
|
|
5
|
-
export { InferenceProvider, default as InferenceProviderDefault } from './InferenceProvider';
|
|
6
4
|
// Shared context utilities
|
|
7
|
-
export { createSharedContext, getSharedContext, isSharedContextReady, } from './sharedContext';
|
|
5
|
+
export { createSharedContext, updateSharedContext, getSharedContext, isSharedContextReady, } from './sharedContext';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
1
|
/**
|
|
3
2
|
* Configuration for the Dynim SDK registration
|
|
4
3
|
*/
|
|
@@ -16,47 +15,5 @@ export interface DynimSDKConfig {
|
|
|
16
15
|
/** Additional globals to expose */
|
|
17
16
|
globals?: Record<string, unknown>;
|
|
18
17
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Props for the InferenceProvider component
|
|
21
|
-
*/
|
|
22
|
-
export interface InferenceProviderProps {
|
|
23
|
-
/** Current tenant ID, null if no tenant */
|
|
24
|
-
tenantId: string | null;
|
|
25
|
-
/** Optional async function to check if tenant has customizations before loading */
|
|
26
|
-
checkCustomizations?: (tenantId: string) => Promise<boolean>;
|
|
27
|
-
/** Custom loading component to show while bundle is loading */
|
|
28
|
-
loadingComponent?: ReactNode;
|
|
29
|
-
/** Show error banner on load failure. Can be boolean or custom ReactNode */
|
|
30
|
-
errorBanner?: boolean | ReactNode;
|
|
31
|
-
/** Callback when bundle fails to load */
|
|
32
|
-
onError?: (error: Error, tenantId: string) => void;
|
|
33
|
-
/** Callback when bundle successfully loads */
|
|
34
|
-
onLoad?: (tenantId: string) => void;
|
|
35
|
-
/** Base app to render (used as fallback when no customizations) */
|
|
36
|
-
children: ReactNode;
|
|
37
|
-
/** NPM packages to expose to bundles (e.g., { 'react-router-dom': ReactRouterDOM }) */
|
|
38
|
-
packages?: Record<string, unknown>;
|
|
39
|
-
/** Custom hooks to expose to bundles */
|
|
40
|
-
hooks?: Record<string, unknown>;
|
|
41
|
-
/** React contexts to expose to bundles */
|
|
42
|
-
contexts?: Record<string, unknown>;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Internal state for the InferenceProvider
|
|
46
|
-
*/
|
|
47
|
-
export interface InferenceState {
|
|
48
|
-
status: 'idle' | 'checking' | 'loading' | 'loaded' | 'error' | 'no-customization';
|
|
49
|
-
error: Error | null;
|
|
50
|
-
currentTenantId: string | null;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Module loaded from a tenant bundle via dynamic import
|
|
54
|
-
*/
|
|
55
|
-
export interface BundleModule {
|
|
56
|
-
/** The App component exported by the bundle */
|
|
57
|
-
App: React.ComponentType<unknown>;
|
|
58
|
-
/** Cleanup function to revoke the blob URL */
|
|
59
|
-
cleanup: () => void;
|
|
60
|
-
}
|
|
61
18
|
export type { DynimGlobal } from 'dynim-core';
|
|
62
19
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/inference/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/inference/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,KAAK,EAAE,cAAc,OAAO,CAAC,CAAC;IAC9B,mDAAmD;IACnD,QAAQ,EAAE,cAAc,WAAW,CAAC,CAAC;IACrC,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAGD,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BuilderProvider - React integration for the visual builder
|
|
3
|
-
*
|
|
4
|
-
* Integrates the visual builder UI (floating bar, overlays) from dynim-core.
|
|
5
|
-
* Loads tenant bundles via dynamic import() and renders them as React components.
|
|
6
|
-
*/
|
|
7
|
-
import { type ReactNode } from 'react';
|
|
8
|
-
import type { BuilderInstance, CodeEdit, CodeMessage } from 'dynim-core';
|
|
9
|
-
export interface BuilderConfig {
|
|
10
|
-
/** JWT session token for authentication */
|
|
11
|
-
sessionToken?: string;
|
|
12
|
-
/** Refresh token for getting new session tokens */
|
|
13
|
-
refreshToken?: string;
|
|
14
|
-
/** Function to fetch a new session when current one expires */
|
|
15
|
-
getSession?: () => Promise<{
|
|
16
|
-
token: string;
|
|
17
|
-
refreshToken?: string;
|
|
18
|
-
}>;
|
|
19
|
-
/** Called when an error occurs */
|
|
20
|
-
onError?: (error: Event | Error) => void;
|
|
21
|
-
/** Called when the structured code message updates (recommended) */
|
|
22
|
-
onCodeMessageUpdate?: (message: CodeMessage) => void;
|
|
23
|
-
/** Called when a code edit is received */
|
|
24
|
-
onCodeEdit?: (edit: CodeEdit) => void;
|
|
25
|
-
/** NPM packages to expose to bundles (e.g., { 'react-router-dom': ReactRouterDOM }) */
|
|
26
|
-
packages?: Record<string, unknown>;
|
|
27
|
-
/** Custom hooks to expose to bundles */
|
|
28
|
-
hooks?: Record<string, unknown>;
|
|
29
|
-
/** React contexts to expose to bundles */
|
|
30
|
-
contexts?: Record<string, unknown>;
|
|
31
|
-
}
|
|
32
|
-
export interface BuilderContextValue {
|
|
33
|
-
enterBuilder: () => void;
|
|
34
|
-
exitBuilder: () => void;
|
|
35
|
-
isBuilderActive: boolean;
|
|
36
|
-
getBuilder: () => BuilderInstance | null;
|
|
37
|
-
sendCode: (query: string) => Promise<void>;
|
|
38
|
-
saveCode: () => Promise<void>;
|
|
39
|
-
abandonCode: () => Promise<void>;
|
|
40
|
-
warmCache: () => Promise<void>;
|
|
41
|
-
/** Current structured code message state */
|
|
42
|
-
codeMessage: CodeMessage;
|
|
43
|
-
/** Reset code message state */
|
|
44
|
-
resetCodeMessage: () => void;
|
|
45
|
-
/** @deprecated Use codeMessage.edits instead */
|
|
46
|
-
codeEdits: CodeEdit[];
|
|
47
|
-
/** Load a bundle by URL */
|
|
48
|
-
loadBundle: (bundleUrl: string) => Promise<void>;
|
|
49
|
-
/** Load bundle for a project */
|
|
50
|
-
loadProjectBundle: (projectId: string) => Promise<void>;
|
|
51
|
-
/** Load saved (permanent) bundle for a project */
|
|
52
|
-
loadSavedBundle: (projectId: string) => Promise<void>;
|
|
53
|
-
/** Load temp bundle for a project (AI edits preview) */
|
|
54
|
-
loadTempBundle: (projectId: string) => Promise<void>;
|
|
55
|
-
/** Check if a bundle is currently loaded */
|
|
56
|
-
isBundleLoaded: boolean;
|
|
57
|
-
/** Check if bundle is loading */
|
|
58
|
-
isBundleLoading: boolean;
|
|
59
|
-
/** Unload current bundle (show children again) */
|
|
60
|
-
unloadBundle: () => void;
|
|
61
|
-
}
|
|
62
|
-
export interface BuilderProviderProps {
|
|
63
|
-
children: ReactNode;
|
|
64
|
-
config?: BuilderConfig;
|
|
65
|
-
onBuilderReady?: (context: BuilderContextValue) => void;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* BuilderProvider - Wraps your app with builder support
|
|
69
|
-
*
|
|
70
|
-
* When a bundle is loaded, it replaces children with the bundle's App component.
|
|
71
|
-
* Bundles are loaded via dynamic import() and rendered as React components.
|
|
72
|
-
*/
|
|
73
|
-
export declare function BuilderProvider({ children, config, onBuilderReady, }: BuilderProviderProps): JSX.Element;
|
|
74
|
-
/**
|
|
75
|
-
* Hook to access builder functionality
|
|
76
|
-
*/
|
|
77
|
-
export declare function useBuilder(): BuilderContextValue;
|
|
78
|
-
//# sourceMappingURL=BuilderProvider.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BuilderProvider.d.ts","sourceRoot":"","sources":["../../src/builder/BuilderProvider.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,505 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* BuilderProvider - React integration for the visual builder
|
|
4
|
-
*
|
|
5
|
-
* Integrates the visual builder UI (floating bar, overlays) from dynim-core.
|
|
6
|
-
* Loads tenant bundles via dynamic import() and renders them as React components.
|
|
7
|
-
*/
|
|
8
|
-
import React, { createContext, useContext, useEffect, useRef, useCallback, useMemo, useState, } from 'react';
|
|
9
|
-
import ReactDOM from 'react-dom';
|
|
10
|
-
import { createBuilderClient, createBuilder, createCodeClient, createBundleLoader, BundleNotFoundError, } from 'dynim-core';
|
|
11
|
-
import { createSharedContext } from '../inference/sharedContext';
|
|
12
|
-
const BuilderContext = createContext(null);
|
|
13
|
-
/**
|
|
14
|
-
* BuilderProvider - Wraps your app with builder support
|
|
15
|
-
*
|
|
16
|
-
* When a bundle is loaded, it replaces children with the bundle's App component.
|
|
17
|
-
* Bundles are loaded via dynamic import() and rendered as React components.
|
|
18
|
-
*/
|
|
19
|
-
export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
20
|
-
const builderClientRef = useRef(null);
|
|
21
|
-
const builderRef = useRef(null);
|
|
22
|
-
const codeClientRef = useRef(null);
|
|
23
|
-
// Track when we're exiting to prevent auto-load from re-triggering
|
|
24
|
-
const isExitingRef = useRef(false);
|
|
25
|
-
// Track if initial bundle load has been attempted
|
|
26
|
-
const hasAttemptedInitialLoadRef = useRef(false);
|
|
27
|
-
// Track builder active state - use BOTH ref (for sync checks in callbacks) and state (for renders)
|
|
28
|
-
const isBuilderActiveRef = useRef(false);
|
|
29
|
-
const [isBuilderActive, setIsBuilderActive] = useState(false);
|
|
30
|
-
// Cached auth token and in-flight promise for fast subsequent loads
|
|
31
|
-
const cachedTokenRef = useRef(null);
|
|
32
|
-
const tokenPromiseRef = useRef(null);
|
|
33
|
-
// Signal counter for triggering bundle loads - ONLY incremented when we decide to load
|
|
34
|
-
// This avoids the dependency cascade problem with the old approach
|
|
35
|
-
const [bundleLoadSignal, setBundleLoadSignal] = useState(0);
|
|
36
|
-
const pendingBundleProjectIdRef = useRef(null);
|
|
37
|
-
const [isBundleLoaded, setIsBundleLoaded] = useState(false);
|
|
38
|
-
const [isBundleLoading, setIsBundleLoading] = useState(false);
|
|
39
|
-
// Track if initial bundle load has completed (to avoid flash of children)
|
|
40
|
-
const [isInitialLoadComplete, setIsInitialLoadComplete] = useState(false);
|
|
41
|
-
const [codeMessage, setCodeMessage] = useState({
|
|
42
|
-
thinking: '',
|
|
43
|
-
text: '',
|
|
44
|
-
edits: [],
|
|
45
|
-
status: 'idle',
|
|
46
|
-
bundleReady: false,
|
|
47
|
-
bundleError: undefined,
|
|
48
|
-
});
|
|
49
|
-
// The loaded tenant app component
|
|
50
|
-
const [TenantApp, setTenantApp] = useState(null);
|
|
51
|
-
// Error state for bundle rendering failures
|
|
52
|
-
const [bundleError, setBundleError] = useState(null);
|
|
53
|
-
// Bundle loader from core (created once)
|
|
54
|
-
const bundleLoaderRef = useRef(null);
|
|
55
|
-
// Keep config callbacks in refs so loader always gets latest values
|
|
56
|
-
const configCallbacksRef = useRef({
|
|
57
|
-
onError: config.onError,
|
|
58
|
-
});
|
|
59
|
-
configCallbacksRef.current = {
|
|
60
|
-
onError: config.onError,
|
|
61
|
-
};
|
|
62
|
-
// Set up shared context for bundles (exposes React, ReactDOM, packages, hooks, contexts)
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
const { packages = {}, hooks = {}, contexts = {} } = config;
|
|
65
|
-
// Import useBuilder dynamically to register in packages (avoids circular dep at module level)
|
|
66
|
-
const sdkExports = {
|
|
67
|
-
useBuilder,
|
|
68
|
-
BuilderProvider,
|
|
69
|
-
// Add other commonly used exports bundles might need
|
|
70
|
-
};
|
|
71
|
-
createSharedContext({
|
|
72
|
-
React,
|
|
73
|
-
ReactDOM,
|
|
74
|
-
packages: {
|
|
75
|
-
'dynim-react': sdkExports,
|
|
76
|
-
...packages,
|
|
77
|
-
},
|
|
78
|
-
hooks,
|
|
79
|
-
contexts,
|
|
80
|
-
});
|
|
81
|
-
console.log('[BuilderProvider] Shared context initialized with packages:', ['dynim-react', ...Object.keys(packages)]);
|
|
82
|
-
}, [config]);
|
|
83
|
-
// Initialize code client FIRST (before builder, so we can share it)
|
|
84
|
-
useEffect(() => {
|
|
85
|
-
const { sessionToken, refreshToken, getSession, onCodeMessageUpdate, onCodeEdit, onError } = config;
|
|
86
|
-
codeClientRef.current = createCodeClient({
|
|
87
|
-
sessionToken,
|
|
88
|
-
refreshToken,
|
|
89
|
-
getSession,
|
|
90
|
-
onMessageUpdate: (message) => {
|
|
91
|
-
setCodeMessage(message);
|
|
92
|
-
onCodeMessageUpdate?.(message);
|
|
93
|
-
},
|
|
94
|
-
onEdit: (edit) => {
|
|
95
|
-
console.log('[BuilderProvider] Code edit:', edit);
|
|
96
|
-
onCodeEdit?.(edit);
|
|
97
|
-
},
|
|
98
|
-
onError: (error) => {
|
|
99
|
-
console.error('[BuilderProvider] Code error:', error);
|
|
100
|
-
onError?.(error);
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
}, [config]);
|
|
104
|
-
// Initialize visual builder (createBuilder from core) - AFTER codeClient
|
|
105
|
-
// We need loadBundle to be stable, so we use a ref
|
|
106
|
-
const loadBundleRef = useRef(null);
|
|
107
|
-
useEffect(() => {
|
|
108
|
-
const { sessionToken, refreshToken, getSession } = config;
|
|
109
|
-
// Create the visual builder instance, sharing our CodeClient
|
|
110
|
-
builderRef.current = createBuilder({
|
|
111
|
-
sessionToken,
|
|
112
|
-
refreshToken,
|
|
113
|
-
getSession,
|
|
114
|
-
codeClient: codeClientRef.current ?? undefined,
|
|
115
|
-
// Called IMMEDIATELY when exit starts, before any async work
|
|
116
|
-
// This prevents race condition where streaming events trigger temp bundle reload
|
|
117
|
-
onExitStart: () => {
|
|
118
|
-
isExitingRef.current = true;
|
|
119
|
-
isBuilderActiveRef.current = false; // Mark inactive immediately
|
|
120
|
-
console.log('[BuilderProvider] Exit started, isExiting=true');
|
|
121
|
-
// Abort any active streaming to prevent late bundle_ready events
|
|
122
|
-
codeClientRef.current?.abort();
|
|
123
|
-
// Reset message state to clear bundleReady flag
|
|
124
|
-
codeClientRef.current?.resetMessage();
|
|
125
|
-
},
|
|
126
|
-
// Pass loadBundle so exit() can restore permanent bundle
|
|
127
|
-
loadBundle: (bundleUrl) => {
|
|
128
|
-
// isExitingRef already set by onExitStart, but keep as backup
|
|
129
|
-
isExitingRef.current = true;
|
|
130
|
-
console.log('[BuilderProvider] Exit loadBundle called, isExiting=true');
|
|
131
|
-
if (loadBundleRef.current) {
|
|
132
|
-
return loadBundleRef.current(bundleUrl);
|
|
133
|
-
}
|
|
134
|
-
return Promise.resolve();
|
|
135
|
-
},
|
|
136
|
-
onEnter: () => {
|
|
137
|
-
console.log('[BuilderProvider] Visual builder entered');
|
|
138
|
-
isExitingRef.current = false;
|
|
139
|
-
isBuilderActiveRef.current = true;
|
|
140
|
-
setIsBuilderActive(true);
|
|
141
|
-
},
|
|
142
|
-
onExit: () => {
|
|
143
|
-
console.log('[BuilderProvider] Visual builder exited');
|
|
144
|
-
setIsBuilderActive(false);
|
|
145
|
-
// NOTE: Do NOT reset isExitingRef here - it must stay true until
|
|
146
|
-
// React finishes processing state updates. It gets reset in onEnter.
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
return () => {
|
|
150
|
-
builderRef.current?.destroy();
|
|
151
|
-
};
|
|
152
|
-
}, [config]);
|
|
153
|
-
// Initialize builder client for visual builder operations (preview, exit)
|
|
154
|
-
useEffect(() => {
|
|
155
|
-
const { sessionToken, refreshToken, getSession, onError } = config;
|
|
156
|
-
builderClientRef.current = createBuilderClient({
|
|
157
|
-
sessionToken,
|
|
158
|
-
refreshToken,
|
|
159
|
-
getSession,
|
|
160
|
-
onError: (error) => {
|
|
161
|
-
console.error('[BuilderProvider] Error:', error);
|
|
162
|
-
onError?.(error);
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
}, [config]);
|
|
166
|
-
// Enter builder mode - activates the visual builder UI
|
|
167
|
-
const enterBuilder = useCallback(() => {
|
|
168
|
-
if (builderRef.current && !builderRef.current.isActive()) {
|
|
169
|
-
builderRef.current.enter();
|
|
170
|
-
}
|
|
171
|
-
}, []);
|
|
172
|
-
// Exit builder mode - deactivates the visual builder UI
|
|
173
|
-
const exitBuilder = useCallback(() => {
|
|
174
|
-
if (builderRef.current && builderRef.current.isActive()) {
|
|
175
|
-
builderRef.current.exit();
|
|
176
|
-
}
|
|
177
|
-
}, []);
|
|
178
|
-
/**
|
|
179
|
-
* Get auth token for bundle requests (with caching for performance)
|
|
180
|
-
* Returns cached token if available, otherwise fetches and caches.
|
|
181
|
-
* Reuses in-flight promise to avoid duplicate requests.
|
|
182
|
-
*/
|
|
183
|
-
const getAuthToken = useCallback(async () => {
|
|
184
|
-
const { sessionToken, getSession } = config;
|
|
185
|
-
// Fast path: static token from config
|
|
186
|
-
if (sessionToken) {
|
|
187
|
-
return sessionToken;
|
|
188
|
-
}
|
|
189
|
-
// Fast path: return cached token
|
|
190
|
-
if (cachedTokenRef.current) {
|
|
191
|
-
return cachedTokenRef.current;
|
|
192
|
-
}
|
|
193
|
-
// Reuse in-flight promise if one exists (prevents duplicate getSession calls)
|
|
194
|
-
if (tokenPromiseRef.current) {
|
|
195
|
-
return tokenPromiseRef.current;
|
|
196
|
-
}
|
|
197
|
-
if (getSession) {
|
|
198
|
-
// Create and cache the promise
|
|
199
|
-
tokenPromiseRef.current = (async () => {
|
|
200
|
-
try {
|
|
201
|
-
const session = await getSession();
|
|
202
|
-
cachedTokenRef.current = session.token; // Cache for future use
|
|
203
|
-
return session.token;
|
|
204
|
-
}
|
|
205
|
-
catch (e) {
|
|
206
|
-
console.warn('[BuilderProvider] Failed to get session for bundle auth:', e);
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
finally {
|
|
210
|
-
tokenPromiseRef.current = null; // Clear in-flight promise
|
|
211
|
-
}
|
|
212
|
-
})();
|
|
213
|
-
return tokenPromiseRef.current;
|
|
214
|
-
}
|
|
215
|
-
return null;
|
|
216
|
-
}, [config]);
|
|
217
|
-
// Initialize bundle loader from core (lazy initialization)
|
|
218
|
-
const getBundleLoader = useCallback(() => {
|
|
219
|
-
if (!bundleLoaderRef.current) {
|
|
220
|
-
bundleLoaderRef.current = createBundleLoader({
|
|
221
|
-
getAuthToken,
|
|
222
|
-
includeCredentials: true, // Uses same-origin session cookies
|
|
223
|
-
onError: (error) => {
|
|
224
|
-
console.error('[BuilderProvider] Bundle load failed:', error);
|
|
225
|
-
configCallbacksRef.current.onError?.(error);
|
|
226
|
-
},
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
return bundleLoaderRef.current;
|
|
230
|
-
}, [getAuthToken]);
|
|
231
|
-
/**
|
|
232
|
-
* Load a bundle via dynamic import()
|
|
233
|
-
* Uses createBundleLoader from dynim-core
|
|
234
|
-
*/
|
|
235
|
-
const loadBundle = useCallback(async (bundleUrl) => {
|
|
236
|
-
const loader = getBundleLoader();
|
|
237
|
-
console.log('[BuilderProvider] loadBundle called:', bundleUrl, 'isExiting=', isExitingRef.current);
|
|
238
|
-
if (loader.isLoading()) {
|
|
239
|
-
console.warn('[BuilderProvider] Already loading a bundle, skipping');
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
setIsBundleLoading(true);
|
|
243
|
-
setBundleError(null); // Clear any previous error
|
|
244
|
-
try {
|
|
245
|
-
const { App, cleanup } = await loader.load(bundleUrl);
|
|
246
|
-
console.log('[BuilderProvider] Bundle imported successfully');
|
|
247
|
-
// Store the component (cleanup is called automatically by the loader)
|
|
248
|
-
setTenantApp(() => App);
|
|
249
|
-
setIsBundleLoaded(true);
|
|
250
|
-
// Cleanup blob URL immediately after import
|
|
251
|
-
cleanup();
|
|
252
|
-
}
|
|
253
|
-
catch (error) {
|
|
254
|
-
// 404 is expected when no permanent bundle exists yet (new project, no saved edits)
|
|
255
|
-
// Don't treat it as an error - unload current bundle to show base application (children)
|
|
256
|
-
if (error instanceof BundleNotFoundError) {
|
|
257
|
-
console.log('[BuilderProvider] Bundle not found (404) - unloading to show base application');
|
|
258
|
-
setTenantApp(null);
|
|
259
|
-
setIsBundleLoaded(false);
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
// Note: Error already logged by loader.onError callback
|
|
263
|
-
setBundleError(error);
|
|
264
|
-
// Don't throw - let the app continue with children
|
|
265
|
-
}
|
|
266
|
-
finally {
|
|
267
|
-
setIsBundleLoading(false);
|
|
268
|
-
}
|
|
269
|
-
}, [getBundleLoader]);
|
|
270
|
-
// Keep the ref updated so the builder can call loadBundle
|
|
271
|
-
useEffect(() => {
|
|
272
|
-
loadBundleRef.current = loadBundle;
|
|
273
|
-
}, [loadBundle]);
|
|
274
|
-
// PERF: Eagerly prefetch auth token on mount (runs in parallel with render)
|
|
275
|
-
// This ensures the token is ready by the time we need to fetch the bundle
|
|
276
|
-
useEffect(() => {
|
|
277
|
-
const { sessionToken, getSession } = config;
|
|
278
|
-
if (!sessionToken && getSession) {
|
|
279
|
-
// Start fetching token immediately - don't await, just fire and forget
|
|
280
|
-
// The result will be cached in cachedTokenRef for when loadBundle needs it
|
|
281
|
-
getAuthToken();
|
|
282
|
-
}
|
|
283
|
-
}, []); // Empty deps - only run once on mount
|
|
284
|
-
// Auto-load saved bundle on mount (if authenticated)
|
|
285
|
-
// This ensures the permanent bundle is displayed immediately when the app loads
|
|
286
|
-
useEffect(() => {
|
|
287
|
-
if (hasAttemptedInitialLoadRef.current)
|
|
288
|
-
return;
|
|
289
|
-
const { sessionToken, getSession } = config;
|
|
290
|
-
// Need auth to load bundle - if no auth configured, just render children
|
|
291
|
-
if (!sessionToken && !getSession) {
|
|
292
|
-
console.log('[BuilderProvider] No auth configured, skipping initial bundle load');
|
|
293
|
-
setIsInitialLoadComplete(true); // Mark complete so children render
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
hasAttemptedInitialLoadRef.current = true;
|
|
297
|
-
const doInitialLoad = async () => {
|
|
298
|
-
console.log('[BuilderProvider] Auto-loading saved bundle on mount');
|
|
299
|
-
const bundleUrl = '/api/code/bundle';
|
|
300
|
-
if (loadBundleRef.current) {
|
|
301
|
-
try {
|
|
302
|
-
await loadBundleRef.current(bundleUrl);
|
|
303
|
-
}
|
|
304
|
-
catch (error) {
|
|
305
|
-
// Errors are already handled in loadBundle (sets bundleError state)
|
|
306
|
-
// 404 means no saved bundle - that's fine, children will render
|
|
307
|
-
console.log('[BuilderProvider] Initial bundle load completed (may be 404 if no saved bundle)');
|
|
308
|
-
}
|
|
309
|
-
finally {
|
|
310
|
-
setIsInitialLoadComplete(true); // Mark complete regardless of outcome
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
setIsInitialLoadComplete(true); // No loader available, show children
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
doInitialLoad();
|
|
318
|
-
}, [config.sessionToken, config.getSession]);
|
|
319
|
-
const loadProjectBundle = useCallback(async (projectId, forceReload = false) => {
|
|
320
|
-
let bundleUrl = `/api/bundles/preview?project_id=${projectId}`;
|
|
321
|
-
// Cache bust to force reload
|
|
322
|
-
if (forceReload) {
|
|
323
|
-
bundleUrl += `&_t=${Date.now()}`;
|
|
324
|
-
}
|
|
325
|
-
await loadBundle(bundleUrl);
|
|
326
|
-
}, [loadBundle]);
|
|
327
|
-
// Load saved (permanent) bundle - used for initial load, after save, and after abandon
|
|
328
|
-
const loadSavedBundle = useCallback(async (_projectId) => {
|
|
329
|
-
const bundleUrl = '/api/code/bundle';
|
|
330
|
-
console.log('[BuilderProvider] Loading saved (perm) bundle via:', bundleUrl);
|
|
331
|
-
await loadBundle(bundleUrl);
|
|
332
|
-
}, [loadBundle]);
|
|
333
|
-
// Load temp bundle - used when AI finishes editing to preview changes
|
|
334
|
-
const loadTempBundle = useCallback(async (_projectId) => {
|
|
335
|
-
const bundleUrl = `/api/code/bundle?temp=true&_t=${Date.now()}`;
|
|
336
|
-
console.log('[BuilderProvider] Loading temp bundle via:', bundleUrl);
|
|
337
|
-
await loadBundle(bundleUrl);
|
|
338
|
-
}, [loadBundle]);
|
|
339
|
-
// Watch for bundleReady and decide if we should queue a load
|
|
340
|
-
// Uses refs for instant checks (no stale closure issues)
|
|
341
|
-
useEffect(() => {
|
|
342
|
-
console.log('[BuilderProvider] bundleReady effect:', {
|
|
343
|
-
bundleReady: codeMessage.bundleReady,
|
|
344
|
-
projectId: codeMessage.projectId,
|
|
345
|
-
isBuilderActiveRef: isBuilderActiveRef.current,
|
|
346
|
-
isExitingRef: isExitingRef.current,
|
|
347
|
-
});
|
|
348
|
-
if (codeMessage.bundleReady && codeMessage.projectId) {
|
|
349
|
-
// Check refs for current state (not stale closure values)
|
|
350
|
-
if (isBuilderActiveRef.current && !isExitingRef.current) {
|
|
351
|
-
console.log('[BuilderProvider] >>> QUEUEING LOAD:', codeMessage.projectId);
|
|
352
|
-
pendingBundleProjectIdRef.current = codeMessage.projectId;
|
|
353
|
-
setBundleLoadSignal(s => s + 1);
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
console.log('[BuilderProvider] >>> BLOCKING LOAD (exiting or inactive)');
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}, [codeMessage.bundleReady, codeMessage.projectId]);
|
|
360
|
-
// Effect that actually loads - ONLY triggered by the signal counter
|
|
361
|
-
// Uses refs for everything so it has ONLY ONE dependency: bundleLoadSignal
|
|
362
|
-
useEffect(() => {
|
|
363
|
-
console.log('[BuilderProvider] Load signal effect, signal=', bundleLoadSignal);
|
|
364
|
-
if (bundleLoadSignal === 0)
|
|
365
|
-
return; // Skip initial render
|
|
366
|
-
const projectId = pendingBundleProjectIdRef.current;
|
|
367
|
-
console.log('[BuilderProvider] Load signal triggered, projectId=', projectId, 'isExiting=', isExitingRef.current);
|
|
368
|
-
if (!projectId)
|
|
369
|
-
return;
|
|
370
|
-
// Final safety check before loading
|
|
371
|
-
if (isExitingRef.current) {
|
|
372
|
-
console.log('[BuilderProvider] >>> BLOCKED BY FINAL CHECK - exit in progress');
|
|
373
|
-
pendingBundleProjectIdRef.current = null;
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
// Use refs to avoid dependency on config/loadTempBundle
|
|
377
|
-
// temp=true requests temp bundle (falls back to perm if no temp exists)
|
|
378
|
-
const bundleUrl = `/api/code/bundle?temp=true&_t=${Date.now()}`;
|
|
379
|
-
console.log('[BuilderProvider] >>> LOADING TEMP BUNDLE:', bundleUrl);
|
|
380
|
-
if (loadBundleRef.current) {
|
|
381
|
-
loadBundleRef.current(bundleUrl);
|
|
382
|
-
}
|
|
383
|
-
pendingBundleProjectIdRef.current = null;
|
|
384
|
-
}, [bundleLoadSignal]); // ONLY depends on the signal!
|
|
385
|
-
// Unload bundle - show children again
|
|
386
|
-
const unloadBundle = useCallback(() => {
|
|
387
|
-
setTenantApp(null);
|
|
388
|
-
setIsBundleLoaded(false);
|
|
389
|
-
console.log('[BuilderProvider] Bundle unloaded');
|
|
390
|
-
}, []);
|
|
391
|
-
// Code client methods
|
|
392
|
-
const sendCode = useCallback(async (query) => {
|
|
393
|
-
await codeClientRef.current?.sendCode(query);
|
|
394
|
-
}, []);
|
|
395
|
-
const saveCode = useCallback(async () => {
|
|
396
|
-
// 1. Persist changes to backend (promotes temp to saved)
|
|
397
|
-
await codeClientRef.current?.saveCode();
|
|
398
|
-
// 2. Load the newly saved bundle
|
|
399
|
-
if (codeMessage.projectId) {
|
|
400
|
-
await loadSavedBundle(codeMessage.projectId);
|
|
401
|
-
}
|
|
402
|
-
}, [loadSavedBundle, codeMessage.projectId]);
|
|
403
|
-
const abandonCode = useCallback(async () => {
|
|
404
|
-
// 1. Discard temp changes on server
|
|
405
|
-
await codeClientRef.current?.abandonCode();
|
|
406
|
-
// 2. Revert to saved bundle
|
|
407
|
-
if (codeMessage.projectId) {
|
|
408
|
-
await loadSavedBundle(codeMessage.projectId);
|
|
409
|
-
}
|
|
410
|
-
}, [loadSavedBundle, codeMessage.projectId]);
|
|
411
|
-
const warmCache = useCallback(async () => {
|
|
412
|
-
await codeClientRef.current?.warmCache();
|
|
413
|
-
}, []);
|
|
414
|
-
const resetCodeMessage = useCallback(() => {
|
|
415
|
-
codeClientRef.current?.resetMessage();
|
|
416
|
-
}, []);
|
|
417
|
-
const contextValue = useMemo(() => ({
|
|
418
|
-
enterBuilder,
|
|
419
|
-
exitBuilder,
|
|
420
|
-
isBuilderActive,
|
|
421
|
-
getBuilder: () => builderRef.current,
|
|
422
|
-
// Code client methods
|
|
423
|
-
sendCode,
|
|
424
|
-
saveCode,
|
|
425
|
-
abandonCode,
|
|
426
|
-
warmCache,
|
|
427
|
-
codeMessage,
|
|
428
|
-
resetCodeMessage,
|
|
429
|
-
codeEdits: codeMessage.edits,
|
|
430
|
-
// Bundle methods
|
|
431
|
-
loadBundle,
|
|
432
|
-
loadProjectBundle,
|
|
433
|
-
loadSavedBundle,
|
|
434
|
-
loadTempBundle,
|
|
435
|
-
isBundleLoaded,
|
|
436
|
-
isBundleLoading,
|
|
437
|
-
unloadBundle,
|
|
438
|
-
}), [
|
|
439
|
-
enterBuilder,
|
|
440
|
-
exitBuilder,
|
|
441
|
-
isBuilderActive,
|
|
442
|
-
sendCode,
|
|
443
|
-
saveCode,
|
|
444
|
-
abandonCode,
|
|
445
|
-
warmCache,
|
|
446
|
-
codeMessage,
|
|
447
|
-
resetCodeMessage,
|
|
448
|
-
loadBundle,
|
|
449
|
-
loadProjectBundle,
|
|
450
|
-
loadSavedBundle,
|
|
451
|
-
loadTempBundle,
|
|
452
|
-
isBundleLoaded,
|
|
453
|
-
isBundleLoading,
|
|
454
|
-
unloadBundle,
|
|
455
|
-
]);
|
|
456
|
-
// Notify parent when ready (only once on mount)
|
|
457
|
-
const hasNotifiedRef = useRef(false);
|
|
458
|
-
useEffect(() => {
|
|
459
|
-
if (!hasNotifiedRef.current && onBuilderReady) {
|
|
460
|
-
hasNotifiedRef.current = true;
|
|
461
|
-
onBuilderReady(contextValue);
|
|
462
|
-
}
|
|
463
|
-
}, [contextValue, onBuilderReady]);
|
|
464
|
-
// Render the appropriate content based on state
|
|
465
|
-
const renderContent = () => {
|
|
466
|
-
// Initial load in progress - show nothing to prevent flash of children
|
|
467
|
-
// This only applies to the first load; subsequent loads show current content
|
|
468
|
-
if (!isInitialLoadComplete) {
|
|
469
|
-
return null;
|
|
470
|
-
}
|
|
471
|
-
// Loading state (after initial load) - keep showing current content
|
|
472
|
-
if (isBundleLoading) {
|
|
473
|
-
// If we have TenantApp, keep showing it during reload
|
|
474
|
-
// Otherwise show children (this is a subsequent load, not initial)
|
|
475
|
-
return TenantApp ? _jsx(TenantApp, {}) : children;
|
|
476
|
-
}
|
|
477
|
-
// Error state - show error banner with children
|
|
478
|
-
if (bundleError) {
|
|
479
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: {
|
|
480
|
-
padding: 12,
|
|
481
|
-
background: '#fef3c7',
|
|
482
|
-
borderBottom: '1px solid #fcd34d',
|
|
483
|
-
fontSize: 13,
|
|
484
|
-
color: '#92400e',
|
|
485
|
-
}, children: ["Bundle error: ", bundleError.message] }), children] }));
|
|
486
|
-
}
|
|
487
|
-
// Bundle loaded - render tenant app
|
|
488
|
-
if (TenantApp) {
|
|
489
|
-
return _jsx(TenantApp, {});
|
|
490
|
-
}
|
|
491
|
-
// Default - render children
|
|
492
|
-
return children;
|
|
493
|
-
};
|
|
494
|
-
return (_jsx(BuilderContext.Provider, { value: contextValue, children: renderContent() }));
|
|
495
|
-
}
|
|
496
|
-
/**
|
|
497
|
-
* Hook to access builder functionality
|
|
498
|
-
*/
|
|
499
|
-
export function useBuilder() {
|
|
500
|
-
const context = useContext(BuilderContext);
|
|
501
|
-
if (!context) {
|
|
502
|
-
throw new Error('useBuilder must be used within a BuilderProvider');
|
|
503
|
-
}
|
|
504
|
-
return context;
|
|
505
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { InferenceProviderProps } from './types';
|
|
2
|
-
export declare function InferenceProvider({ tenantId, checkCustomizations, loadingComponent, errorBanner, onError, onLoad, children, packages, hooks, contexts, }: InferenceProviderProps): JSX.Element;
|
|
3
|
-
export default InferenceProvider;
|
|
4
|
-
//# sourceMappingURL=InferenceProvider.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"InferenceProvider.d.ts","sourceRoot":"","sources":["../../src/inference/InferenceProvider.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,sBAAsB,EAAgC,MAAM,SAAS,CAAC;AA6DpF,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,mBAAmB,EACnB,gBAAgB,EAChB,WAAmB,EACnB,OAAO,EACP,MAAM,EACN,QAAQ,EACR,QAAa,EACb,KAAU,EACV,QAAa,GACd,EAAE,sBAAsB,GAAG,GAAG,CAAC,OAAO,CAyOtC;AAED,eAAe,iBAAiB,CAAC"}
|
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
3
|
-
import ReactDOM from 'react-dom';
|
|
4
|
-
import { createBundleLoader, BundleNotFoundError, BundleAuthError, isSharedRegistryReady, } from 'dynim-core';
|
|
5
|
-
import { createSharedContext } from './sharedContext';
|
|
6
|
-
/**
|
|
7
|
-
* Default loading component shown while bundle is loading
|
|
8
|
-
*/
|
|
9
|
-
function DefaultLoadingComponent() {
|
|
10
|
-
return (_jsx("div", { style: {
|
|
11
|
-
display: 'flex',
|
|
12
|
-
justifyContent: 'center',
|
|
13
|
-
alignItems: 'center',
|
|
14
|
-
padding: '20px',
|
|
15
|
-
}, children: _jsx("span", { children: "Loading customizations..." }) }));
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Default error banner component
|
|
19
|
-
*/
|
|
20
|
-
function DefaultErrorBanner({ error }) {
|
|
21
|
-
return (_jsxs("div", { style: {
|
|
22
|
-
background: '#fee2e2',
|
|
23
|
-
border: '1px solid #ef4444',
|
|
24
|
-
borderRadius: '4px',
|
|
25
|
-
padding: '12px 16px',
|
|
26
|
-
marginBottom: '16px',
|
|
27
|
-
color: '#991b1b',
|
|
28
|
-
}, children: [_jsx("strong", { children: "Customization Error:" }), " ", error.message] }));
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* InferenceProvider - A React component that dynamically loads tenant-specific bundles.
|
|
32
|
-
*
|
|
33
|
-
* This provider handles the lifecycle of loading, rendering, and cleaning up
|
|
34
|
-
* tenant customization bundles. It supports optional pre-checks to determine
|
|
35
|
-
* if a tenant has customizations before attempting to load them.
|
|
36
|
-
*
|
|
37
|
-
* Uses createBundleLoader from dynim-core for the actual bundle loading.
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* ```tsx
|
|
41
|
-
* <InferenceProvider tenantId={user?.tenantId}>
|
|
42
|
-
* <YourBaseApp />
|
|
43
|
-
* </InferenceProvider>
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
/** Internal bundle URL pattern - not exposed to users */
|
|
47
|
-
const BUNDLE_URL_PATTERN = '/api/bundles/{tenantId}';
|
|
48
|
-
export function InferenceProvider({ tenantId, checkCustomizations, loadingComponent, errorBanner = false, onError, onLoad, children, packages = {}, hooks = {}, contexts = {}, }) {
|
|
49
|
-
const [state, setState] = useState({
|
|
50
|
-
status: 'idle',
|
|
51
|
-
error: null,
|
|
52
|
-
currentTenantId: null,
|
|
53
|
-
});
|
|
54
|
-
const [bundleModule, setBundleModule] = useState(null);
|
|
55
|
-
// Auto-setup shared context so bundles can access React (only once)
|
|
56
|
-
const hasSetupSharedContextRef = useRef(false);
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
// Only setup if not already done (by this component or BuilderProvider)
|
|
59
|
-
if (!hasSetupSharedContextRef.current && !isSharedRegistryReady()) {
|
|
60
|
-
createSharedContext({
|
|
61
|
-
React,
|
|
62
|
-
ReactDOM,
|
|
63
|
-
packages,
|
|
64
|
-
hooks,
|
|
65
|
-
contexts,
|
|
66
|
-
});
|
|
67
|
-
hasSetupSharedContextRef.current = true;
|
|
68
|
-
}
|
|
69
|
-
}, []); // Run once on mount
|
|
70
|
-
// Bundle loader ref (created once per component instance)
|
|
71
|
-
const bundleLoaderRef = useRef(null);
|
|
72
|
-
// Get or create the bundle loader (no auth needed - uses session cookies)
|
|
73
|
-
const getBundleLoader = useCallback(() => {
|
|
74
|
-
if (!bundleLoaderRef.current) {
|
|
75
|
-
bundleLoaderRef.current = createBundleLoader({
|
|
76
|
-
includeCredentials: true, // Use session cookies for auth
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
return bundleLoaderRef.current;
|
|
80
|
-
}, []);
|
|
81
|
-
/**
|
|
82
|
-
* Resolves the bundle URL for a given tenant ID
|
|
83
|
-
*/
|
|
84
|
-
const resolveBundleUrl = useCallback((id) => {
|
|
85
|
-
return BUNDLE_URL_PATTERN.replace('{tenantId}', id);
|
|
86
|
-
}, []);
|
|
87
|
-
/**
|
|
88
|
-
* Cleans up previously loaded bundle module
|
|
89
|
-
*/
|
|
90
|
-
const cleanup = useCallback(() => {
|
|
91
|
-
if (bundleModule?.cleanup) {
|
|
92
|
-
bundleModule.cleanup();
|
|
93
|
-
}
|
|
94
|
-
setBundleModule(null);
|
|
95
|
-
}, [bundleModule]);
|
|
96
|
-
/**
|
|
97
|
-
* Main effect to handle tenant bundle loading
|
|
98
|
-
*/
|
|
99
|
-
useEffect(() => {
|
|
100
|
-
// No tenant - reset to idle and show children
|
|
101
|
-
if (!tenantId) {
|
|
102
|
-
cleanup();
|
|
103
|
-
setState({
|
|
104
|
-
status: 'idle',
|
|
105
|
-
error: null,
|
|
106
|
-
currentTenantId: null,
|
|
107
|
-
});
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
// Same tenant already loaded - do nothing
|
|
111
|
-
if (state.currentTenantId === tenantId && state.status === 'loaded') {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
let cancelled = false;
|
|
115
|
-
async function loadBundle() {
|
|
116
|
-
const loader = getBundleLoader();
|
|
117
|
-
try {
|
|
118
|
-
// Step 1: Check if tenant has customizations (if check function provided)
|
|
119
|
-
if (checkCustomizations) {
|
|
120
|
-
setState((prev) => ({ ...prev, status: 'checking' }));
|
|
121
|
-
const hasCustomizations = await checkCustomizations(tenantId);
|
|
122
|
-
if (cancelled)
|
|
123
|
-
return;
|
|
124
|
-
if (!hasCustomizations) {
|
|
125
|
-
cleanup();
|
|
126
|
-
setState({
|
|
127
|
-
status: 'no-customization',
|
|
128
|
-
error: null,
|
|
129
|
-
currentTenantId: tenantId,
|
|
130
|
-
});
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
// Step 2: Load the bundle using core's bundle loader
|
|
135
|
-
setState((prev) => ({ ...prev, status: 'loading' }));
|
|
136
|
-
// Clean up previous bundle
|
|
137
|
-
cleanup();
|
|
138
|
-
const url = resolveBundleUrl(tenantId);
|
|
139
|
-
// Load bundle via createBundleLoader from dynim-core
|
|
140
|
-
const result = await loader.load(url);
|
|
141
|
-
if (cancelled) {
|
|
142
|
-
result.cleanup();
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
// Create BundleModule from loader result
|
|
146
|
-
const module = {
|
|
147
|
-
App: result.App,
|
|
148
|
-
cleanup: result.cleanup,
|
|
149
|
-
};
|
|
150
|
-
setBundleModule(module);
|
|
151
|
-
setState({
|
|
152
|
-
status: 'loaded',
|
|
153
|
-
error: null,
|
|
154
|
-
currentTenantId: tenantId,
|
|
155
|
-
});
|
|
156
|
-
onLoad?.(tenantId);
|
|
157
|
-
}
|
|
158
|
-
catch (err) {
|
|
159
|
-
if (cancelled)
|
|
160
|
-
return;
|
|
161
|
-
// Convert core errors to appropriate Error instances
|
|
162
|
-
let error;
|
|
163
|
-
if (err instanceof BundleNotFoundError) {
|
|
164
|
-
error = new Error('Bundle not found');
|
|
165
|
-
}
|
|
166
|
-
else if (err instanceof BundleAuthError) {
|
|
167
|
-
error = new Error('Bundle token is invalid or expired');
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
error = err instanceof Error ? err : new Error(String(err));
|
|
171
|
-
}
|
|
172
|
-
setState({
|
|
173
|
-
status: 'error',
|
|
174
|
-
error,
|
|
175
|
-
currentTenantId: tenantId,
|
|
176
|
-
});
|
|
177
|
-
onError?.(error, tenantId);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
loadBundle();
|
|
181
|
-
return () => {
|
|
182
|
-
cancelled = true;
|
|
183
|
-
// Abort any in-flight load
|
|
184
|
-
const loader = bundleLoaderRef.current;
|
|
185
|
-
if (loader?.isLoading()) {
|
|
186
|
-
loader.abort();
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
}, [
|
|
190
|
-
tenantId,
|
|
191
|
-
checkCustomizations,
|
|
192
|
-
resolveBundleUrl,
|
|
193
|
-
cleanup,
|
|
194
|
-
onError,
|
|
195
|
-
onLoad,
|
|
196
|
-
state.currentTenantId,
|
|
197
|
-
state.status,
|
|
198
|
-
getBundleLoader,
|
|
199
|
-
]);
|
|
200
|
-
/**
|
|
201
|
-
* Cleanup on unmount
|
|
202
|
-
*/
|
|
203
|
-
useEffect(() => {
|
|
204
|
-
return () => {
|
|
205
|
-
cleanup();
|
|
206
|
-
};
|
|
207
|
-
}, [cleanup]);
|
|
208
|
-
// Render error banner if enabled and there's an error
|
|
209
|
-
const renderErrorBanner = () => {
|
|
210
|
-
if (!errorBanner || state.status !== 'error' || !state.error) {
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
-
if (errorBanner === true) {
|
|
214
|
-
return _jsx(DefaultErrorBanner, { error: state.error });
|
|
215
|
-
}
|
|
216
|
-
return errorBanner;
|
|
217
|
-
};
|
|
218
|
-
// Determine what to render based on state
|
|
219
|
-
const renderContent = () => {
|
|
220
|
-
switch (state.status) {
|
|
221
|
-
case 'checking':
|
|
222
|
-
case 'loading':
|
|
223
|
-
return loadingComponent ?? _jsx(DefaultLoadingComponent, {});
|
|
224
|
-
case 'loaded':
|
|
225
|
-
// Render the loaded App component directly
|
|
226
|
-
if (bundleModule?.App) {
|
|
227
|
-
const TenantApp = bundleModule.App;
|
|
228
|
-
return _jsx(TenantApp, {});
|
|
229
|
-
}
|
|
230
|
-
// Fallback if no App component found
|
|
231
|
-
return children;
|
|
232
|
-
case 'error':
|
|
233
|
-
// Show children as fallback with optional error banner
|
|
234
|
-
return (_jsxs(_Fragment, { children: [renderErrorBanner(), children] }));
|
|
235
|
-
case 'no-customization':
|
|
236
|
-
case 'idle':
|
|
237
|
-
default:
|
|
238
|
-
// No customizations or no tenant - render base app
|
|
239
|
-
return children;
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
return _jsx(_Fragment, { children: renderContent() });
|
|
243
|
-
}
|
|
244
|
-
export default InferenceProvider;
|