@prodact.ai/sdk 0.0.2

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/react.js ADDED
@@ -0,0 +1,1116 @@
1
+ "use client";
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/react/index.ts
32
+ var react_exports = {};
33
+ __export(react_exports, {
34
+ ProduckChat: () => ProduckChat,
35
+ ProduckContext: () => ProduckContext,
36
+ ProduckProvider: () => ProduckProvider,
37
+ ProduckTarget: () => ProduckTarget,
38
+ useProduck: () => useProduck,
39
+ useProduckAction: () => useProduckAction,
40
+ useProduckFlow: () => useProduckFlow,
41
+ useProduckMessages: () => useProduckMessages,
42
+ useProduckReady: () => useProduckReady
43
+ });
44
+ module.exports = __toCommonJS(react_exports);
45
+
46
+ // src/react/ProduckProvider.tsx
47
+ var import_react2 = require("react");
48
+
49
+ // src/core.ts
50
+ var ProduckSDK = class {
51
+ constructor(config) {
52
+ this.actions = /* @__PURE__ */ new Map();
53
+ this.eventListeners = /* @__PURE__ */ new Map();
54
+ this.sessionToken = null;
55
+ this.isReady = false;
56
+ let apiUrl = config.apiUrl;
57
+ if (!apiUrl) {
58
+ if (typeof window !== "undefined") {
59
+ apiUrl = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" ? "http://localhost:4001/api/v1" : `${window.location.protocol}//${window.location.host}/api/v1`;
60
+ } else {
61
+ apiUrl = "http://localhost:4001/api/v1";
62
+ }
63
+ }
64
+ this.config = {
65
+ apiUrl,
66
+ ...config
67
+ };
68
+ }
69
+ /**
70
+ * Initialize the SDK and create a chat session
71
+ */
72
+ async init() {
73
+ await this.log("info", "SDK Initializing", {
74
+ apiUrl: this.config.apiUrl,
75
+ hasSDKKey: !!this.config.sdkKey,
76
+ hasGuiderId: !!this.config.guiderId
77
+ });
78
+ try {
79
+ let endpoint;
80
+ if (this.config.sdkKey) {
81
+ endpoint = `${this.config.apiUrl}/sdk/session`;
82
+ await this.log("info", "Using SDK key authentication");
83
+ } else if (this.config.guiderId) {
84
+ endpoint = `${this.config.apiUrl}/chat/${this.config.guiderId}/session`;
85
+ await this.log("info", "Using guider ID authentication");
86
+ } else {
87
+ throw new Error("Either sdkKey or guiderId must be provided");
88
+ }
89
+ await this.log("info", "Creating session", { endpoint });
90
+ const headers = { "Content-Type": "application/json" };
91
+ if (this.config.sdkKey) {
92
+ headers["X-SDK-Key"] = this.config.sdkKey;
93
+ }
94
+ const response = await fetch(endpoint, {
95
+ method: "POST",
96
+ headers
97
+ });
98
+ if (!response.ok) {
99
+ await this.log("error", "Failed to create session", { status: response.status });
100
+ throw new Error(`Failed to create session: ${response.status}`);
101
+ }
102
+ const session = await response.json();
103
+ this.sessionToken = session.session_token;
104
+ this.isReady = true;
105
+ await this.log("info", "SDK initialized successfully", { hasSessionToken: !!this.sessionToken });
106
+ this.emit("ready", { sessionToken: this.sessionToken });
107
+ } catch (error) {
108
+ await this.log("error", "SDK initialization failed", { error: error instanceof Error ? error.message : String(error) });
109
+ this.emit("error", error);
110
+ throw error;
111
+ }
112
+ }
113
+ /**
114
+ * Register an action handler
115
+ */
116
+ register(actionKey, handler) {
117
+ this.actions.set(actionKey, handler);
118
+ this.log("info", "Action handler registered", { actionKey, totalActions: this.actions.size });
119
+ }
120
+ /**
121
+ * Unregister an action handler
122
+ */
123
+ unregister(actionKey) {
124
+ this.actions.delete(actionKey);
125
+ this.log("info", "Action handler unregistered", { actionKey, remainingActions: this.actions.size });
126
+ }
127
+ /**
128
+ * Send a message and handle potential action or flow triggers
129
+ */
130
+ async sendMessage(message) {
131
+ if (!this.isReady || !this.sessionToken) {
132
+ throw new Error("SDK not initialized. Call init() first.");
133
+ }
134
+ const startTime = Date.now();
135
+ await this.log("info", "sendMessage called", { message });
136
+ try {
137
+ let intentEndpoint;
138
+ const headers = { "Content-Type": "application/json" };
139
+ if (this.config.sdkKey) {
140
+ intentEndpoint = `${this.config.apiUrl}/sdk/match-intent`;
141
+ headers["X-SDK-Key"] = this.config.sdkKey;
142
+ } else {
143
+ intentEndpoint = `${this.config.apiUrl}/sdk/public/${this.config.guiderId}/match-intent`;
144
+ }
145
+ const intentResponse = await fetch(intentEndpoint, {
146
+ method: "POST",
147
+ headers,
148
+ body: JSON.stringify({ userMessage: message })
149
+ });
150
+ await this.log("info", "Match-intent response received", { status: intentResponse.status });
151
+ if (intentResponse.ok) {
152
+ const intentResult = await intentResponse.json();
153
+ if (intentResult.matched && intentResult.type === "flow" && intentResult.flow) {
154
+ await this.log("info", "Flow matched", {
155
+ flowId: intentResult.flow.flowId,
156
+ name: intentResult.flow.name,
157
+ stepCount: intentResult.flow.steps?.length
158
+ });
159
+ await this.sendLogToBackend({
160
+ userMessage: message,
161
+ matched: true,
162
+ actionKey: `flow:${intentResult.flow.flowId}`,
163
+ responseMessage: intentResult.responseMessage,
164
+ executionTimeMs: Date.now() - startTime
165
+ });
166
+ const flowResult = await this.executeFlow(intentResult.flow);
167
+ const flowMessage = {
168
+ role: "assistant",
169
+ content: intentResult.responseMessage || `I've completed the "${intentResult.flow.name}" flow for you.`,
170
+ flow: intentResult.flow,
171
+ flowResult
172
+ };
173
+ this.emit("message", flowMessage);
174
+ return flowMessage;
175
+ }
176
+ if (intentResult.matched && (intentResult.type === "operation" || intentResult.action)) {
177
+ const action = intentResult.action;
178
+ await this.log("info", "Action matched", {
179
+ actionKey: action.actionKey,
180
+ actionType: action.actionType,
181
+ responseMessage: action.responseMessage
182
+ });
183
+ await this.sendLogToBackend({
184
+ userMessage: message,
185
+ matched: true,
186
+ actionKey: action.actionKey,
187
+ responseMessage: action.responseMessage,
188
+ executionTimeMs: Date.now() - startTime
189
+ });
190
+ await this.executeAction(action);
191
+ const actionMessage = {
192
+ role: "assistant",
193
+ content: action.responseMessage || `I've triggered the "${action.name}" action for you.`,
194
+ action
195
+ };
196
+ this.emit("message", actionMessage);
197
+ return actionMessage;
198
+ } else {
199
+ await this.sendLogToBackend({
200
+ userMessage: message,
201
+ matched: false,
202
+ executionTimeMs: Date.now() - startTime
203
+ });
204
+ }
205
+ }
206
+ const sessionResponse = await fetch(
207
+ `${this.config.apiUrl}/chat/sessions/${this.sessionToken}/message`,
208
+ {
209
+ method: "POST",
210
+ headers: { "Content-Type": "application/json" },
211
+ body: JSON.stringify({ message })
212
+ }
213
+ );
214
+ if (!sessionResponse.ok) {
215
+ throw new Error(`Failed to send message: ${sessionResponse.status}`);
216
+ }
217
+ const result = await sessionResponse.json();
218
+ const chatMessage = {
219
+ role: "assistant",
220
+ content: result.message?.content || result.response || ""
221
+ };
222
+ await this.log("info", "Chat response received");
223
+ this.emit("message", chatMessage);
224
+ return chatMessage;
225
+ } catch (error) {
226
+ await this.log("error", "Error in sendMessage", { error: error instanceof Error ? error.message : String(error) });
227
+ this.emit("error", error);
228
+ throw error;
229
+ }
230
+ }
231
+ /**
232
+ * Execute a flow by running each step's operation sequentially
233
+ */
234
+ async executeFlow(flow) {
235
+ await this.log("info", "executeFlow started", {
236
+ flowId: flow.flowId,
237
+ name: flow.name,
238
+ stepCount: flow.steps.length
239
+ });
240
+ this.emit("flowStart", flow);
241
+ if (this.config.onFlowStart) {
242
+ this.config.onFlowStart(flow);
243
+ }
244
+ const stepResults = [];
245
+ let context = {};
246
+ const sortedSteps = [...flow.steps].sort((a, b) => a.order - b.order);
247
+ for (const step of sortedSteps) {
248
+ await this.log("info", "Executing flow step", {
249
+ flowId: flow.flowId,
250
+ operationId: step.operationId,
251
+ order: step.order
252
+ });
253
+ const stepResult = {
254
+ operationId: step.operationId,
255
+ order: step.order,
256
+ success: false
257
+ };
258
+ try {
259
+ const handler = this.actions.get(step.operationId);
260
+ if (handler) {
261
+ const actionPayload = {
262
+ actionKey: step.operationId,
263
+ name: step.operationId,
264
+ actionType: "callback",
265
+ actionConfig: {
266
+ flowContext: context,
267
+ inputMapping: step.inputMapping
268
+ }
269
+ };
270
+ const result = await handler(actionPayload);
271
+ stepResult.success = true;
272
+ stepResult.data = result;
273
+ context = {
274
+ ...context,
275
+ [`step_${step.order}`]: result,
276
+ previousResult: result
277
+ };
278
+ await this.log("info", "Flow step completed successfully", {
279
+ flowId: flow.flowId,
280
+ operationId: step.operationId,
281
+ order: step.order
282
+ });
283
+ } else {
284
+ await this.log("warn", "No handler registered for flow step", {
285
+ flowId: flow.flowId,
286
+ operationId: step.operationId
287
+ });
288
+ stepResult.success = true;
289
+ stepResult.data = { skipped: true, reason: "No handler registered" };
290
+ }
291
+ } catch (error) {
292
+ stepResult.success = false;
293
+ stepResult.error = error instanceof Error ? error.message : String(error);
294
+ await this.log("error", "Flow step failed", {
295
+ flowId: flow.flowId,
296
+ operationId: step.operationId,
297
+ error: stepResult.error
298
+ });
299
+ }
300
+ stepResults.push(stepResult);
301
+ this.emit("flowStepComplete", { step: stepResult, flow });
302
+ if (this.config.onFlowStepComplete) {
303
+ this.config.onFlowStepComplete(stepResult, flow);
304
+ }
305
+ }
306
+ const flowResult = {
307
+ flowId: flow.flowId,
308
+ name: flow.name,
309
+ steps: stepResults,
310
+ completed: true,
311
+ totalSteps: flow.steps.length,
312
+ successfulSteps: stepResults.filter((s) => s.success).length
313
+ };
314
+ await this.log("info", "Flow execution completed", {
315
+ flowId: flow.flowId,
316
+ totalSteps: flowResult.totalSteps,
317
+ successfulSteps: flowResult.successfulSteps
318
+ });
319
+ this.emit("flowComplete", flowResult);
320
+ if (this.config.onFlowComplete) {
321
+ this.config.onFlowComplete(flowResult);
322
+ }
323
+ return flowResult;
324
+ }
325
+ /**
326
+ * Send log data to backend for analytics
327
+ */
328
+ async sendLogToBackend(logData) {
329
+ try {
330
+ const headers = { "Content-Type": "application/json" };
331
+ if (this.config.sdkKey) {
332
+ headers["X-SDK-Key"] = this.config.sdkKey;
333
+ }
334
+ const logEndpoint = `${this.config.apiUrl}/sdk-logs/client`;
335
+ await fetch(logEndpoint, {
336
+ method: "POST",
337
+ headers,
338
+ body: JSON.stringify({
339
+ ...logData,
340
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
341
+ sessionToken: this.sessionToken
342
+ })
343
+ });
344
+ } catch (error) {
345
+ }
346
+ }
347
+ /**
348
+ * Send detailed log to backend
349
+ */
350
+ async log(level, message, data) {
351
+ try {
352
+ const headers = { "Content-Type": "application/json" };
353
+ if (this.config.sdkKey) {
354
+ headers["X-SDK-Key"] = this.config.sdkKey;
355
+ }
356
+ const logEndpoint = `${this.config.apiUrl}/sdk-logs/client`;
357
+ await fetch(logEndpoint, {
358
+ method: "POST",
359
+ headers,
360
+ body: JSON.stringify({
361
+ level,
362
+ message,
363
+ data,
364
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
365
+ sessionToken: this.sessionToken
366
+ })
367
+ });
368
+ } catch (error) {
369
+ }
370
+ }
371
+ /**
372
+ * Execute a registered action
373
+ */
374
+ async executeAction(action) {
375
+ const registeredKeys = Array.from(this.actions.keys());
376
+ if (typeof window !== "undefined") {
377
+ console.log("%c\u{1F3AF} SDK executeAction", "background: #ff0; color: #000; font-size: 20px; padding: 10px;", {
378
+ actionKey: action.actionKey,
379
+ totalRegistered: this.actions.size,
380
+ registeredKeys
381
+ });
382
+ }
383
+ await this.log("info", "executeAction called", {
384
+ actionKey: action.actionKey,
385
+ actionType: typeof action.actionKey,
386
+ totalRegistered: this.actions.size,
387
+ registeredKeys,
388
+ keyComparisons: registeredKeys.map((key) => ({
389
+ key,
390
+ matches: key === action.actionKey,
391
+ keyLength: key.length,
392
+ actionKeyLength: action.actionKey.length
393
+ }))
394
+ });
395
+ const handler = this.actions.get(action.actionKey);
396
+ if (typeof window !== "undefined") {
397
+ console.log("%c\u{1F50D} SDK Handler Lookup", "background: #0ff; color: #000; font-size: 16px; padding: 5px;", {
398
+ actionKey: action.actionKey,
399
+ found: !!handler,
400
+ hasConfigOnAction: !!this.config.onAction,
401
+ registeredKeys: Array.from(this.actions.keys())
402
+ });
403
+ }
404
+ if (handler) {
405
+ try {
406
+ const startTime = Date.now();
407
+ await handler(action);
408
+ const executionTime = Date.now() - startTime;
409
+ await this.log("info", "Handler executed successfully", {
410
+ actionKey: action.actionKey,
411
+ executionTime
412
+ });
413
+ await this.sendLogToBackend({
414
+ userMessage: `Action executed: ${action.actionKey}`,
415
+ matched: true,
416
+ actionKey: action.actionKey,
417
+ responseMessage: action.responseMessage || `Executed ${action.name}`,
418
+ executionTimeMs: executionTime
419
+ });
420
+ this.emit("action", action);
421
+ this.config.onAction?.(action.actionKey, action);
422
+ } catch (error) {
423
+ await this.log("error", "Handler execution failed", {
424
+ actionKey: action.actionKey,
425
+ error: error instanceof Error ? error.message : String(error),
426
+ stack: error instanceof Error ? error.stack : void 0
427
+ });
428
+ await this.sendLogToBackend({
429
+ userMessage: `Action execution failed: ${action.actionKey}`,
430
+ matched: true,
431
+ actionKey: action.actionKey,
432
+ error: error instanceof Error ? error.message : String(error)
433
+ });
434
+ this.emit("error", error);
435
+ }
436
+ } else {
437
+ if (this.config.onAction) {
438
+ await this.log("info", "No internal handler, trying config.onAction callback", {
439
+ actionKey: action.actionKey
440
+ });
441
+ try {
442
+ this.config.onAction(action.actionKey, action);
443
+ this.emit("action", action);
444
+ await this.log("info", "Action dispatched via config.onAction", {
445
+ actionKey: action.actionKey
446
+ });
447
+ await this.sendLogToBackend({
448
+ userMessage: `Action dispatched: ${action.actionKey}`,
449
+ matched: true,
450
+ actionKey: action.actionKey,
451
+ responseMessage: action.responseMessage || `Dispatched ${action.name}`
452
+ });
453
+ return;
454
+ } catch (error) {
455
+ await this.log("error", "config.onAction callback failed", {
456
+ actionKey: action.actionKey,
457
+ error: error instanceof Error ? error.message : String(error)
458
+ });
459
+ }
460
+ }
461
+ await this.log("warn", "No handler registered for action", {
462
+ actionKey: action.actionKey,
463
+ registeredActions: Array.from(this.actions.keys())
464
+ });
465
+ await this.sendLogToBackend({
466
+ userMessage: `No handler for action: ${action.actionKey}`,
467
+ matched: false,
468
+ actionKey: action.actionKey,
469
+ error: `Handler not registered. Available: ${Array.from(this.actions.keys()).join(", ")}`
470
+ });
471
+ }
472
+ }
473
+ /**
474
+ * Add event listener
475
+ */
476
+ on(event, callback) {
477
+ if (!this.eventListeners.has(event)) {
478
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
479
+ }
480
+ this.eventListeners.get(event).add(callback);
481
+ }
482
+ /**
483
+ * Remove event listener
484
+ */
485
+ off(event, callback) {
486
+ this.eventListeners.get(event)?.delete(callback);
487
+ }
488
+ /**
489
+ * Emit event
490
+ */
491
+ emit(event, data) {
492
+ this.eventListeners.get(event)?.forEach((callback) => callback(data));
493
+ }
494
+ /**
495
+ * Get session token
496
+ */
497
+ getSessionToken() {
498
+ return this.sessionToken;
499
+ }
500
+ /**
501
+ * Check if SDK is ready
502
+ */
503
+ getIsReady() {
504
+ return this.isReady;
505
+ }
506
+ /**
507
+ * Get registered action keys
508
+ */
509
+ getRegisteredActions() {
510
+ return Array.from(this.actions.keys());
511
+ }
512
+ /**
513
+ * Destroy the SDK instance
514
+ */
515
+ destroy() {
516
+ this.actions.clear();
517
+ this.eventListeners.clear();
518
+ this.sessionToken = null;
519
+ this.isReady = false;
520
+ }
521
+ };
522
+
523
+ // src/react/context.ts
524
+ var import_react = require("react");
525
+ var ProduckContext = (0, import_react.createContext)(null);
526
+ function useProduckContext() {
527
+ const context = (0, import_react.useContext)(ProduckContext);
528
+ if (!context) {
529
+ throw new Error("useProduck must be used within a ProduckProvider");
530
+ }
531
+ return context;
532
+ }
533
+
534
+ // src/react/ProduckProvider.tsx
535
+ var import_jsx_runtime = require("react/jsx-runtime");
536
+ function ProduckProvider({
537
+ config,
538
+ children,
539
+ onAction,
540
+ onError,
541
+ onFlowStart,
542
+ onFlowStepComplete,
543
+ onFlowComplete
544
+ }) {
545
+ const [sdk, setSdk] = (0, import_react2.useState)(null);
546
+ const [isReady, setIsReady] = (0, import_react2.useState)(false);
547
+ const [sessionToken, setSessionToken] = (0, import_react2.useState)(null);
548
+ const [messages, setMessages] = (0, import_react2.useState)([]);
549
+ const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
550
+ const [activeFlow, setActiveFlow] = (0, import_react2.useState)(null);
551
+ const [flowResult, setFlowResult] = (0, import_react2.useState)(null);
552
+ const [isExecutingFlow, setIsExecutingFlow] = (0, import_react2.useState)(false);
553
+ const actionsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
554
+ const initAttempted = (0, import_react2.useRef)(false);
555
+ (0, import_react2.useEffect)(() => {
556
+ if (initAttempted.current) return;
557
+ initAttempted.current = true;
558
+ const produckSdk = new ProduckSDK({
559
+ ...config,
560
+ onAction: (key, payload) => {
561
+ const handler = actionsRef.current.get(key);
562
+ if (handler) {
563
+ handler(payload);
564
+ }
565
+ onAction?.(key, payload);
566
+ },
567
+ onError,
568
+ // Flow event handlers
569
+ onFlowStart: (flow) => {
570
+ setActiveFlow(flow);
571
+ setIsExecutingFlow(true);
572
+ setFlowResult(null);
573
+ onFlowStart?.(flow);
574
+ },
575
+ onFlowStepComplete: (step, flow) => {
576
+ onFlowStepComplete?.(step, flow);
577
+ },
578
+ onFlowComplete: (result) => {
579
+ setFlowResult(result);
580
+ setIsExecutingFlow(false);
581
+ onFlowComplete?.(result);
582
+ }
583
+ });
584
+ setSdk(produckSdk);
585
+ produckSdk.init().then(() => {
586
+ setIsReady(true);
587
+ setSessionToken(produckSdk.getSessionToken());
588
+ }).catch((err) => {
589
+ console.error("Failed to initialize Produck SDK:", err);
590
+ setIsReady(true);
591
+ onError?.(err);
592
+ });
593
+ return () => {
594
+ produckSdk.destroy();
595
+ initAttempted.current = false;
596
+ };
597
+ }, [config.guiderId, config.apiUrl, config.sdkKey]);
598
+ const sendMessage = (0, import_react2.useCallback)(async (message) => {
599
+ if (!sdk || !isReady) return;
600
+ setIsLoading(true);
601
+ setMessages((prev) => [...prev, { role: "user", content: message }]);
602
+ try {
603
+ const response = await sdk.sendMessage(message);
604
+ setMessages((prev) => [...prev, response]);
605
+ } catch (error) {
606
+ console.error("Failed to send message:", error);
607
+ onError?.(error);
608
+ } finally {
609
+ setIsLoading(false);
610
+ }
611
+ }, [sdk, isReady, onError]);
612
+ const register = (0, import_react2.useCallback)((actionKey, handler) => {
613
+ console.log("%c\u{1F4DD} [Provider] register called", "background: #0f0; color: #000;", {
614
+ actionKey,
615
+ sdkExists: !!sdk,
616
+ isReady
617
+ });
618
+ actionsRef.current.set(actionKey, handler);
619
+ if (sdk) {
620
+ sdk.register(actionKey, handler);
621
+ console.log("%c\u2705 [Provider] Registered with SDK", "background: #0f0; color: #000;", {
622
+ actionKey,
623
+ sdkRegisteredActions: sdk.getRegisteredActions()
624
+ });
625
+ } else {
626
+ console.log("%c\u23F3 [Provider] SDK not ready, stored in actionsRef only", "background: #ff0; color: #000;", { actionKey });
627
+ }
628
+ }, [sdk, isReady]);
629
+ (0, import_react2.useEffect)(() => {
630
+ if (sdk && actionsRef.current.size > 0) {
631
+ console.log("%c\u{1F504} [Provider] SDK ready, re-registering actions", "background: #0ff; color: #000;", {
632
+ actionsToRegister: Array.from(actionsRef.current.keys())
633
+ });
634
+ actionsRef.current.forEach((handler, key) => {
635
+ sdk.register(key, handler);
636
+ });
637
+ console.log("%c\u2705 [Provider] All actions re-registered", "background: #0f0; color: #000;", {
638
+ registeredActions: sdk.getRegisteredActions()
639
+ });
640
+ }
641
+ }, [sdk]);
642
+ const unregister = (0, import_react2.useCallback)((actionKey) => {
643
+ actionsRef.current.delete(actionKey);
644
+ sdk?.unregister(actionKey);
645
+ }, [sdk]);
646
+ const contextValue = {
647
+ sdk,
648
+ isReady,
649
+ sessionToken,
650
+ messages,
651
+ isLoading,
652
+ sendMessage,
653
+ register,
654
+ unregister,
655
+ // Flow-related state
656
+ activeFlow,
657
+ flowResult,
658
+ isExecutingFlow
659
+ };
660
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProduckContext.Provider, { value: contextValue, children });
661
+ }
662
+
663
+ // src/react/ProduckChat.tsx
664
+ var import_react4 = require("react");
665
+
666
+ // src/react/hooks.ts
667
+ var import_react3 = require("react");
668
+ function useProduck() {
669
+ const context = useProduckContext();
670
+ return context;
671
+ }
672
+ function useProduckAction(actionKey, handler, deps = []) {
673
+ const { register, unregister } = useProduckContext();
674
+ const memoizedHandler = (0, import_react3.useCallback)(handler, deps);
675
+ (0, import_react3.useEffect)(() => {
676
+ console.log("\u{1F3A3} [React Hook] useProduckAction - Registering action");
677
+ console.log(" Action Key:", actionKey);
678
+ register(actionKey, memoizedHandler);
679
+ return () => {
680
+ console.log("\u{1F3A3} [React Hook] useProduckAction - Cleanup, unregistering action");
681
+ console.log(" Action Key:", actionKey);
682
+ unregister(actionKey);
683
+ };
684
+ }, [actionKey, memoizedHandler, register, unregister]);
685
+ }
686
+ function useProduckReady() {
687
+ const { isReady } = useProduckContext();
688
+ return isReady;
689
+ }
690
+ function useProduckMessages() {
691
+ const { messages, isLoading, sendMessage } = useProduckContext();
692
+ return { messages, isLoading, sendMessage };
693
+ }
694
+ function useProduckFlow() {
695
+ const { activeFlow, flowResult, isExecutingFlow } = useProduckContext();
696
+ return { activeFlow, flowResult, isExecutingFlow };
697
+ }
698
+
699
+ // src/react/ProduckChat.tsx
700
+ var import_jsx_runtime2 = require("react/jsx-runtime");
701
+ function ProduckChat({
702
+ placeholder = "Ask a question...",
703
+ title = "Chat Assistant",
704
+ position = "bottom-right",
705
+ theme = "light",
706
+ primaryColor = "#f97316",
707
+ className = "",
708
+ style = {},
709
+ defaultOpen = false,
710
+ appearance = {}
711
+ }) {
712
+ const { messages, isLoading, sendMessage } = useProduckMessages();
713
+ const isReady = useProduckReady();
714
+ const [input, setInput] = (0, import_react4.useState)("");
715
+ const [isOpen, setIsOpen] = (0, import_react4.useState)(position === "inline" ? true : defaultOpen);
716
+ const [isHovering, setIsHovering] = (0, import_react4.useState)(false);
717
+ const messagesEndRef = (0, import_react4.useRef)(null);
718
+ const inputRef = (0, import_react4.useRef)(null);
719
+ (0, import_react4.useEffect)(() => {
720
+ if (messagesEndRef.current) {
721
+ messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
722
+ }
723
+ }, [messages]);
724
+ (0, import_react4.useEffect)(() => {
725
+ if (isOpen && inputRef.current) {
726
+ setTimeout(() => inputRef.current?.focus(), 100);
727
+ }
728
+ }, [isOpen]);
729
+ const handleSubmit = async (e) => {
730
+ e.preventDefault();
731
+ if (!input.trim() || isLoading) return;
732
+ const message = input;
733
+ setInput("");
734
+ await sendMessage(message);
735
+ };
736
+ const isDark = theme === "dark";
737
+ const {
738
+ width = position === "inline" ? "100%" : "380px",
739
+ height = position === "inline" ? "100%" : "520px",
740
+ borderRadius: containerBorderRadius = "16px",
741
+ backgroundColor = isDark ? "#1f2937" : "#ffffff",
742
+ headerBackgroundColor = primaryColor,
743
+ headerTextColor = "#ffffff",
744
+ inputBackgroundColor = isDark ? "#1f2937" : "#ffffff",
745
+ inputTextColor = isDark ? "#f3f4f6" : "#1f2937",
746
+ inputBorderColor = isDark ? "#374151" : "#e5e7eb",
747
+ userMessageBackgroundColor = primaryColor,
748
+ userMessageTextColor = "#ffffff",
749
+ assistantMessageBackgroundColor = isDark ? "#374151" : "#f3f4f6",
750
+ assistantMessageTextColor = isDark ? "#f3f4f6" : "#1f2937",
751
+ buttonBackgroundColor = primaryColor,
752
+ buttonTextColor = "#ffffff",
753
+ buttonBorderRadius = "12px",
754
+ floatingButtonSize = "60px",
755
+ fontFamily = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
756
+ fontSize = "14px",
757
+ headerFontSize = "16px",
758
+ border = `1px solid ${isDark ? "#374151" : "#e5e7eb"}`,
759
+ boxShadow = "0 10px 40px rgba(0,0,0,0.15), 0 0 0 1px rgba(0,0,0,0.05)",
760
+ floatingButtonIcon = "\u{1F4AC}",
761
+ floatingButtonLoadingIcon = "\u23F3",
762
+ showCloseButton = true,
763
+ headerIcon = "\u{1F4AC}",
764
+ sendButtonText = "Send",
765
+ sendButtonIcon,
766
+ emptyStateIcon = "\u{1F44B}",
767
+ emptyStateTitle = "Hi! How can I help you today?",
768
+ emptyStateSubtitle = "Type a message below to get started."
769
+ } = appearance;
770
+ const containerStyles = {
771
+ fontFamily,
772
+ ...position !== "inline" && {
773
+ position: "fixed",
774
+ bottom: "20px",
775
+ [position === "bottom-right" ? "right" : "left"]: "20px",
776
+ zIndex: 9999
777
+ },
778
+ ...style
779
+ };
780
+ const chatWindowStyles = {
781
+ width,
782
+ height,
783
+ maxHeight: position === "inline" ? "100%" : "80vh",
784
+ backgroundColor,
785
+ borderRadius: containerBorderRadius,
786
+ boxShadow,
787
+ display: "flex",
788
+ flexDirection: "column",
789
+ overflow: "hidden",
790
+ border
791
+ };
792
+ const headerStyles = {
793
+ padding: "16px 20px",
794
+ backgroundColor: headerBackgroundColor,
795
+ color: headerTextColor,
796
+ fontWeight: 600,
797
+ fontSize: headerFontSize,
798
+ display: "flex",
799
+ alignItems: "center",
800
+ justifyContent: "space-between",
801
+ flexShrink: 0
802
+ };
803
+ const messagesContainerStyles = {
804
+ flex: 1,
805
+ overflowY: "auto",
806
+ padding: "16px",
807
+ display: "flex",
808
+ flexDirection: "column",
809
+ gap: "12px",
810
+ minHeight: 0
811
+ };
812
+ const inputContainerStyles = {
813
+ padding: "16px",
814
+ borderTop: `1px solid ${inputBorderColor}`,
815
+ backgroundColor: isDark ? "#111827" : "#f9fafb",
816
+ flexShrink: 0
817
+ };
818
+ const renderFloatingButton = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: containerStyles, className, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
819
+ "button",
820
+ {
821
+ onClick: () => setIsOpen(true),
822
+ onMouseEnter: () => setIsHovering(true),
823
+ onMouseLeave: () => setIsHovering(false),
824
+ disabled: !isReady,
825
+ "aria-label": "Open chat",
826
+ style: {
827
+ width: typeof floatingButtonSize === "number" ? `${floatingButtonSize}px` : floatingButtonSize,
828
+ height: typeof floatingButtonSize === "number" ? `${floatingButtonSize}px` : floatingButtonSize,
829
+ borderRadius: "50%",
830
+ backgroundColor: buttonBackgroundColor,
831
+ color: buttonTextColor,
832
+ border: "none",
833
+ cursor: isReady ? "pointer" : "wait",
834
+ boxShadow: isHovering ? "0 6px 24px rgba(0,0,0,0.3)" : "0 4px 20px rgba(0,0,0,0.2)",
835
+ display: "flex",
836
+ alignItems: "center",
837
+ justifyContent: "center",
838
+ fontSize: "26px",
839
+ transition: "all 0.2s ease",
840
+ transform: isHovering ? "scale(1.05)" : "scale(1)",
841
+ opacity: isReady ? 1 : 0.7
842
+ },
843
+ children: isReady ? floatingButtonIcon : floatingButtonLoadingIcon
844
+ }
845
+ ) });
846
+ if (position !== "inline" && !isOpen) {
847
+ return renderFloatingButton();
848
+ }
849
+ if (position === "inline" && !isReady) {
850
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: containerStyles, className, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: chatWindowStyles, children: [
851
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...headerStyles, justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
852
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { animation: "spin 1s linear infinite" }, children: floatingButtonLoadingIcon }),
853
+ "Loading..."
854
+ ] }) }),
855
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
856
+ flex: 1,
857
+ display: "flex",
858
+ alignItems: "center",
859
+ justifyContent: "center",
860
+ color: isDark ? "#9ca3af" : "#6b7280"
861
+ }, children: "Connecting to chat..." })
862
+ ] }) });
863
+ }
864
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: containerStyles, className, children: [
865
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: chatWindowStyles, children: [
866
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: headerStyles, children: [
867
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
868
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: headerIcon }),
869
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: title })
870
+ ] }),
871
+ position !== "inline" && showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
872
+ "button",
873
+ {
874
+ onClick: () => setIsOpen(false),
875
+ "aria-label": "Close chat",
876
+ style: {
877
+ background: "rgba(255,255,255,0.2)",
878
+ border: "none",
879
+ color: "#ffffff",
880
+ cursor: "pointer",
881
+ fontSize: "16px",
882
+ padding: "4px 8px",
883
+ borderRadius: "6px",
884
+ lineHeight: 1,
885
+ transition: "background 0.2s ease"
886
+ },
887
+ onMouseEnter: (e) => e.currentTarget.style.background = "rgba(255,255,255,0.3)",
888
+ onMouseLeave: (e) => e.currentTarget.style.background = "rgba(255,255,255,0.2)",
889
+ children: "\u2715"
890
+ }
891
+ )
892
+ ] }),
893
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: messagesContainerStyles, children: [
894
+ messages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
895
+ "div",
896
+ {
897
+ style: {
898
+ textAlign: "center",
899
+ color: isDark ? "#9ca3af" : "#6b7280",
900
+ padding: "40px 20px"
901
+ },
902
+ children: [
903
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: "40px", marginBottom: "16px" }, children: emptyStateIcon }),
904
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: typeof fontSize === "number" ? `${fontSize + 2}px` : fontSize, fontWeight: 500 }, children: emptyStateTitle }),
905
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: typeof fontSize === "number" ? `${fontSize}px` : fontSize, marginTop: "8px", opacity: 0.8 }, children: emptyStateSubtitle })
906
+ ]
907
+ }
908
+ ),
909
+ messages.map((msg, idx) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
910
+ "div",
911
+ {
912
+ style: {
913
+ display: "flex",
914
+ justifyContent: msg.role === "user" ? "flex-end" : "flex-start"
915
+ },
916
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
917
+ "div",
918
+ {
919
+ style: {
920
+ maxWidth: "85%",
921
+ padding: "12px 16px",
922
+ borderRadius: msg.role === "user" ? "16px 16px 4px 16px" : "16px 16px 16px 4px",
923
+ backgroundColor: msg.role === "user" ? userMessageBackgroundColor : assistantMessageBackgroundColor,
924
+ color: msg.role === "user" ? userMessageTextColor : assistantMessageTextColor,
925
+ fontSize: typeof fontSize === "number" ? `${fontSize}px` : fontSize,
926
+ lineHeight: "1.5",
927
+ wordBreak: "break-word"
928
+ },
929
+ children: [
930
+ msg.content,
931
+ msg.action && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
932
+ "div",
933
+ {
934
+ style: {
935
+ marginTop: "8px",
936
+ padding: "8px 12px",
937
+ backgroundColor: "rgba(0,0,0,0.1)",
938
+ borderRadius: "8px",
939
+ fontSize: "12px",
940
+ display: "flex",
941
+ alignItems: "center",
942
+ gap: "6px"
943
+ },
944
+ children: [
945
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "\u26A1" }),
946
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
947
+ "Action triggered: ",
948
+ msg.action.name
949
+ ] })
950
+ ]
951
+ }
952
+ )
953
+ ]
954
+ }
955
+ )
956
+ },
957
+ idx
958
+ )),
959
+ isLoading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", justifyContent: "flex-start" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
960
+ "div",
961
+ {
962
+ style: {
963
+ padding: "12px 16px",
964
+ borderRadius: "16px 16px 16px 4px",
965
+ backgroundColor: assistantMessageBackgroundColor,
966
+ color: isDark ? "#9ca3af" : "#6b7280",
967
+ fontSize: typeof fontSize === "number" ? `${fontSize}px` : fontSize
968
+ },
969
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: {
970
+ display: "inline-block",
971
+ animation: "pulse 1.5s ease-in-out infinite"
972
+ }, children: "Thinking..." })
973
+ }
974
+ ) }),
975
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: messagesEndRef })
976
+ ] }),
977
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("form", { onSubmit: handleSubmit, style: inputContainerStyles, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: "10px", alignItems: "center" }, children: [
978
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
979
+ "input",
980
+ {
981
+ ref: inputRef,
982
+ type: "text",
983
+ value: input,
984
+ onChange: (e) => setInput(e.target.value),
985
+ placeholder,
986
+ disabled: isLoading || !isReady,
987
+ style: {
988
+ flex: 1,
989
+ padding: "14px 18px",
990
+ borderRadius: typeof buttonBorderRadius === "number" ? `${buttonBorderRadius}px` : buttonBorderRadius,
991
+ border: `2px solid ${inputBorderColor}`,
992
+ backgroundColor: inputBackgroundColor,
993
+ color: inputTextColor,
994
+ fontSize: typeof fontSize === "number" ? `${fontSize + 1}px` : fontSize,
995
+ outline: "none",
996
+ transition: "border-color 0.2s ease, box-shadow 0.2s ease",
997
+ boxSizing: "border-box"
998
+ },
999
+ onFocus: (e) => {
1000
+ e.currentTarget.style.borderColor = primaryColor;
1001
+ e.currentTarget.style.boxShadow = `0 0 0 3px ${primaryColor}22`;
1002
+ },
1003
+ onBlur: (e) => {
1004
+ e.currentTarget.style.borderColor = inputBorderColor;
1005
+ e.currentTarget.style.boxShadow = "none";
1006
+ }
1007
+ }
1008
+ ),
1009
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1010
+ "button",
1011
+ {
1012
+ type: "submit",
1013
+ disabled: isLoading || !input.trim() || !isReady,
1014
+ style: {
1015
+ padding: "14px 24px",
1016
+ borderRadius: typeof buttonBorderRadius === "number" ? `${buttonBorderRadius}px` : buttonBorderRadius,
1017
+ backgroundColor: buttonBackgroundColor,
1018
+ color: buttonTextColor,
1019
+ border: "none",
1020
+ cursor: isLoading || !input.trim() || !isReady ? "not-allowed" : "pointer",
1021
+ opacity: isLoading || !input.trim() || !isReady ? 0.5 : 1,
1022
+ fontWeight: 600,
1023
+ fontSize: typeof fontSize === "number" ? `${fontSize + 1}px` : fontSize,
1024
+ transition: "all 0.2s ease",
1025
+ flexShrink: 0,
1026
+ display: "flex",
1027
+ alignItems: "center",
1028
+ gap: "6px"
1029
+ },
1030
+ onMouseEnter: (e) => {
1031
+ if (!isLoading && input.trim() && isReady) {
1032
+ e.currentTarget.style.transform = "scale(1.02)";
1033
+ e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.2)";
1034
+ }
1035
+ },
1036
+ onMouseLeave: (e) => {
1037
+ e.currentTarget.style.transform = "scale(1)";
1038
+ e.currentTarget.style.boxShadow = "none";
1039
+ },
1040
+ children: [
1041
+ sendButtonIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: sendButtonIcon }),
1042
+ sendButtonText
1043
+ ]
1044
+ }
1045
+ )
1046
+ ] }) })
1047
+ ] }),
1048
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `
1049
+ @keyframes pulse {
1050
+ 0%, 100% { opacity: 1; }
1051
+ 50% { opacity: 0.5; }
1052
+ }
1053
+ @keyframes spin {
1054
+ from { transform: rotate(0deg); }
1055
+ to { transform: rotate(360deg); }
1056
+ }
1057
+ ` })
1058
+ ] });
1059
+ }
1060
+
1061
+ // src/react/ProduckTarget.tsx
1062
+ var import_react5 = __toESM(require("react"));
1063
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1064
+ function ProduckTarget({
1065
+ actionKey,
1066
+ children,
1067
+ onTrigger,
1068
+ highlightStyle = {
1069
+ outline: "3px solid #f97316",
1070
+ outlineOffset: "2px",
1071
+ borderRadius: "8px",
1072
+ transition: "outline 0.3s ease"
1073
+ },
1074
+ highlightDuration = 3e3,
1075
+ scrollIntoView = true
1076
+ }) {
1077
+ const ref = (0, import_react5.useRef)(null);
1078
+ const [isHighlighted, setIsHighlighted] = import_react5.default.useState(false);
1079
+ useProduckAction(
1080
+ actionKey,
1081
+ (payload) => {
1082
+ if (scrollIntoView && ref.current) {
1083
+ ref.current.scrollIntoView({
1084
+ behavior: "smooth",
1085
+ block: "center"
1086
+ });
1087
+ }
1088
+ setIsHighlighted(true);
1089
+ setTimeout(() => setIsHighlighted(false), highlightDuration);
1090
+ onTrigger?.(payload);
1091
+ },
1092
+ [scrollIntoView, highlightDuration, onTrigger]
1093
+ );
1094
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1095
+ "div",
1096
+ {
1097
+ ref,
1098
+ style: isHighlighted ? highlightStyle : void 0,
1099
+ "data-produck-target": actionKey,
1100
+ children
1101
+ }
1102
+ );
1103
+ }
1104
+ // Annotate the CommonJS export names for ESM import in node:
1105
+ 0 && (module.exports = {
1106
+ ProduckChat,
1107
+ ProduckContext,
1108
+ ProduckProvider,
1109
+ ProduckTarget,
1110
+ useProduck,
1111
+ useProduckAction,
1112
+ useProduckFlow,
1113
+ useProduckMessages,
1114
+ useProduckReady
1115
+ });
1116
+ //# sourceMappingURL=react.js.map