@surf-kit/agent 0.1.1
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 +12 -0
- package/README.md +60 -0
- package/dist/hooks-B8CSeOsn.d.cts +232 -0
- package/dist/hooks-B8CSeOsn.d.ts +232 -0
- package/dist/hooks.cjs +652 -0
- package/dist/hooks.cjs.map +1 -0
- package/dist/hooks.d.cts +2 -0
- package/dist/hooks.d.ts +2 -0
- package/dist/hooks.js +620 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.cjs +2358 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +321 -0
- package/dist/index.d.ts +321 -0
- package/dist/index.js +2284 -0
- package/dist/index.js.map +1 -0
- package/package.json +86 -0
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
// src/hooks/useAgentChat.ts
|
|
2
|
+
import { useReducer, useCallback, useRef } from "react";
|
|
3
|
+
var initialState = {
|
|
4
|
+
messages: [],
|
|
5
|
+
conversationId: null,
|
|
6
|
+
isLoading: false,
|
|
7
|
+
error: null,
|
|
8
|
+
inputValue: "",
|
|
9
|
+
streamPhase: "idle",
|
|
10
|
+
streamingContent: ""
|
|
11
|
+
};
|
|
12
|
+
function reducer(state, action) {
|
|
13
|
+
switch (action.type) {
|
|
14
|
+
case "SET_INPUT":
|
|
15
|
+
return { ...state, inputValue: action.value };
|
|
16
|
+
case "SEND_START":
|
|
17
|
+
return {
|
|
18
|
+
...state,
|
|
19
|
+
messages: [...state.messages, action.message],
|
|
20
|
+
isLoading: true,
|
|
21
|
+
error: null,
|
|
22
|
+
inputValue: "",
|
|
23
|
+
streamPhase: "thinking",
|
|
24
|
+
streamingContent: ""
|
|
25
|
+
};
|
|
26
|
+
case "STREAM_PHASE":
|
|
27
|
+
return { ...state, streamPhase: action.phase };
|
|
28
|
+
case "STREAM_CONTENT":
|
|
29
|
+
return { ...state, streamingContent: state.streamingContent + action.content };
|
|
30
|
+
case "SEND_SUCCESS":
|
|
31
|
+
return {
|
|
32
|
+
...state,
|
|
33
|
+
messages: [...state.messages, action.message],
|
|
34
|
+
conversationId: action.conversationId ?? state.conversationId,
|
|
35
|
+
isLoading: false,
|
|
36
|
+
streamPhase: "idle",
|
|
37
|
+
streamingContent: ""
|
|
38
|
+
};
|
|
39
|
+
case "SEND_ERROR":
|
|
40
|
+
return {
|
|
41
|
+
...state,
|
|
42
|
+
isLoading: false,
|
|
43
|
+
error: action.error,
|
|
44
|
+
streamPhase: "idle",
|
|
45
|
+
streamingContent: ""
|
|
46
|
+
};
|
|
47
|
+
case "LOAD_CONVERSATION":
|
|
48
|
+
return {
|
|
49
|
+
...state,
|
|
50
|
+
conversationId: action.conversationId,
|
|
51
|
+
messages: action.messages,
|
|
52
|
+
error: null
|
|
53
|
+
};
|
|
54
|
+
case "RESET":
|
|
55
|
+
return { ...initialState };
|
|
56
|
+
case "CLEAR_ERROR":
|
|
57
|
+
return { ...state, error: null };
|
|
58
|
+
default:
|
|
59
|
+
return state;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
var msgIdCounter = 0;
|
|
63
|
+
function generateMessageId() {
|
|
64
|
+
return `msg-${Date.now()}-${++msgIdCounter}`;
|
|
65
|
+
}
|
|
66
|
+
function useAgentChat(config) {
|
|
67
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
68
|
+
const configRef = useRef(config);
|
|
69
|
+
configRef.current = config;
|
|
70
|
+
const lastUserMessageRef = useRef(null);
|
|
71
|
+
const sendMessage = useCallback(
|
|
72
|
+
async (content) => {
|
|
73
|
+
const { apiUrl, streamPath = "/chat/stream", headers = {}, timeout = 3e4 } = configRef.current;
|
|
74
|
+
lastUserMessageRef.current = content;
|
|
75
|
+
const userMessage = {
|
|
76
|
+
id: generateMessageId(),
|
|
77
|
+
role: "user",
|
|
78
|
+
content,
|
|
79
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
80
|
+
};
|
|
81
|
+
dispatch({ type: "SEND_START", message: userMessage });
|
|
82
|
+
const controller = new AbortController();
|
|
83
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
84
|
+
try {
|
|
85
|
+
const response = await fetch(`${apiUrl}${streamPath}`, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: {
|
|
88
|
+
"Content-Type": "application/json",
|
|
89
|
+
Accept: "text/event-stream",
|
|
90
|
+
...headers
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify({
|
|
93
|
+
message: content,
|
|
94
|
+
conversation_id: state.conversationId
|
|
95
|
+
}),
|
|
96
|
+
signal: controller.signal
|
|
97
|
+
});
|
|
98
|
+
clearTimeout(timeoutId);
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
dispatch({
|
|
101
|
+
type: "SEND_ERROR",
|
|
102
|
+
error: {
|
|
103
|
+
code: "API_ERROR",
|
|
104
|
+
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
105
|
+
retryable: response.status >= 500
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const reader = response.body?.getReader();
|
|
111
|
+
if (!reader) {
|
|
112
|
+
dispatch({
|
|
113
|
+
type: "SEND_ERROR",
|
|
114
|
+
error: { code: "STREAM_ERROR", message: "No response body", retryable: true }
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const decoder = new TextDecoder();
|
|
119
|
+
let buffer = "";
|
|
120
|
+
let accumulatedContent = "";
|
|
121
|
+
let agentResponse = null;
|
|
122
|
+
let capturedAgent = null;
|
|
123
|
+
let capturedConversationId = null;
|
|
124
|
+
while (true) {
|
|
125
|
+
const { done, value } = await reader.read();
|
|
126
|
+
if (done) break;
|
|
127
|
+
buffer += decoder.decode(value, { stream: true });
|
|
128
|
+
const lines = buffer.split("\n");
|
|
129
|
+
buffer = lines.pop() ?? "";
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
if (!line.startsWith("data: ")) continue;
|
|
132
|
+
const data = line.slice(6).trim();
|
|
133
|
+
if (data === "[DONE]") continue;
|
|
134
|
+
try {
|
|
135
|
+
const event = JSON.parse(data);
|
|
136
|
+
switch (event.type) {
|
|
137
|
+
case "agent":
|
|
138
|
+
capturedAgent = event.agent;
|
|
139
|
+
break;
|
|
140
|
+
case "phase":
|
|
141
|
+
dispatch({ type: "STREAM_PHASE", phase: event.phase });
|
|
142
|
+
break;
|
|
143
|
+
case "delta":
|
|
144
|
+
accumulatedContent += event.content;
|
|
145
|
+
dispatch({ type: "STREAM_CONTENT", content: event.content });
|
|
146
|
+
break;
|
|
147
|
+
case "done":
|
|
148
|
+
agentResponse = event.response;
|
|
149
|
+
capturedConversationId = event.conversation_id ?? null;
|
|
150
|
+
break;
|
|
151
|
+
case "error":
|
|
152
|
+
dispatch({ type: "SEND_ERROR", error: event.error });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const assistantMessage = {
|
|
160
|
+
id: generateMessageId(),
|
|
161
|
+
role: "assistant",
|
|
162
|
+
content: agentResponse?.message ?? accumulatedContent,
|
|
163
|
+
response: agentResponse ?? void 0,
|
|
164
|
+
agent: capturedAgent ?? void 0,
|
|
165
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
166
|
+
};
|
|
167
|
+
dispatch({
|
|
168
|
+
type: "SEND_SUCCESS",
|
|
169
|
+
message: assistantMessage,
|
|
170
|
+
streamingContent: accumulatedContent,
|
|
171
|
+
conversationId: capturedConversationId
|
|
172
|
+
});
|
|
173
|
+
} catch (err) {
|
|
174
|
+
clearTimeout(timeoutId);
|
|
175
|
+
if (err.name === "AbortError") {
|
|
176
|
+
dispatch({
|
|
177
|
+
type: "SEND_ERROR",
|
|
178
|
+
error: { code: "TIMEOUT", message: "Request timed out", retryable: true }
|
|
179
|
+
});
|
|
180
|
+
} else {
|
|
181
|
+
dispatch({
|
|
182
|
+
type: "SEND_ERROR",
|
|
183
|
+
error: {
|
|
184
|
+
code: "NETWORK_ERROR",
|
|
185
|
+
message: err.message ?? "Network error",
|
|
186
|
+
retryable: true
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
[state.conversationId]
|
|
193
|
+
);
|
|
194
|
+
const setInputValue = useCallback((value) => {
|
|
195
|
+
dispatch({ type: "SET_INPUT", value });
|
|
196
|
+
}, []);
|
|
197
|
+
const loadConversation = useCallback((conversationId, messages) => {
|
|
198
|
+
dispatch({ type: "LOAD_CONVERSATION", conversationId, messages });
|
|
199
|
+
}, []);
|
|
200
|
+
const submitFeedback = useCallback(
|
|
201
|
+
async (messageId, rating, comment) => {
|
|
202
|
+
const { apiUrl, feedbackPath = "/feedback", headers = {} } = configRef.current;
|
|
203
|
+
await fetch(`${apiUrl}${feedbackPath}`, {
|
|
204
|
+
method: "POST",
|
|
205
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
206
|
+
body: JSON.stringify({ messageId, rating, comment })
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
[]
|
|
210
|
+
);
|
|
211
|
+
const retry = useCallback(async () => {
|
|
212
|
+
if (lastUserMessageRef.current) {
|
|
213
|
+
await sendMessage(lastUserMessageRef.current);
|
|
214
|
+
}
|
|
215
|
+
}, [sendMessage]);
|
|
216
|
+
const reset = useCallback(() => {
|
|
217
|
+
dispatch({ type: "RESET" });
|
|
218
|
+
lastUserMessageRef.current = null;
|
|
219
|
+
}, []);
|
|
220
|
+
const actions = {
|
|
221
|
+
sendMessage,
|
|
222
|
+
setInputValue,
|
|
223
|
+
loadConversation,
|
|
224
|
+
submitFeedback,
|
|
225
|
+
retry,
|
|
226
|
+
reset
|
|
227
|
+
};
|
|
228
|
+
return { state, actions };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/hooks/useStreaming.ts
|
|
232
|
+
import { useState, useCallback as useCallback2, useRef as useRef2, useEffect } from "react";
|
|
233
|
+
var initialState2 = {
|
|
234
|
+
active: false,
|
|
235
|
+
phase: "idle",
|
|
236
|
+
content: "",
|
|
237
|
+
sources: [],
|
|
238
|
+
agent: null,
|
|
239
|
+
agentLabel: null
|
|
240
|
+
};
|
|
241
|
+
function useStreaming(options) {
|
|
242
|
+
const { url, headers, onDone, onError } = options;
|
|
243
|
+
const [state, setState] = useState(initialState2);
|
|
244
|
+
const abortRef = useRef2(null);
|
|
245
|
+
const optionsRef = useRef2(options);
|
|
246
|
+
optionsRef.current = options;
|
|
247
|
+
const stop = useCallback2(() => {
|
|
248
|
+
if (abortRef.current) {
|
|
249
|
+
abortRef.current.abort();
|
|
250
|
+
abortRef.current = null;
|
|
251
|
+
}
|
|
252
|
+
setState((prev) => ({ ...prev, active: false, phase: "idle" }));
|
|
253
|
+
}, []);
|
|
254
|
+
const start = useCallback2(
|
|
255
|
+
async (body) => {
|
|
256
|
+
setState({ ...initialState2, active: true, phase: "thinking" });
|
|
257
|
+
const controller = new AbortController();
|
|
258
|
+
abortRef.current = controller;
|
|
259
|
+
try {
|
|
260
|
+
const response = await fetch(url, {
|
|
261
|
+
method: "POST",
|
|
262
|
+
headers: {
|
|
263
|
+
"Content-Type": "application/json",
|
|
264
|
+
Accept: "text/event-stream",
|
|
265
|
+
...headers
|
|
266
|
+
},
|
|
267
|
+
body: JSON.stringify(body),
|
|
268
|
+
signal: controller.signal
|
|
269
|
+
});
|
|
270
|
+
if (!response.ok) {
|
|
271
|
+
const errorEvent = {
|
|
272
|
+
type: "error",
|
|
273
|
+
error: {
|
|
274
|
+
code: "API_ERROR",
|
|
275
|
+
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
276
|
+
retryable: response.status >= 500
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
setState((prev) => ({ ...prev, active: false, phase: "idle" }));
|
|
280
|
+
optionsRef.current.onError?.(errorEvent);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const reader = response.body?.getReader();
|
|
284
|
+
if (!reader) {
|
|
285
|
+
setState((prev) => ({ ...prev, active: false, phase: "idle" }));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const decoder = new TextDecoder();
|
|
289
|
+
let buffer = "";
|
|
290
|
+
while (true) {
|
|
291
|
+
const { done, value } = await reader.read();
|
|
292
|
+
if (done) break;
|
|
293
|
+
buffer += decoder.decode(value, { stream: true });
|
|
294
|
+
const lines = buffer.split("\n");
|
|
295
|
+
buffer = lines.pop() ?? "";
|
|
296
|
+
for (const line of lines) {
|
|
297
|
+
if (!line.startsWith("data: ")) continue;
|
|
298
|
+
const data = line.slice(6).trim();
|
|
299
|
+
if (data === "[DONE]") continue;
|
|
300
|
+
try {
|
|
301
|
+
const event = JSON.parse(data);
|
|
302
|
+
processEvent(event, setState, optionsRef);
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
} catch (err) {
|
|
308
|
+
if (err.name === "AbortError") return;
|
|
309
|
+
const errorEvent = {
|
|
310
|
+
type: "error",
|
|
311
|
+
error: {
|
|
312
|
+
code: "NETWORK_ERROR",
|
|
313
|
+
message: err.message ?? "Network error",
|
|
314
|
+
retryable: true
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
setState((prev) => ({ ...prev, active: false, phase: "idle" }));
|
|
318
|
+
optionsRef.current.onError?.(errorEvent);
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
[url, headers]
|
|
322
|
+
);
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
return () => {
|
|
325
|
+
if (abortRef.current) {
|
|
326
|
+
abortRef.current.abort();
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}, []);
|
|
330
|
+
return { state, start, stop };
|
|
331
|
+
}
|
|
332
|
+
function processEvent(event, setState, optionsRef) {
|
|
333
|
+
switch (event.type) {
|
|
334
|
+
case "phase":
|
|
335
|
+
setState((prev) => ({ ...prev, phase: event.phase }));
|
|
336
|
+
break;
|
|
337
|
+
case "delta":
|
|
338
|
+
setState((prev) => ({ ...prev, content: prev.content + event.content }));
|
|
339
|
+
break;
|
|
340
|
+
case "source":
|
|
341
|
+
setState((prev) => ({ ...prev, sources: [...prev.sources, event.source] }));
|
|
342
|
+
break;
|
|
343
|
+
case "agent":
|
|
344
|
+
setState((prev) => ({ ...prev, agent: event.agent }));
|
|
345
|
+
break;
|
|
346
|
+
case "done":
|
|
347
|
+
setState((prev) => ({ ...prev, active: false, phase: "idle" }));
|
|
348
|
+
optionsRef.current.onDone?.(event);
|
|
349
|
+
break;
|
|
350
|
+
case "error":
|
|
351
|
+
setState((prev) => ({ ...prev, active: false, phase: "idle" }));
|
|
352
|
+
optionsRef.current.onError?.(event);
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/hooks/useConversation.ts
|
|
358
|
+
import { useState as useState2, useCallback as useCallback3 } from "react";
|
|
359
|
+
var STORAGE_PREFIX = "surf-kit-conversations";
|
|
360
|
+
function getStorageKey(prefix) {
|
|
361
|
+
return `${prefix}:list`;
|
|
362
|
+
}
|
|
363
|
+
function loadFromStorage(storageKey) {
|
|
364
|
+
if (typeof window === "undefined") return [];
|
|
365
|
+
try {
|
|
366
|
+
const raw = window.localStorage.getItem(getStorageKey(storageKey));
|
|
367
|
+
if (!raw) return [];
|
|
368
|
+
const parsed = JSON.parse(raw);
|
|
369
|
+
return parsed.map((c) => ({
|
|
370
|
+
...c,
|
|
371
|
+
createdAt: new Date(c.createdAt),
|
|
372
|
+
updatedAt: new Date(c.updatedAt),
|
|
373
|
+
messages: c.messages.map((m) => ({ ...m, timestamp: new Date(m.timestamp) }))
|
|
374
|
+
}));
|
|
375
|
+
} catch {
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function saveToStorage(storageKey, conversations) {
|
|
380
|
+
if (typeof window === "undefined") return;
|
|
381
|
+
try {
|
|
382
|
+
window.localStorage.setItem(getStorageKey(storageKey), JSON.stringify(conversations));
|
|
383
|
+
} catch {
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
var idCounter = 0;
|
|
387
|
+
function generateId() {
|
|
388
|
+
return `conv-${Date.now()}-${++idCounter}`;
|
|
389
|
+
}
|
|
390
|
+
function useConversation(options = {}) {
|
|
391
|
+
const { persist = false, storageKey = STORAGE_PREFIX } = options;
|
|
392
|
+
const [conversations, setConversations] = useState2(
|
|
393
|
+
() => persist ? loadFromStorage(storageKey) : []
|
|
394
|
+
);
|
|
395
|
+
const [current, setCurrent] = useState2(null);
|
|
396
|
+
const persistIfNeeded = useCallback3(
|
|
397
|
+
(convs) => {
|
|
398
|
+
if (persist) saveToStorage(storageKey, convs);
|
|
399
|
+
},
|
|
400
|
+
[persist, storageKey]
|
|
401
|
+
);
|
|
402
|
+
const create = useCallback3(
|
|
403
|
+
(title) => {
|
|
404
|
+
const now = /* @__PURE__ */ new Date();
|
|
405
|
+
const conv = {
|
|
406
|
+
id: generateId(),
|
|
407
|
+
title: title ?? "New Conversation",
|
|
408
|
+
messages: [],
|
|
409
|
+
createdAt: now,
|
|
410
|
+
updatedAt: now
|
|
411
|
+
};
|
|
412
|
+
setConversations((prev) => {
|
|
413
|
+
const next = [conv, ...prev];
|
|
414
|
+
persistIfNeeded(next);
|
|
415
|
+
return next;
|
|
416
|
+
});
|
|
417
|
+
setCurrent(conv);
|
|
418
|
+
return conv;
|
|
419
|
+
},
|
|
420
|
+
[persistIfNeeded]
|
|
421
|
+
);
|
|
422
|
+
const list = useCallback3(() => {
|
|
423
|
+
return conversations.map((c) => ({
|
|
424
|
+
id: c.id,
|
|
425
|
+
title: c.title,
|
|
426
|
+
lastMessage: c.messages.length > 0 ? c.messages[c.messages.length - 1].content : "",
|
|
427
|
+
updatedAt: c.updatedAt,
|
|
428
|
+
messageCount: c.messages.length
|
|
429
|
+
}));
|
|
430
|
+
}, [conversations]);
|
|
431
|
+
const load = useCallback3(
|
|
432
|
+
(id) => {
|
|
433
|
+
const conv = conversations.find((c) => c.id === id) ?? null;
|
|
434
|
+
setCurrent(conv);
|
|
435
|
+
return conv;
|
|
436
|
+
},
|
|
437
|
+
[conversations]
|
|
438
|
+
);
|
|
439
|
+
const remove = useCallback3(
|
|
440
|
+
(id) => {
|
|
441
|
+
setConversations((prev) => {
|
|
442
|
+
const next = prev.filter((c) => c.id !== id);
|
|
443
|
+
persistIfNeeded(next);
|
|
444
|
+
return next;
|
|
445
|
+
});
|
|
446
|
+
setCurrent((prev) => prev?.id === id ? null : prev);
|
|
447
|
+
},
|
|
448
|
+
[persistIfNeeded]
|
|
449
|
+
);
|
|
450
|
+
const rename = useCallback3(
|
|
451
|
+
(id, title) => {
|
|
452
|
+
setConversations((prev) => {
|
|
453
|
+
const next = prev.map((c) => c.id === id ? { ...c, title, updatedAt: /* @__PURE__ */ new Date() } : c);
|
|
454
|
+
persistIfNeeded(next);
|
|
455
|
+
return next;
|
|
456
|
+
});
|
|
457
|
+
setCurrent((prev) => prev?.id === id ? { ...prev, title, updatedAt: /* @__PURE__ */ new Date() } : prev);
|
|
458
|
+
},
|
|
459
|
+
[persistIfNeeded]
|
|
460
|
+
);
|
|
461
|
+
const addMessage = useCallback3(
|
|
462
|
+
(conversationId, message) => {
|
|
463
|
+
setConversations((prev) => {
|
|
464
|
+
const next = prev.map(
|
|
465
|
+
(c) => c.id === conversationId ? { ...c, messages: [...c.messages, message], updatedAt: /* @__PURE__ */ new Date() } : c
|
|
466
|
+
);
|
|
467
|
+
persistIfNeeded(next);
|
|
468
|
+
return next;
|
|
469
|
+
});
|
|
470
|
+
setCurrent(
|
|
471
|
+
(prev) => prev?.id === conversationId ? { ...prev, messages: [...prev.messages, message], updatedAt: /* @__PURE__ */ new Date() } : prev
|
|
472
|
+
);
|
|
473
|
+
},
|
|
474
|
+
[persistIfNeeded]
|
|
475
|
+
);
|
|
476
|
+
return {
|
|
477
|
+
conversations,
|
|
478
|
+
current,
|
|
479
|
+
create,
|
|
480
|
+
list,
|
|
481
|
+
load,
|
|
482
|
+
delete: remove,
|
|
483
|
+
rename,
|
|
484
|
+
addMessage
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// src/hooks/useFeedback.ts
|
|
489
|
+
import { useState as useState3, useCallback as useCallback4, useRef as useRef3 } from "react";
|
|
490
|
+
function useFeedback(options) {
|
|
491
|
+
const { url, headers } = options;
|
|
492
|
+
const [state, setState] = useState3("idle");
|
|
493
|
+
const [error, setError] = useState3(null);
|
|
494
|
+
const abortRef = useRef3(null);
|
|
495
|
+
const submit = useCallback4(
|
|
496
|
+
async (messageId, rating, comment) => {
|
|
497
|
+
setState("submitting");
|
|
498
|
+
setError(null);
|
|
499
|
+
const controller = new AbortController();
|
|
500
|
+
abortRef.current = controller;
|
|
501
|
+
try {
|
|
502
|
+
const response = await fetch(url, {
|
|
503
|
+
method: "POST",
|
|
504
|
+
headers: {
|
|
505
|
+
"Content-Type": "application/json",
|
|
506
|
+
...headers
|
|
507
|
+
},
|
|
508
|
+
body: JSON.stringify({ messageId, rating, comment }),
|
|
509
|
+
signal: controller.signal
|
|
510
|
+
});
|
|
511
|
+
if (!response.ok) {
|
|
512
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
513
|
+
}
|
|
514
|
+
setState("submitted");
|
|
515
|
+
} catch (err) {
|
|
516
|
+
if (err.name === "AbortError") return;
|
|
517
|
+
setError(err.message ?? "Failed to submit feedback");
|
|
518
|
+
setState("error");
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
[url, headers]
|
|
522
|
+
);
|
|
523
|
+
const reset = useCallback4(() => {
|
|
524
|
+
setState("idle");
|
|
525
|
+
setError(null);
|
|
526
|
+
}, []);
|
|
527
|
+
return { state, error, submit, reset };
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// src/hooks/useAgentTheme.ts
|
|
531
|
+
import { useMemo } from "react";
|
|
532
|
+
var DEFAULT_ACCENT = "#6366f1";
|
|
533
|
+
var DEFAULT_LABEL = "Agent";
|
|
534
|
+
function useAgentTheme(agentId, agentThemes) {
|
|
535
|
+
return useMemo(() => {
|
|
536
|
+
if (!agentId) {
|
|
537
|
+
return { accent: DEFAULT_ACCENT, icon: null, label: DEFAULT_LABEL };
|
|
538
|
+
}
|
|
539
|
+
const theme = agentThemes?.[agentId];
|
|
540
|
+
if (!theme) {
|
|
541
|
+
return { accent: DEFAULT_ACCENT, icon: null, label: agentId };
|
|
542
|
+
}
|
|
543
|
+
return {
|
|
544
|
+
accent: theme.accent ?? DEFAULT_ACCENT,
|
|
545
|
+
icon: theme.icon ?? null,
|
|
546
|
+
label: theme.label
|
|
547
|
+
};
|
|
548
|
+
}, [agentId, agentThemes]);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// src/hooks/useCharacterDrain.ts
|
|
552
|
+
import { useState as useState4, useRef as useRef4, useEffect as useEffect2 } from "react";
|
|
553
|
+
function useCharacterDrain(target, msPerChar = 15) {
|
|
554
|
+
const [displayed, setDisplayed] = useState4("");
|
|
555
|
+
const indexRef = useRef4(0);
|
|
556
|
+
const lastTimeRef = useRef4(0);
|
|
557
|
+
const rafRef = useRef4(null);
|
|
558
|
+
const drainTargetRef = useRef4("");
|
|
559
|
+
const msPerCharRef = useRef4(msPerChar);
|
|
560
|
+
msPerCharRef.current = msPerChar;
|
|
561
|
+
if (target !== "") {
|
|
562
|
+
drainTargetRef.current = target;
|
|
563
|
+
}
|
|
564
|
+
const drainTarget = drainTargetRef.current;
|
|
565
|
+
const isDraining = displayed.length < drainTarget.length;
|
|
566
|
+
const tickRef = useRef4(() => {
|
|
567
|
+
});
|
|
568
|
+
tickRef.current = (now) => {
|
|
569
|
+
const currentTarget = drainTargetRef.current;
|
|
570
|
+
if (currentTarget === "") {
|
|
571
|
+
rafRef.current = null;
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (lastTimeRef.current === 0) lastTimeRef.current = now;
|
|
575
|
+
const elapsed = now - lastTimeRef.current;
|
|
576
|
+
const charsToAdvance = Math.floor(elapsed / msPerCharRef.current);
|
|
577
|
+
if (charsToAdvance > 0 && indexRef.current < currentTarget.length) {
|
|
578
|
+
const nextIndex = Math.min(indexRef.current + charsToAdvance, currentTarget.length);
|
|
579
|
+
indexRef.current = nextIndex;
|
|
580
|
+
lastTimeRef.current = now;
|
|
581
|
+
setDisplayed(currentTarget.slice(0, nextIndex));
|
|
582
|
+
}
|
|
583
|
+
if (indexRef.current < currentTarget.length) {
|
|
584
|
+
rafRef.current = requestAnimationFrame((t) => tickRef.current(t));
|
|
585
|
+
} else {
|
|
586
|
+
rafRef.current = null;
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
useEffect2(() => {
|
|
590
|
+
if (drainTargetRef.current !== "" && indexRef.current < drainTargetRef.current.length && rafRef.current === null) {
|
|
591
|
+
rafRef.current = requestAnimationFrame((t) => tickRef.current(t));
|
|
592
|
+
}
|
|
593
|
+
}, [drainTarget]);
|
|
594
|
+
useEffect2(() => {
|
|
595
|
+
if (target === "" && !isDraining && displayed !== "") {
|
|
596
|
+
indexRef.current = 0;
|
|
597
|
+
lastTimeRef.current = 0;
|
|
598
|
+
drainTargetRef.current = "";
|
|
599
|
+
setDisplayed("");
|
|
600
|
+
}
|
|
601
|
+
}, [target, isDraining, displayed]);
|
|
602
|
+
useEffect2(() => {
|
|
603
|
+
return () => {
|
|
604
|
+
if (rafRef.current !== null) {
|
|
605
|
+
cancelAnimationFrame(rafRef.current);
|
|
606
|
+
rafRef.current = null;
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
}, []);
|
|
610
|
+
return { displayed, isDraining };
|
|
611
|
+
}
|
|
612
|
+
export {
|
|
613
|
+
useAgentChat,
|
|
614
|
+
useAgentTheme,
|
|
615
|
+
useCharacterDrain,
|
|
616
|
+
useConversation,
|
|
617
|
+
useFeedback,
|
|
618
|
+
useStreaming
|
|
619
|
+
};
|
|
620
|
+
//# sourceMappingURL=hooks.js.map
|