@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.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
|