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.
@@ -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 { useBuilder } from './BuilderProvider';
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 } = useBuilder();
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);
@@ -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,eAAe,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAChE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGlG,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"}
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"}
@@ -1,8 +1,6 @@
1
1
  /**
2
2
  * Builder React exports
3
3
  */
4
- // Builder provider with capture integration
5
- export { BuilderProvider, useBuilder } from './BuilderProvider';
6
4
  // Chat context and provider
7
5
  export { ChatProvider, useChatContext } from './ChatContext';
8
6
  // Components
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Inference exports
3
3
  */
4
- export { InferenceProvider, default as InferenceProviderDefault } from './InferenceProvider';
5
- export { createSharedContext, getSharedContext, isSharedContextReady, } from './sharedContext';
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,EAAE,iBAAiB,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAG7F,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAGzB,YAAY,EAAE,cAAc,EAAE,sBAAsB,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
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"}
@@ -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,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;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;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,mFAAmF;IACnF,mBAAmB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,SAAS,CAAC;IAC7B,4EAA4E;IAC5E,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAClC,yCAAyC;IACzC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,8CAA8C;IAC9C,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,mEAAmE;IACnE,QAAQ,EAAE,SAAS,CAAC;IACpB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,kBAAkB,CAAC;IAClF,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,+CAA+C;IAC/C,GAAG,EAAE,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAClC,8CAA8C;IAC9C,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAID,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
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,6 +1,6 @@
1
1
  {
2
2
  "name": "dynim-react",
3
- "version": "1.0.37",
3
+ "version": "1.0.39",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -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;