@elqnt/chat 2.0.7 → 3.0.0
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/README.md +386 -0
- package/dist/api/index.d.mts +308 -0
- package/dist/api/index.d.ts +308 -0
- package/dist/api/index.js +220 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/index.mjs +183 -0
- package/dist/api/index.mjs.map +1 -0
- package/dist/hooks/index.d.mts +78 -0
- package/dist/hooks/index.d.ts +78 -0
- package/dist/hooks/index.js +709 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +683 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index.d.mts +4 -109
- package/dist/index.d.ts +4 -109
- package/dist/index.js +699 -3607
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +690 -3584
- package/dist/index.mjs.map +1 -1
- package/dist/models/index.d.mts +76 -6
- package/dist/models/index.d.ts +76 -6
- package/dist/models/index.js +21 -0
- package/dist/models/index.js.map +1 -1
- package/dist/models/index.mjs +14 -0
- package/dist/models/index.mjs.map +1 -1
- package/dist/transport/index.d.mts +243 -0
- package/dist/transport/index.d.ts +243 -0
- package/dist/transport/index.js +875 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/index.mjs +843 -0
- package/dist/transport/index.mjs.map +1 -0
- package/dist/types-BB5nRdZs.d.mts +222 -0
- package/dist/types-CNvuxtcv.d.ts +222 -0
- package/package.json +32 -40
- package/dist/hooks/use-websocket-chat-admin.d.mts +0 -17
- package/dist/hooks/use-websocket-chat-admin.d.ts +0 -17
- package/dist/hooks/use-websocket-chat-admin.js +0 -1196
- package/dist/hooks/use-websocket-chat-admin.js.map +0 -1
- package/dist/hooks/use-websocket-chat-admin.mjs +0 -1172
- package/dist/hooks/use-websocket-chat-admin.mjs.map +0 -1
- package/dist/hooks/use-websocket-chat-base.d.mts +0 -81
- package/dist/hooks/use-websocket-chat-base.d.ts +0 -81
- package/dist/hooks/use-websocket-chat-base.js +0 -1025
- package/dist/hooks/use-websocket-chat-base.js.map +0 -1
- package/dist/hooks/use-websocket-chat-base.mjs +0 -1001
- package/dist/hooks/use-websocket-chat-base.mjs.map +0 -1
- package/dist/hooks/use-websocket-chat-customer.d.mts +0 -24
- package/dist/hooks/use-websocket-chat-customer.d.ts +0 -24
- package/dist/hooks/use-websocket-chat-customer.js +0 -1092
- package/dist/hooks/use-websocket-chat-customer.js.map +0 -1
- package/dist/hooks/use-websocket-chat-customer.mjs +0 -1068
- package/dist/hooks/use-websocket-chat-customer.mjs.map +0 -1
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// hooks/use-chat.ts
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
|
+
|
|
6
|
+
// transport/types.ts
|
|
7
|
+
function createLogger(debug = false) {
|
|
8
|
+
return {
|
|
9
|
+
debug: debug ? console.log.bind(console, "[chat]") : () => {
|
|
10
|
+
},
|
|
11
|
+
info: console.info.bind(console, "[chat]"),
|
|
12
|
+
warn: console.warn.bind(console, "[chat]"),
|
|
13
|
+
error: console.error.bind(console, "[chat]")
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
17
|
+
maxRetries: 10,
|
|
18
|
+
intervals: [1e3, 2e3, 5e3],
|
|
19
|
+
backoffMultiplier: 1.5,
|
|
20
|
+
maxBackoffTime: 3e4
|
|
21
|
+
};
|
|
22
|
+
function calculateRetryInterval(retryCount, config = DEFAULT_RETRY_CONFIG) {
|
|
23
|
+
const {
|
|
24
|
+
intervals = DEFAULT_RETRY_CONFIG.intervals,
|
|
25
|
+
backoffMultiplier = DEFAULT_RETRY_CONFIG.backoffMultiplier,
|
|
26
|
+
maxBackoffTime = DEFAULT_RETRY_CONFIG.maxBackoffTime
|
|
27
|
+
} = config;
|
|
28
|
+
if (retryCount < intervals.length) {
|
|
29
|
+
return intervals[retryCount];
|
|
30
|
+
}
|
|
31
|
+
const baseInterval = intervals[intervals.length - 1] || 5e3;
|
|
32
|
+
const backoffTime = baseInterval * Math.pow(backoffMultiplier, retryCount - intervals.length + 1);
|
|
33
|
+
return Math.min(backoffTime, maxBackoffTime);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// transport/sse.ts
|
|
37
|
+
function createSSETransport(options = {}) {
|
|
38
|
+
const {
|
|
39
|
+
retryConfig = DEFAULT_RETRY_CONFIG,
|
|
40
|
+
debug = false,
|
|
41
|
+
logger = createLogger(debug)
|
|
42
|
+
} = options;
|
|
43
|
+
let eventSource;
|
|
44
|
+
let config;
|
|
45
|
+
let state = "disconnected";
|
|
46
|
+
let error;
|
|
47
|
+
let retryCount = 0;
|
|
48
|
+
let reconnectTimeout;
|
|
49
|
+
let intentionalDisconnect = false;
|
|
50
|
+
const metrics = {
|
|
51
|
+
latency: 0,
|
|
52
|
+
messagesSent: 0,
|
|
53
|
+
messagesReceived: 0,
|
|
54
|
+
messagesQueued: 0,
|
|
55
|
+
reconnectCount: 0,
|
|
56
|
+
transportType: "sse"
|
|
57
|
+
};
|
|
58
|
+
const globalHandlers = /* @__PURE__ */ new Set();
|
|
59
|
+
const typeHandlers = /* @__PURE__ */ new Map();
|
|
60
|
+
function emit(event) {
|
|
61
|
+
metrics.messagesReceived++;
|
|
62
|
+
metrics.lastMessageAt = Date.now();
|
|
63
|
+
globalHandlers.forEach((handler) => {
|
|
64
|
+
try {
|
|
65
|
+
handler(event);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
logger.error("Error in message handler:", err);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
const handlers = typeHandlers.get(event.type);
|
|
71
|
+
if (handlers) {
|
|
72
|
+
handlers.forEach((handler) => {
|
|
73
|
+
try {
|
|
74
|
+
handler(event);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
logger.error(`Error in ${event.type} handler:`, err);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function sendRest(endpoint, body) {
|
|
82
|
+
if (!config) {
|
|
83
|
+
throw new Error("Transport not connected");
|
|
84
|
+
}
|
|
85
|
+
const url = `${config.baseUrl}/${endpoint}`;
|
|
86
|
+
logger.debug(`POST ${endpoint}`, body);
|
|
87
|
+
const response = await fetch(url, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: { "Content-Type": "application/json" },
|
|
90
|
+
body: JSON.stringify(body)
|
|
91
|
+
});
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
const errorText = await response.text();
|
|
94
|
+
throw new Error(`API error: ${response.status} - ${errorText}`);
|
|
95
|
+
}
|
|
96
|
+
return response.json();
|
|
97
|
+
}
|
|
98
|
+
function handleMessage(event) {
|
|
99
|
+
if (!event.data || event.data === "") return;
|
|
100
|
+
try {
|
|
101
|
+
const data = JSON.parse(event.data);
|
|
102
|
+
logger.debug("Received:", data.type);
|
|
103
|
+
emit(data);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
logger.error("Failed to parse SSE message:", err);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function setupEventListeners(es) {
|
|
109
|
+
es.addEventListener("message", handleMessage);
|
|
110
|
+
const eventTypes = [
|
|
111
|
+
"reconnected",
|
|
112
|
+
"typing",
|
|
113
|
+
"stopped_typing",
|
|
114
|
+
"waiting",
|
|
115
|
+
"waiting_for_agent",
|
|
116
|
+
"human_agent_joined",
|
|
117
|
+
"human_agent_left",
|
|
118
|
+
"chat_ended",
|
|
119
|
+
"chat_updated",
|
|
120
|
+
"load_chat_response",
|
|
121
|
+
"new_chat_created",
|
|
122
|
+
"show_csat_survey",
|
|
123
|
+
"csat_response",
|
|
124
|
+
"user_suggested_actions",
|
|
125
|
+
"agent_execution_started",
|
|
126
|
+
"agent_execution_ended",
|
|
127
|
+
"agent_context_update",
|
|
128
|
+
"plan_pending_approval",
|
|
129
|
+
"step_started",
|
|
130
|
+
"step_completed",
|
|
131
|
+
"step_failed",
|
|
132
|
+
"plan_completed",
|
|
133
|
+
"skills_changed",
|
|
134
|
+
"summary_update"
|
|
135
|
+
];
|
|
136
|
+
eventTypes.forEach((type) => {
|
|
137
|
+
es.addEventListener(type, handleMessage);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function scheduleReconnect() {
|
|
141
|
+
if (intentionalDisconnect || !config) return;
|
|
142
|
+
const maxRetries = retryConfig.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries;
|
|
143
|
+
if (retryCount >= maxRetries) {
|
|
144
|
+
logger.error(`Max retries (${maxRetries}) exceeded`);
|
|
145
|
+
error = {
|
|
146
|
+
code: "CONNECTION_FAILED",
|
|
147
|
+
message: `Max retries (${maxRetries}) exceeded`,
|
|
148
|
+
retryable: false,
|
|
149
|
+
timestamp: Date.now()
|
|
150
|
+
};
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const interval = calculateRetryInterval(retryCount, retryConfig);
|
|
154
|
+
retryCount++;
|
|
155
|
+
metrics.reconnectCount++;
|
|
156
|
+
logger.info(`Reconnecting in ${interval}ms (attempt ${retryCount})`);
|
|
157
|
+
state = "reconnecting";
|
|
158
|
+
reconnectTimeout = setTimeout(() => {
|
|
159
|
+
if (config) {
|
|
160
|
+
transport.connect(config).catch((err) => {
|
|
161
|
+
logger.error("Reconnect failed:", err);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}, interval);
|
|
165
|
+
}
|
|
166
|
+
const transport = {
|
|
167
|
+
async connect(cfg) {
|
|
168
|
+
config = cfg;
|
|
169
|
+
intentionalDisconnect = false;
|
|
170
|
+
if (eventSource) {
|
|
171
|
+
eventSource.close();
|
|
172
|
+
eventSource = void 0;
|
|
173
|
+
}
|
|
174
|
+
if (reconnectTimeout) {
|
|
175
|
+
clearTimeout(reconnectTimeout);
|
|
176
|
+
reconnectTimeout = void 0;
|
|
177
|
+
}
|
|
178
|
+
state = retryCount > 0 ? "reconnecting" : "connecting";
|
|
179
|
+
return new Promise((resolve, reject) => {
|
|
180
|
+
const connectionStart = Date.now();
|
|
181
|
+
const url = `${cfg.baseUrl}/stream?orgId=${cfg.orgId}&userId=${cfg.userId}&clientType=${cfg.clientType}${cfg.chatKey ? `&chatId=${cfg.chatKey}` : ""}`;
|
|
182
|
+
logger.debug("Connecting to:", url);
|
|
183
|
+
const es = new EventSource(url);
|
|
184
|
+
es.onopen = () => {
|
|
185
|
+
const connectionTime = Date.now() - connectionStart;
|
|
186
|
+
logger.info(`Connected in ${connectionTime}ms`);
|
|
187
|
+
state = "connected";
|
|
188
|
+
error = void 0;
|
|
189
|
+
retryCount = 0;
|
|
190
|
+
metrics.connectedAt = Date.now();
|
|
191
|
+
metrics.latency = connectionTime;
|
|
192
|
+
setupEventListeners(es);
|
|
193
|
+
resolve();
|
|
194
|
+
};
|
|
195
|
+
es.onerror = () => {
|
|
196
|
+
if (es.readyState === EventSource.CLOSED) {
|
|
197
|
+
const sseError = {
|
|
198
|
+
code: "CONNECTION_FAILED",
|
|
199
|
+
message: "SSE connection failed",
|
|
200
|
+
retryable: true,
|
|
201
|
+
timestamp: Date.now()
|
|
202
|
+
};
|
|
203
|
+
error = sseError;
|
|
204
|
+
metrics.lastError = sseError;
|
|
205
|
+
state = "disconnected";
|
|
206
|
+
if (!intentionalDisconnect) {
|
|
207
|
+
scheduleReconnect();
|
|
208
|
+
}
|
|
209
|
+
if (retryCount === 0) {
|
|
210
|
+
reject(sseError);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
eventSource = es;
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
disconnect(intentional = true) {
|
|
218
|
+
logger.info("Disconnecting", { intentional });
|
|
219
|
+
intentionalDisconnect = intentional;
|
|
220
|
+
if (reconnectTimeout) {
|
|
221
|
+
clearTimeout(reconnectTimeout);
|
|
222
|
+
reconnectTimeout = void 0;
|
|
223
|
+
}
|
|
224
|
+
if (eventSource) {
|
|
225
|
+
eventSource.close();
|
|
226
|
+
eventSource = void 0;
|
|
227
|
+
}
|
|
228
|
+
state = "disconnected";
|
|
229
|
+
retryCount = 0;
|
|
230
|
+
},
|
|
231
|
+
async send(event) {
|
|
232
|
+
if (!config) {
|
|
233
|
+
throw new Error("Transport not connected");
|
|
234
|
+
}
|
|
235
|
+
switch (event.type) {
|
|
236
|
+
case "message":
|
|
237
|
+
await sendRest("send", {
|
|
238
|
+
orgId: event.orgId,
|
|
239
|
+
chatKey: event.chatKey,
|
|
240
|
+
userId: event.userId,
|
|
241
|
+
message: event.message
|
|
242
|
+
});
|
|
243
|
+
break;
|
|
244
|
+
case "typing":
|
|
245
|
+
await sendRest("typing", {
|
|
246
|
+
orgId: event.orgId,
|
|
247
|
+
chatKey: event.chatKey,
|
|
248
|
+
userId: event.userId,
|
|
249
|
+
typing: true
|
|
250
|
+
});
|
|
251
|
+
break;
|
|
252
|
+
case "stopped_typing":
|
|
253
|
+
await sendRest("typing", {
|
|
254
|
+
orgId: event.orgId,
|
|
255
|
+
chatKey: event.chatKey,
|
|
256
|
+
userId: event.userId,
|
|
257
|
+
typing: false
|
|
258
|
+
});
|
|
259
|
+
break;
|
|
260
|
+
case "load_chat":
|
|
261
|
+
await sendRest("load", {
|
|
262
|
+
orgId: event.orgId,
|
|
263
|
+
chatKey: event.chatKey,
|
|
264
|
+
userId: event.userId
|
|
265
|
+
});
|
|
266
|
+
break;
|
|
267
|
+
case "new_chat":
|
|
268
|
+
await sendRest("create", {
|
|
269
|
+
orgId: event.orgId,
|
|
270
|
+
userId: event.userId,
|
|
271
|
+
metadata: event.data
|
|
272
|
+
});
|
|
273
|
+
break;
|
|
274
|
+
case "end_chat":
|
|
275
|
+
await sendRest("end", {
|
|
276
|
+
orgId: event.orgId,
|
|
277
|
+
chatKey: event.chatKey,
|
|
278
|
+
userId: event.userId,
|
|
279
|
+
data: event.data
|
|
280
|
+
});
|
|
281
|
+
break;
|
|
282
|
+
default:
|
|
283
|
+
await sendRest("event", {
|
|
284
|
+
type: event.type,
|
|
285
|
+
orgId: event.orgId,
|
|
286
|
+
chatKey: event.chatKey,
|
|
287
|
+
userId: event.userId,
|
|
288
|
+
data: event.data
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
metrics.messagesSent++;
|
|
292
|
+
},
|
|
293
|
+
async sendMessage(message) {
|
|
294
|
+
if (!config) {
|
|
295
|
+
throw new Error("Transport not connected");
|
|
296
|
+
}
|
|
297
|
+
await sendRest("send", {
|
|
298
|
+
orgId: config.orgId,
|
|
299
|
+
chatKey: config.chatKey,
|
|
300
|
+
userId: config.userId,
|
|
301
|
+
message
|
|
302
|
+
});
|
|
303
|
+
metrics.messagesSent++;
|
|
304
|
+
},
|
|
305
|
+
onMessage(handler) {
|
|
306
|
+
globalHandlers.add(handler);
|
|
307
|
+
return () => globalHandlers.delete(handler);
|
|
308
|
+
},
|
|
309
|
+
on(eventType, handler) {
|
|
310
|
+
if (!typeHandlers.has(eventType)) {
|
|
311
|
+
typeHandlers.set(eventType, /* @__PURE__ */ new Set());
|
|
312
|
+
}
|
|
313
|
+
typeHandlers.get(eventType).add(handler);
|
|
314
|
+
return () => {
|
|
315
|
+
const handlers = typeHandlers.get(eventType);
|
|
316
|
+
if (handlers) {
|
|
317
|
+
handlers.delete(handler);
|
|
318
|
+
if (handlers.size === 0) {
|
|
319
|
+
typeHandlers.delete(eventType);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
},
|
|
324
|
+
getState() {
|
|
325
|
+
return state;
|
|
326
|
+
},
|
|
327
|
+
getMetrics() {
|
|
328
|
+
return { ...metrics };
|
|
329
|
+
},
|
|
330
|
+
getError() {
|
|
331
|
+
return error;
|
|
332
|
+
},
|
|
333
|
+
clearError() {
|
|
334
|
+
error = void 0;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
return transport;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// hooks/use-chat.ts
|
|
341
|
+
function getDefaultTransport(type, debug) {
|
|
342
|
+
return createSSETransport({ debug });
|
|
343
|
+
}
|
|
344
|
+
function useChat(options) {
|
|
345
|
+
const {
|
|
346
|
+
baseUrl,
|
|
347
|
+
orgId,
|
|
348
|
+
userId,
|
|
349
|
+
clientType = "customer",
|
|
350
|
+
transport: transportOption,
|
|
351
|
+
onMessage,
|
|
352
|
+
onError,
|
|
353
|
+
onConnectionChange,
|
|
354
|
+
autoConnect = false,
|
|
355
|
+
retryConfig,
|
|
356
|
+
debug = false
|
|
357
|
+
} = options;
|
|
358
|
+
const [connectionState, setConnectionState] = useState("disconnected");
|
|
359
|
+
const [currentChat, setCurrentChat] = useState(null);
|
|
360
|
+
const [chatKey, setChatKey] = useState(null);
|
|
361
|
+
const [messages, setMessages] = useState([]);
|
|
362
|
+
const [error, setError] = useState(null);
|
|
363
|
+
const [metrics, setMetrics] = useState({
|
|
364
|
+
latency: 0,
|
|
365
|
+
messagesSent: 0,
|
|
366
|
+
messagesReceived: 0,
|
|
367
|
+
messagesQueued: 0,
|
|
368
|
+
reconnectCount: 0
|
|
369
|
+
});
|
|
370
|
+
const transportRef = useRef(null);
|
|
371
|
+
const mountedRef = useRef(false);
|
|
372
|
+
const onMessageRef = useRef(onMessage);
|
|
373
|
+
const onErrorRef = useRef(onError);
|
|
374
|
+
const chatCreationResolverRef = useRef(null);
|
|
375
|
+
const typingTimeoutRef = useRef(null);
|
|
376
|
+
useEffect(() => {
|
|
377
|
+
onMessageRef.current = onMessage;
|
|
378
|
+
onErrorRef.current = onError;
|
|
379
|
+
}, [onMessage, onError]);
|
|
380
|
+
useEffect(() => {
|
|
381
|
+
if (typeof transportOption === "object") {
|
|
382
|
+
transportRef.current = transportOption;
|
|
383
|
+
} else {
|
|
384
|
+
transportRef.current = getDefaultTransport(
|
|
385
|
+
transportOption,
|
|
386
|
+
debug
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
}, [transportOption, debug]);
|
|
390
|
+
const handleEvent = useCallback((event) => {
|
|
391
|
+
if (!mountedRef.current) return;
|
|
392
|
+
setMetrics((prev) => ({
|
|
393
|
+
...prev,
|
|
394
|
+
messagesReceived: prev.messagesReceived + 1,
|
|
395
|
+
lastMessageAt: Date.now()
|
|
396
|
+
}));
|
|
397
|
+
switch (event.type) {
|
|
398
|
+
case "new_chat_created":
|
|
399
|
+
const newChatKey = event.data?.chatKey;
|
|
400
|
+
if (newChatKey) {
|
|
401
|
+
setChatKey(newChatKey);
|
|
402
|
+
if (chatCreationResolverRef.current) {
|
|
403
|
+
chatCreationResolverRef.current.resolve(newChatKey);
|
|
404
|
+
chatCreationResolverRef.current = null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
break;
|
|
408
|
+
case "load_chat_response":
|
|
409
|
+
const chat = event.data?.chat;
|
|
410
|
+
if (chat) {
|
|
411
|
+
setCurrentChat(chat);
|
|
412
|
+
setChatKey(chat.key);
|
|
413
|
+
setMessages(chat.messages || []);
|
|
414
|
+
}
|
|
415
|
+
break;
|
|
416
|
+
case "message":
|
|
417
|
+
if (event.message) {
|
|
418
|
+
setMessages((prev) => [...prev, event.message]);
|
|
419
|
+
}
|
|
420
|
+
break;
|
|
421
|
+
case "chat_ended":
|
|
422
|
+
setCurrentChat(null);
|
|
423
|
+
setChatKey(null);
|
|
424
|
+
break;
|
|
425
|
+
case "error":
|
|
426
|
+
const errorMsg = event.data?.message;
|
|
427
|
+
if (errorMsg) {
|
|
428
|
+
const transportError = {
|
|
429
|
+
code: "NETWORK_ERROR",
|
|
430
|
+
message: errorMsg,
|
|
431
|
+
retryable: true,
|
|
432
|
+
timestamp: Date.now()
|
|
433
|
+
};
|
|
434
|
+
setError(transportError);
|
|
435
|
+
onErrorRef.current?.(transportError);
|
|
436
|
+
}
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
onMessageRef.current?.(event);
|
|
440
|
+
}, []);
|
|
441
|
+
const connect = useCallback(async () => {
|
|
442
|
+
const transport = transportRef.current;
|
|
443
|
+
if (!transport) {
|
|
444
|
+
throw new Error("Transport not initialized");
|
|
445
|
+
}
|
|
446
|
+
if (!orgId) {
|
|
447
|
+
const err = {
|
|
448
|
+
code: "CONNECTION_FAILED",
|
|
449
|
+
message: "orgId is required",
|
|
450
|
+
retryable: false,
|
|
451
|
+
timestamp: Date.now()
|
|
452
|
+
};
|
|
453
|
+
setError(err);
|
|
454
|
+
throw err;
|
|
455
|
+
}
|
|
456
|
+
setConnectionState("connecting");
|
|
457
|
+
try {
|
|
458
|
+
const unsubscribe = transport.onMessage(handleEvent);
|
|
459
|
+
await transport.connect({
|
|
460
|
+
baseUrl,
|
|
461
|
+
orgId,
|
|
462
|
+
userId,
|
|
463
|
+
clientType,
|
|
464
|
+
chatKey: chatKey || void 0,
|
|
465
|
+
debug
|
|
466
|
+
});
|
|
467
|
+
setConnectionState("connected");
|
|
468
|
+
setError(null);
|
|
469
|
+
setMetrics(transport.getMetrics());
|
|
470
|
+
onConnectionChange?.("connected");
|
|
471
|
+
return;
|
|
472
|
+
} catch (err) {
|
|
473
|
+
const transportError = err;
|
|
474
|
+
setConnectionState("disconnected");
|
|
475
|
+
setError(transportError);
|
|
476
|
+
onErrorRef.current?.(transportError);
|
|
477
|
+
throw err;
|
|
478
|
+
}
|
|
479
|
+
}, [baseUrl, orgId, userId, clientType, chatKey, debug, handleEvent, onConnectionChange]);
|
|
480
|
+
const disconnect = useCallback(() => {
|
|
481
|
+
const transport = transportRef.current;
|
|
482
|
+
if (transport) {
|
|
483
|
+
transport.disconnect(true);
|
|
484
|
+
}
|
|
485
|
+
setConnectionState("disconnected");
|
|
486
|
+
onConnectionChange?.("disconnected");
|
|
487
|
+
}, [onConnectionChange]);
|
|
488
|
+
const startChat = useCallback(
|
|
489
|
+
async (metadata) => {
|
|
490
|
+
const transport = transportRef.current;
|
|
491
|
+
if (!transport) {
|
|
492
|
+
throw new Error("Transport not initialized");
|
|
493
|
+
}
|
|
494
|
+
return new Promise((resolve, reject) => {
|
|
495
|
+
chatCreationResolverRef.current = { resolve, reject };
|
|
496
|
+
transport.send({
|
|
497
|
+
type: "new_chat",
|
|
498
|
+
orgId,
|
|
499
|
+
chatKey: "",
|
|
500
|
+
userId,
|
|
501
|
+
timestamp: Date.now(),
|
|
502
|
+
data: metadata
|
|
503
|
+
}).catch((err) => {
|
|
504
|
+
chatCreationResolverRef.current = null;
|
|
505
|
+
reject(err);
|
|
506
|
+
});
|
|
507
|
+
setTimeout(() => {
|
|
508
|
+
if (chatCreationResolverRef.current) {
|
|
509
|
+
chatCreationResolverRef.current = null;
|
|
510
|
+
reject(new Error("Chat creation timed out"));
|
|
511
|
+
}
|
|
512
|
+
}, 3e4);
|
|
513
|
+
});
|
|
514
|
+
},
|
|
515
|
+
[orgId, userId]
|
|
516
|
+
);
|
|
517
|
+
const loadChat = useCallback(
|
|
518
|
+
async (key) => {
|
|
519
|
+
const transport = transportRef.current;
|
|
520
|
+
if (!transport) {
|
|
521
|
+
throw new Error("Transport not initialized");
|
|
522
|
+
}
|
|
523
|
+
setChatKey(key);
|
|
524
|
+
await transport.send({
|
|
525
|
+
type: "load_chat",
|
|
526
|
+
orgId,
|
|
527
|
+
chatKey: key,
|
|
528
|
+
userId,
|
|
529
|
+
timestamp: Date.now()
|
|
530
|
+
});
|
|
531
|
+
},
|
|
532
|
+
[orgId, userId]
|
|
533
|
+
);
|
|
534
|
+
const sendMessage = useCallback(
|
|
535
|
+
async (content, attachments) => {
|
|
536
|
+
const transport = transportRef.current;
|
|
537
|
+
if (!transport) {
|
|
538
|
+
throw new Error("Transport not initialized");
|
|
539
|
+
}
|
|
540
|
+
if (!chatKey) {
|
|
541
|
+
throw new Error("No active chat");
|
|
542
|
+
}
|
|
543
|
+
const message = {
|
|
544
|
+
id: `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
545
|
+
role: "user",
|
|
546
|
+
content,
|
|
547
|
+
time: Date.now(),
|
|
548
|
+
status: "sending",
|
|
549
|
+
senderId: userId,
|
|
550
|
+
createdAt: Date.now(),
|
|
551
|
+
attachments
|
|
552
|
+
};
|
|
553
|
+
setMessages((prev) => [...prev, message]);
|
|
554
|
+
await transport.send({
|
|
555
|
+
type: "message",
|
|
556
|
+
orgId,
|
|
557
|
+
chatKey,
|
|
558
|
+
userId,
|
|
559
|
+
timestamp: Date.now(),
|
|
560
|
+
message
|
|
561
|
+
});
|
|
562
|
+
setMetrics((prev) => ({
|
|
563
|
+
...prev,
|
|
564
|
+
messagesSent: prev.messagesSent + 1
|
|
565
|
+
}));
|
|
566
|
+
},
|
|
567
|
+
[orgId, chatKey, userId]
|
|
568
|
+
);
|
|
569
|
+
const endChat = useCallback(
|
|
570
|
+
async (reason) => {
|
|
571
|
+
const transport = transportRef.current;
|
|
572
|
+
if (!transport) {
|
|
573
|
+
throw new Error("Transport not initialized");
|
|
574
|
+
}
|
|
575
|
+
if (!chatKey) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
await transport.send({
|
|
579
|
+
type: "end_chat",
|
|
580
|
+
orgId,
|
|
581
|
+
chatKey,
|
|
582
|
+
userId,
|
|
583
|
+
timestamp: Date.now(),
|
|
584
|
+
data: reason ? { reason } : void 0
|
|
585
|
+
});
|
|
586
|
+
setCurrentChat(null);
|
|
587
|
+
setChatKey(null);
|
|
588
|
+
},
|
|
589
|
+
[orgId, chatKey, userId]
|
|
590
|
+
);
|
|
591
|
+
const startTyping = useCallback(() => {
|
|
592
|
+
const transport = transportRef.current;
|
|
593
|
+
if (!transport || !chatKey) return;
|
|
594
|
+
if (typingTimeoutRef.current) {
|
|
595
|
+
clearTimeout(typingTimeoutRef.current);
|
|
596
|
+
}
|
|
597
|
+
transport.send({
|
|
598
|
+
type: "typing",
|
|
599
|
+
orgId,
|
|
600
|
+
chatKey,
|
|
601
|
+
userId,
|
|
602
|
+
timestamp: Date.now()
|
|
603
|
+
}).catch(() => {
|
|
604
|
+
});
|
|
605
|
+
typingTimeoutRef.current = setTimeout(() => {
|
|
606
|
+
stopTyping();
|
|
607
|
+
}, 3e3);
|
|
608
|
+
}, [orgId, chatKey, userId]);
|
|
609
|
+
const stopTyping = useCallback(() => {
|
|
610
|
+
const transport = transportRef.current;
|
|
611
|
+
if (!transport || !chatKey) return;
|
|
612
|
+
if (typingTimeoutRef.current) {
|
|
613
|
+
clearTimeout(typingTimeoutRef.current);
|
|
614
|
+
typingTimeoutRef.current = null;
|
|
615
|
+
}
|
|
616
|
+
transport.send({
|
|
617
|
+
type: "stopped_typing",
|
|
618
|
+
orgId,
|
|
619
|
+
chatKey,
|
|
620
|
+
userId,
|
|
621
|
+
timestamp: Date.now()
|
|
622
|
+
}).catch(() => {
|
|
623
|
+
});
|
|
624
|
+
}, [orgId, chatKey, userId]);
|
|
625
|
+
const on = useCallback(
|
|
626
|
+
(eventType, handler) => {
|
|
627
|
+
const transport = transportRef.current;
|
|
628
|
+
if (!transport) {
|
|
629
|
+
return () => {
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
return transport.on(eventType, handler);
|
|
633
|
+
},
|
|
634
|
+
[]
|
|
635
|
+
);
|
|
636
|
+
const clearError = useCallback(() => {
|
|
637
|
+
setError(null);
|
|
638
|
+
transportRef.current?.clearError();
|
|
639
|
+
}, []);
|
|
640
|
+
useEffect(() => {
|
|
641
|
+
mountedRef.current = true;
|
|
642
|
+
if (autoConnect) {
|
|
643
|
+
connect().catch(() => {
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
return () => {
|
|
647
|
+
mountedRef.current = false;
|
|
648
|
+
if (typingTimeoutRef.current) {
|
|
649
|
+
clearTimeout(typingTimeoutRef.current);
|
|
650
|
+
}
|
|
651
|
+
disconnect();
|
|
652
|
+
};
|
|
653
|
+
}, []);
|
|
654
|
+
const isConnected = connectionState === "connected";
|
|
655
|
+
return {
|
|
656
|
+
// Connection
|
|
657
|
+
connect,
|
|
658
|
+
disconnect,
|
|
659
|
+
connectionState,
|
|
660
|
+
isConnected,
|
|
661
|
+
// Chat operations
|
|
662
|
+
startChat,
|
|
663
|
+
loadChat,
|
|
664
|
+
sendMessage,
|
|
665
|
+
endChat,
|
|
666
|
+
// Typing
|
|
667
|
+
startTyping,
|
|
668
|
+
stopTyping,
|
|
669
|
+
// State
|
|
670
|
+
currentChat,
|
|
671
|
+
chatKey,
|
|
672
|
+
messages,
|
|
673
|
+
error,
|
|
674
|
+
metrics,
|
|
675
|
+
// Events
|
|
676
|
+
on,
|
|
677
|
+
clearError
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
export {
|
|
681
|
+
useChat
|
|
682
|
+
};
|
|
683
|
+
//# sourceMappingURL=index.mjs.map
|