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