@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/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/index.d.mts +135 -0
- package/dist/index.d.ts +135 -0
- package/dist/index.js +509 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +481 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react.d.mts +244 -0
- package/dist/react.d.ts +244 -0
- package/dist/react.js +1116 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +1072 -0
- package/dist/react.mjs.map +1 -0
- package/package.json +70 -0
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
|