dynim-react 1.0.38 → 1.0.40

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dynim-react",
3
- "version": "1.0.38",
3
+ "version": "1.0.40",
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,11 +0,0 @@
1
- /**
2
- * React Context for sharing chatbot state across components
3
- */
4
- import { type ReactNode } from 'react';
5
- import { type UseChatbotConfig, type UseChatbotReturn } from './useChatbot';
6
- export interface ChatProviderProps extends UseChatbotConfig {
7
- children: ReactNode;
8
- }
9
- export declare function ChatProvider({ children, ...config }: ChatProviderProps): JSX.Element;
10
- export declare function useChatContext(): UseChatbotReturn;
11
- //# sourceMappingURL=ChatContext.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ChatContext.d.ts","sourceRoot":"","sources":["../../src/builder/ChatContext.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,EAAc,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIxF,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,GAAG,MAAM,EAAE,EAAE,iBAAiB,GAAG,GAAG,CAAC,OAAO,CAIpF;AAED,wBAAgB,cAAc,IAAI,gBAAgB,CAMjD"}
@@ -1,18 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- /**
3
- * React Context for sharing chatbot state across components
4
- */
5
- import { createContext, useContext } from 'react';
6
- import { useChatbot } from './useChatbot';
7
- const ChatContext = createContext(null);
8
- export function ChatProvider({ children, ...config }) {
9
- const chatbot = useChatbot(config);
10
- return _jsx(ChatContext.Provider, { value: chatbot, children: children });
11
- }
12
- export function useChatContext() {
13
- const context = useContext(ChatContext);
14
- if (!context) {
15
- throw new Error('useChatContext must be used within a ChatProvider');
16
- }
17
- return context;
18
- }
@@ -1,11 +0,0 @@
1
- /**
2
- * Chat input component
3
- */
4
- export interface ChatInputProps {
5
- className?: string;
6
- placeholder?: string;
7
- disabled?: boolean;
8
- onSubmit?: (text: string) => void;
9
- }
10
- export declare function ChatInput({ className, placeholder, disabled, onSubmit, }: ChatInputProps): JSX.Element;
11
- //# sourceMappingURL=ChatInput.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ChatInput.d.ts","sourceRoot":"","sources":["../../src/builder/ChatInput.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAKH,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,wBAAgB,SAAS,CAAC,EACxB,SAAc,EACd,WAAiC,EACjC,QAAgB,EAChB,QAAQ,GACT,EAAE,cAAc,GAAG,GAAG,CAAC,OAAO,CAuC9B"}
@@ -1,20 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /**
3
- * Chat input component
4
- */
5
- import { useState, useCallback } from 'react';
6
- import { useChatContext } from './ChatContext';
7
- export function ChatInput({ className = '', placeholder = 'Type a message...', disabled = false, onSubmit, }) {
8
- const { sendMessage, isLoading } = useChatContext();
9
- const [value, setValue] = useState('');
10
- const handleSubmit = useCallback((e) => {
11
- e.preventDefault();
12
- const text = value.trim();
13
- if (!text || isLoading)
14
- return;
15
- setValue('');
16
- onSubmit?.(text);
17
- sendMessage(text);
18
- }, [value, isLoading, sendMessage, onSubmit]);
19
- return (_jsxs("form", { className: `chatbot-form ${className}`, onSubmit: handleSubmit, children: [_jsx("input", { type: "text", className: "chatbot-input", placeholder: placeholder, value: value, onChange: (e) => setValue(e.target.value), disabled: disabled || isLoading, autoComplete: "off" }), _jsx("button", { type: "submit", className: "chatbot-send", disabled: disabled || isLoading || !value.trim(), children: _jsx("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "currentColor", children: _jsx("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) }) })] }));
20
- }