@meetsmore-oss/use-ai-client 1.2.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/dist/bundled.js +34913 -0
- package/dist/bundled.js.map +1 -0
- package/dist/chunk-EGD4LT6R.js +46 -0
- package/dist/chunk-EGD4LT6R.js.map +1 -0
- package/dist/chunk-EGKUR4C7.js +13 -0
- package/dist/chunk-EGKUR4C7.js.map +1 -0
- package/dist/index.d.ts +1953 -0
- package/dist/index.js +4502 -0
- package/dist/index.js.map +1 -0
- package/dist/types-INERONQV.js +9 -0
- package/dist/types-INERONQV.js.map +1 -0
- package/dist/types-TVUXB3NB.js +9 -0
- package/dist/types-TVUXB3NB.js.map +1 -0
- package/package.json +58 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4502 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateChatId,
|
|
3
|
+
generateMessageId
|
|
4
|
+
} from "./chunk-EGKUR4C7.js";
|
|
5
|
+
|
|
6
|
+
// src/useAI.ts
|
|
7
|
+
import { useState as useState11, useEffect as useEffect10, useRef as useRef11, useCallback as useCallback10, useMemo as useMemo6 } from "react";
|
|
8
|
+
|
|
9
|
+
// src/providers/useAIProvider.tsx
|
|
10
|
+
import { createContext as createContext4, useContext as useContext4, useState as useState10, useEffect as useEffect9, useCallback as useCallback9, useRef as useRef9 } from "react";
|
|
11
|
+
|
|
12
|
+
// src/types.ts
|
|
13
|
+
import { EventType, ErrorCode } from "@meetsmore-oss/use-ai-core";
|
|
14
|
+
|
|
15
|
+
// src/theme/strings.ts
|
|
16
|
+
import { createContext, useContext } from "react";
|
|
17
|
+
var defaultStrings = {
|
|
18
|
+
// Chat panel header
|
|
19
|
+
header: {
|
|
20
|
+
/** Header title when no chat history feature */
|
|
21
|
+
aiAssistant: "AI Assistant",
|
|
22
|
+
/** Label for new chat button tooltip */
|
|
23
|
+
newChat: "New Chat",
|
|
24
|
+
/** Delete chat confirmation message */
|
|
25
|
+
deleteConfirm: "Delete this chat from history?",
|
|
26
|
+
/** Delete button tooltip */
|
|
27
|
+
deleteChat: "Delete Chat",
|
|
28
|
+
/** Connection status: online */
|
|
29
|
+
online: "Online",
|
|
30
|
+
/** Connection status: offline */
|
|
31
|
+
offline: "Offline"
|
|
32
|
+
},
|
|
33
|
+
// Chat history dropdown
|
|
34
|
+
chatHistory: {
|
|
35
|
+
/** Chat history: no chats message */
|
|
36
|
+
noChatHistory: "No chat history yet",
|
|
37
|
+
/** Chat history: active chat indicator */
|
|
38
|
+
active: "Active"
|
|
39
|
+
},
|
|
40
|
+
// Empty chat state
|
|
41
|
+
emptyChat: {
|
|
42
|
+
/** Empty chat welcome message */
|
|
43
|
+
startConversation: "Start a conversation with the AI assistant",
|
|
44
|
+
/** Empty chat help text */
|
|
45
|
+
askMeToHelp: "Ask me to help with your tasks!"
|
|
46
|
+
},
|
|
47
|
+
// Chat input
|
|
48
|
+
input: {
|
|
49
|
+
/** Input placeholder when connected */
|
|
50
|
+
placeholder: "Type a message...",
|
|
51
|
+
/** Input placeholder when connecting */
|
|
52
|
+
connectingPlaceholder: "Connecting...",
|
|
53
|
+
/** Send button text */
|
|
54
|
+
send: "Send",
|
|
55
|
+
/** Loading indicator text */
|
|
56
|
+
thinking: "Thinking"
|
|
57
|
+
},
|
|
58
|
+
// File upload
|
|
59
|
+
fileUpload: {
|
|
60
|
+
/** Attach files button tooltip */
|
|
61
|
+
attachFiles: "Attach Files",
|
|
62
|
+
/** Drop zone text when dragging files */
|
|
63
|
+
dropFilesHere: "Drop files here",
|
|
64
|
+
/** File size error (use {filename} and {maxSize} placeholders) */
|
|
65
|
+
fileSizeError: 'File "{filename}" exceeds {maxSize}MB limit',
|
|
66
|
+
/** File type error (use {type} placeholder) */
|
|
67
|
+
fileTypeError: 'File type "{type}" is not accepted'
|
|
68
|
+
},
|
|
69
|
+
// Floating button
|
|
70
|
+
floatingButton: {
|
|
71
|
+
/** Floating button title when connected */
|
|
72
|
+
openAssistant: "Open AI Assistant",
|
|
73
|
+
/** Floating button title when connecting */
|
|
74
|
+
connectingToAssistant: "Connecting to AI..."
|
|
75
|
+
},
|
|
76
|
+
// Slash commands
|
|
77
|
+
commands: {
|
|
78
|
+
/** No saved commands empty state */
|
|
79
|
+
noSavedCommands: "No saved commands yet",
|
|
80
|
+
/** No matching commands message */
|
|
81
|
+
noMatchingCommands: "No matching commands",
|
|
82
|
+
/** Delete command button tooltip */
|
|
83
|
+
deleteCommand: "Delete command",
|
|
84
|
+
/** Command name input placeholder */
|
|
85
|
+
commandNamePlaceholder: "command-name",
|
|
86
|
+
/** Save command button tooltip */
|
|
87
|
+
saveCommand: "Save command",
|
|
88
|
+
/** Error when command name already exists */
|
|
89
|
+
commandNameExists: "Command name already exists",
|
|
90
|
+
/** Error when rename is not supported */
|
|
91
|
+
renameNotSupported: "Rename not supported",
|
|
92
|
+
/** Error when save is not supported */
|
|
93
|
+
saveNotSupported: "Save not supported",
|
|
94
|
+
/** Error when rename fails */
|
|
95
|
+
renameFailed: "Failed to rename",
|
|
96
|
+
/** Error when save fails */
|
|
97
|
+
saveFailed: "Failed to save"
|
|
98
|
+
},
|
|
99
|
+
// Error messages (from server error codes)
|
|
100
|
+
errors: {
|
|
101
|
+
/** Error when AI service is overloaded */
|
|
102
|
+
API_OVERLOADED: "The AI service is currently experiencing high demand. Please try again in a moment.",
|
|
103
|
+
/** Error when rate limited */
|
|
104
|
+
RATE_LIMITED: "Too many requests. Please wait a moment before trying again.",
|
|
105
|
+
/** Error for unknown/unexpected errors */
|
|
106
|
+
UNKNOWN_ERROR: "An unexpected error occurred. Please try again."
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var StringsContext = createContext(defaultStrings);
|
|
110
|
+
function useStrings() {
|
|
111
|
+
return useContext(StringsContext);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/theme/theme.ts
|
|
115
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
116
|
+
var defaultTheme = {
|
|
117
|
+
// Primary colors
|
|
118
|
+
/** Primary color for buttons, links, active states */
|
|
119
|
+
primaryColor: "#667eea",
|
|
120
|
+
/** Primary gradient for user messages and buttons */
|
|
121
|
+
primaryGradient: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
|
122
|
+
/** Translucent primary color for overlays (e.g., drop zone) */
|
|
123
|
+
primaryColorTranslucent: "rgba(102, 126, 234, 0.15)",
|
|
124
|
+
// Backgrounds
|
|
125
|
+
/** Panel background color */
|
|
126
|
+
backgroundColor: "white",
|
|
127
|
+
/** Assistant message bubble background */
|
|
128
|
+
assistantMessageBackground: "#f3f4f6",
|
|
129
|
+
/** Hover background for buttons and items */
|
|
130
|
+
hoverBackground: "#f3f4f6",
|
|
131
|
+
/** Active/selected item background */
|
|
132
|
+
activeBackground: "#f0f0ff",
|
|
133
|
+
/** Disabled button background */
|
|
134
|
+
buttonDisabledBackground: "#e5e7eb",
|
|
135
|
+
// Text colors
|
|
136
|
+
/** Primary text color */
|
|
137
|
+
textColor: "#1f2937",
|
|
138
|
+
/** Secondary/muted text color */
|
|
139
|
+
secondaryTextColor: "#6b7280",
|
|
140
|
+
/** Placeholder text color */
|
|
141
|
+
placeholderTextColor: "#9ca3af",
|
|
142
|
+
// Status colors
|
|
143
|
+
/** Online status indicator color */
|
|
144
|
+
onlineColor: "#10b981",
|
|
145
|
+
/** Offline status indicator color */
|
|
146
|
+
offlineColor: "#6b7280",
|
|
147
|
+
/** Unread notification indicator color */
|
|
148
|
+
unreadIndicatorColor: "#ff4444",
|
|
149
|
+
// Error/danger colors
|
|
150
|
+
/** Error message background */
|
|
151
|
+
errorBackground: "#fee2e2",
|
|
152
|
+
/** Error message text color */
|
|
153
|
+
errorTextColor: "#dc2626",
|
|
154
|
+
/** Danger/destructive action color (e.g., delete) */
|
|
155
|
+
dangerColor: "#ef4444",
|
|
156
|
+
// Borders and dividers
|
|
157
|
+
/** Border color for dividers and inputs */
|
|
158
|
+
borderColor: "#e5e7eb",
|
|
159
|
+
/** Dashed border color (e.g., file placeholder) */
|
|
160
|
+
dashedBorderColor: "#d1d5db",
|
|
161
|
+
// Shadows
|
|
162
|
+
/** Panel box shadow */
|
|
163
|
+
panelShadow: "0 8px 32px rgba(0, 0, 0, 0.12)",
|
|
164
|
+
/** Dropdown box shadow */
|
|
165
|
+
dropdownShadow: "0 4px 16px rgba(0, 0, 0, 0.15)",
|
|
166
|
+
/** Button box shadow */
|
|
167
|
+
buttonShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
|
|
168
|
+
/** Button hover box shadow */
|
|
169
|
+
buttonHoverShadow: "0 6px 16px rgba(0, 0, 0, 0.2)",
|
|
170
|
+
// Typography
|
|
171
|
+
/** Font family */
|
|
172
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
173
|
+
// Backdrop
|
|
174
|
+
/** Modal backdrop color */
|
|
175
|
+
backdropColor: "rgba(0, 0, 0, 0.3)"
|
|
176
|
+
};
|
|
177
|
+
var ThemeContext = createContext2(defaultTheme);
|
|
178
|
+
function useTheme() {
|
|
179
|
+
return useContext2(ThemeContext);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/components/UseAIFloatingButton.tsx
|
|
183
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
184
|
+
function UseAIFloatingButton({
|
|
185
|
+
onClick,
|
|
186
|
+
connected,
|
|
187
|
+
hasUnread = false
|
|
188
|
+
}) {
|
|
189
|
+
const strings = useStrings();
|
|
190
|
+
const theme = useTheme();
|
|
191
|
+
return /* @__PURE__ */ jsxs(
|
|
192
|
+
"button",
|
|
193
|
+
{
|
|
194
|
+
"data-testid": "ai-button",
|
|
195
|
+
className: "ai-floating-button",
|
|
196
|
+
onClick,
|
|
197
|
+
style: {
|
|
198
|
+
position: "fixed",
|
|
199
|
+
bottom: "24px",
|
|
200
|
+
right: "24px",
|
|
201
|
+
width: "56px",
|
|
202
|
+
height: "56px",
|
|
203
|
+
borderRadius: "50%",
|
|
204
|
+
border: "none",
|
|
205
|
+
background: connected ? theme.primaryGradient : theme.offlineColor,
|
|
206
|
+
color: "white",
|
|
207
|
+
fontSize: "20px",
|
|
208
|
+
fontWeight: "bold",
|
|
209
|
+
cursor: connected ? "pointer" : "not-allowed",
|
|
210
|
+
boxShadow: theme.buttonShadow,
|
|
211
|
+
display: "flex",
|
|
212
|
+
alignItems: "center",
|
|
213
|
+
justifyContent: "center",
|
|
214
|
+
transition: "transform 0.2s, box-shadow 0.2s",
|
|
215
|
+
zIndex: 1e3,
|
|
216
|
+
fontFamily: theme.fontFamily
|
|
217
|
+
},
|
|
218
|
+
onMouseEnter: (e) => {
|
|
219
|
+
if (connected) {
|
|
220
|
+
e.currentTarget.style.transform = "scale(1.1)";
|
|
221
|
+
e.currentTarget.style.boxShadow = theme.buttonHoverShadow;
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
onMouseLeave: (e) => {
|
|
225
|
+
e.currentTarget.style.transform = "scale(1)";
|
|
226
|
+
e.currentTarget.style.boxShadow = theme.buttonShadow;
|
|
227
|
+
},
|
|
228
|
+
disabled: !connected,
|
|
229
|
+
title: connected ? strings.floatingButton.openAssistant : strings.floatingButton.connectingToAssistant,
|
|
230
|
+
children: [
|
|
231
|
+
"AI",
|
|
232
|
+
hasUnread && /* @__PURE__ */ jsx(
|
|
233
|
+
"span",
|
|
234
|
+
{
|
|
235
|
+
style: {
|
|
236
|
+
position: "absolute",
|
|
237
|
+
top: "4px",
|
|
238
|
+
right: "4px",
|
|
239
|
+
width: "12px",
|
|
240
|
+
height: "12px",
|
|
241
|
+
borderRadius: "50%",
|
|
242
|
+
background: theme.unreadIndicatorColor,
|
|
243
|
+
border: "2px solid white"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
]
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/components/UseAIChatPanel.tsx
|
|
253
|
+
import { useState as useState4, useRef as useRef4, useEffect as useEffect4 } from "react";
|
|
254
|
+
|
|
255
|
+
// src/components/MarkdownContent.tsx
|
|
256
|
+
import ReactMarkdown from "react-markdown";
|
|
257
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
258
|
+
function MarkdownContent({ content }) {
|
|
259
|
+
return /* @__PURE__ */ jsx2(
|
|
260
|
+
ReactMarkdown,
|
|
261
|
+
{
|
|
262
|
+
components: {
|
|
263
|
+
// Override default element rendering for better chat styling
|
|
264
|
+
p: ({ children }) => /* @__PURE__ */ jsx2("p", { style: { margin: "0 0 0.5em 0" }, children }),
|
|
265
|
+
// Ensure last paragraph has no margin
|
|
266
|
+
h1: ({ children }) => /* @__PURE__ */ jsx2("h1", { style: { margin: "0 0 0.5em 0", fontSize: "1.25em", fontWeight: 600 }, children }),
|
|
267
|
+
h2: ({ children }) => /* @__PURE__ */ jsx2("h2", { style: { margin: "0 0 0.5em 0", fontSize: "1.15em", fontWeight: 600 }, children }),
|
|
268
|
+
h3: ({ children }) => /* @__PURE__ */ jsx2("h3", { style: { margin: "0 0 0.5em 0", fontSize: "1.05em", fontWeight: 600 }, children }),
|
|
269
|
+
ul: ({ children }) => /* @__PURE__ */ jsx2("ul", { style: { margin: "0 0 0.5em 0", paddingLeft: "1.5em" }, children }),
|
|
270
|
+
ol: ({ children }) => /* @__PURE__ */ jsx2("ol", { style: { margin: "0 0 0.5em 0", paddingLeft: "1.5em" }, children }),
|
|
271
|
+
li: ({ children }) => /* @__PURE__ */ jsx2("li", { style: { marginBottom: "0.25em" }, children }),
|
|
272
|
+
code: ({ className, children, ...props }) => {
|
|
273
|
+
const isInline = !className;
|
|
274
|
+
if (isInline) {
|
|
275
|
+
return /* @__PURE__ */ jsx2(
|
|
276
|
+
"code",
|
|
277
|
+
{
|
|
278
|
+
style: {
|
|
279
|
+
backgroundColor: "rgba(0, 0, 0, 0.1)",
|
|
280
|
+
padding: "0.1em 0.3em",
|
|
281
|
+
borderRadius: "3px",
|
|
282
|
+
fontSize: "0.9em",
|
|
283
|
+
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Consolas, monospace'
|
|
284
|
+
},
|
|
285
|
+
...props,
|
|
286
|
+
children
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
return /* @__PURE__ */ jsx2("code", { className, ...props, children });
|
|
291
|
+
},
|
|
292
|
+
pre: ({ children }) => /* @__PURE__ */ jsx2(
|
|
293
|
+
"pre",
|
|
294
|
+
{
|
|
295
|
+
style: {
|
|
296
|
+
margin: "0.5em 0",
|
|
297
|
+
padding: "0.75em",
|
|
298
|
+
backgroundColor: "rgba(0, 0, 0, 0.1)",
|
|
299
|
+
borderRadius: "6px",
|
|
300
|
+
overflow: "auto",
|
|
301
|
+
fontSize: "0.85em",
|
|
302
|
+
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Consolas, monospace'
|
|
303
|
+
},
|
|
304
|
+
children
|
|
305
|
+
}
|
|
306
|
+
),
|
|
307
|
+
blockquote: ({ children }) => /* @__PURE__ */ jsx2(
|
|
308
|
+
"blockquote",
|
|
309
|
+
{
|
|
310
|
+
style: {
|
|
311
|
+
margin: "0.5em 0",
|
|
312
|
+
paddingLeft: "1em",
|
|
313
|
+
borderLeft: "3px solid rgba(0, 0, 0, 0.2)",
|
|
314
|
+
color: "inherit",
|
|
315
|
+
opacity: 0.9
|
|
316
|
+
},
|
|
317
|
+
children
|
|
318
|
+
}
|
|
319
|
+
),
|
|
320
|
+
a: ({ children, href }) => /* @__PURE__ */ jsx2(
|
|
321
|
+
"a",
|
|
322
|
+
{
|
|
323
|
+
href,
|
|
324
|
+
target: "_blank",
|
|
325
|
+
rel: "noopener noreferrer",
|
|
326
|
+
style: {
|
|
327
|
+
color: "inherit",
|
|
328
|
+
textDecoration: "underline",
|
|
329
|
+
textUnderlineOffset: "2px"
|
|
330
|
+
},
|
|
331
|
+
children
|
|
332
|
+
}
|
|
333
|
+
),
|
|
334
|
+
hr: () => /* @__PURE__ */ jsx2(
|
|
335
|
+
"hr",
|
|
336
|
+
{
|
|
337
|
+
style: {
|
|
338
|
+
margin: "0.75em 0",
|
|
339
|
+
border: "none",
|
|
340
|
+
borderTop: "1px solid rgba(0, 0, 0, 0.2)"
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
),
|
|
344
|
+
table: ({ children }) => /* @__PURE__ */ jsx2("div", { style: { overflowX: "auto", margin: "0.5em 0" }, children: /* @__PURE__ */ jsx2(
|
|
345
|
+
"table",
|
|
346
|
+
{
|
|
347
|
+
style: {
|
|
348
|
+
borderCollapse: "collapse",
|
|
349
|
+
fontSize: "0.9em",
|
|
350
|
+
width: "100%"
|
|
351
|
+
},
|
|
352
|
+
children
|
|
353
|
+
}
|
|
354
|
+
) }),
|
|
355
|
+
th: ({ children }) => /* @__PURE__ */ jsx2(
|
|
356
|
+
"th",
|
|
357
|
+
{
|
|
358
|
+
style: {
|
|
359
|
+
padding: "0.4em 0.6em",
|
|
360
|
+
borderBottom: "2px solid rgba(0, 0, 0, 0.2)",
|
|
361
|
+
textAlign: "left",
|
|
362
|
+
fontWeight: 600
|
|
363
|
+
},
|
|
364
|
+
children
|
|
365
|
+
}
|
|
366
|
+
),
|
|
367
|
+
td: ({ children }) => /* @__PURE__ */ jsx2(
|
|
368
|
+
"td",
|
|
369
|
+
{
|
|
370
|
+
style: {
|
|
371
|
+
padding: "0.4em 0.6em",
|
|
372
|
+
borderBottom: "1px solid rgba(0, 0, 0, 0.1)"
|
|
373
|
+
},
|
|
374
|
+
children
|
|
375
|
+
}
|
|
376
|
+
)
|
|
377
|
+
},
|
|
378
|
+
children: content
|
|
379
|
+
}
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/components/FileChip.tsx
|
|
384
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
385
|
+
function formatFileSize(bytes) {
|
|
386
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
387
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
388
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
389
|
+
}
|
|
390
|
+
function truncateFilename(name, maxLength = 20) {
|
|
391
|
+
if (name.length <= maxLength) return name;
|
|
392
|
+
const lastDot = name.lastIndexOf(".");
|
|
393
|
+
const ext = lastDot > 0 ? name.substring(lastDot) : "";
|
|
394
|
+
const baseName = lastDot > 0 ? name.substring(0, lastDot) : name;
|
|
395
|
+
const maxBaseLength = maxLength - ext.length - 3;
|
|
396
|
+
if (maxBaseLength < 5) return name.substring(0, maxLength - 3) + "...";
|
|
397
|
+
return baseName.substring(0, maxBaseLength) + "..." + ext;
|
|
398
|
+
}
|
|
399
|
+
function FileChip({ attachment, onRemove, disabled }) {
|
|
400
|
+
const theme = useTheme();
|
|
401
|
+
const { file, preview } = attachment;
|
|
402
|
+
const isImage = file.type.startsWith("image/");
|
|
403
|
+
return /* @__PURE__ */ jsxs2(
|
|
404
|
+
"div",
|
|
405
|
+
{
|
|
406
|
+
"data-testid": "file-chip",
|
|
407
|
+
style: {
|
|
408
|
+
display: "inline-flex",
|
|
409
|
+
alignItems: "center",
|
|
410
|
+
gap: "8px",
|
|
411
|
+
padding: "6px 10px",
|
|
412
|
+
background: theme.hoverBackground,
|
|
413
|
+
borderRadius: "8px",
|
|
414
|
+
fontSize: "13px",
|
|
415
|
+
color: theme.textColor,
|
|
416
|
+
maxWidth: "200px"
|
|
417
|
+
},
|
|
418
|
+
children: [
|
|
419
|
+
isImage && preview ? /* @__PURE__ */ jsx3(
|
|
420
|
+
"img",
|
|
421
|
+
{
|
|
422
|
+
src: preview,
|
|
423
|
+
alt: file.name,
|
|
424
|
+
style: {
|
|
425
|
+
width: "24px",
|
|
426
|
+
height: "24px",
|
|
427
|
+
borderRadius: "4px",
|
|
428
|
+
objectFit: "cover"
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
) : /* @__PURE__ */ jsx3(
|
|
432
|
+
"div",
|
|
433
|
+
{
|
|
434
|
+
style: {
|
|
435
|
+
width: "24px",
|
|
436
|
+
height: "24px",
|
|
437
|
+
borderRadius: "4px",
|
|
438
|
+
background: theme.borderColor,
|
|
439
|
+
display: "flex",
|
|
440
|
+
alignItems: "center",
|
|
441
|
+
justifyContent: "center",
|
|
442
|
+
fontSize: "12px"
|
|
443
|
+
},
|
|
444
|
+
children: "\u{1F4CE}"
|
|
445
|
+
}
|
|
446
|
+
),
|
|
447
|
+
/* @__PURE__ */ jsxs2("div", { style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: [
|
|
448
|
+
/* @__PURE__ */ jsx3(
|
|
449
|
+
"div",
|
|
450
|
+
{
|
|
451
|
+
style: {
|
|
452
|
+
fontWeight: 500,
|
|
453
|
+
overflow: "hidden",
|
|
454
|
+
textOverflow: "ellipsis",
|
|
455
|
+
whiteSpace: "nowrap"
|
|
456
|
+
},
|
|
457
|
+
title: file.name,
|
|
458
|
+
children: truncateFilename(file.name)
|
|
459
|
+
}
|
|
460
|
+
),
|
|
461
|
+
/* @__PURE__ */ jsx3("div", { style: { fontSize: "11px", color: theme.secondaryTextColor }, children: formatFileSize(file.size) })
|
|
462
|
+
] }),
|
|
463
|
+
/* @__PURE__ */ jsx3(
|
|
464
|
+
"button",
|
|
465
|
+
{
|
|
466
|
+
"data-testid": "file-chip-remove",
|
|
467
|
+
onClick: onRemove,
|
|
468
|
+
disabled,
|
|
469
|
+
style: {
|
|
470
|
+
background: "transparent",
|
|
471
|
+
border: "none",
|
|
472
|
+
padding: "2px 4px",
|
|
473
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
474
|
+
color: theme.placeholderTextColor,
|
|
475
|
+
fontSize: "16px",
|
|
476
|
+
lineHeight: 1,
|
|
477
|
+
borderRadius: "4px",
|
|
478
|
+
transition: "all 0.15s",
|
|
479
|
+
opacity: disabled ? 0.5 : 1
|
|
480
|
+
},
|
|
481
|
+
onMouseEnter: (e) => {
|
|
482
|
+
if (!disabled) {
|
|
483
|
+
e.currentTarget.style.background = theme.borderColor;
|
|
484
|
+
e.currentTarget.style.color = theme.textColor;
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
onMouseLeave: (e) => {
|
|
488
|
+
e.currentTarget.style.background = "transparent";
|
|
489
|
+
e.currentTarget.style.color = theme.placeholderTextColor;
|
|
490
|
+
},
|
|
491
|
+
children: "\xD7"
|
|
492
|
+
}
|
|
493
|
+
)
|
|
494
|
+
]
|
|
495
|
+
}
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
function FilePlaceholder({ name, size }) {
|
|
499
|
+
const theme = useTheme();
|
|
500
|
+
return /* @__PURE__ */ jsxs2(
|
|
501
|
+
"div",
|
|
502
|
+
{
|
|
503
|
+
"data-testid": "file-placeholder",
|
|
504
|
+
style: {
|
|
505
|
+
display: "inline-flex",
|
|
506
|
+
alignItems: "center",
|
|
507
|
+
gap: "8px",
|
|
508
|
+
padding: "6px 10px",
|
|
509
|
+
background: theme.backgroundColor,
|
|
510
|
+
border: `1px dashed ${theme.dashedBorderColor}`,
|
|
511
|
+
borderRadius: "8px",
|
|
512
|
+
fontSize: "13px",
|
|
513
|
+
color: theme.placeholderTextColor,
|
|
514
|
+
maxWidth: "200px"
|
|
515
|
+
},
|
|
516
|
+
children: [
|
|
517
|
+
/* @__PURE__ */ jsx3("span", { children: "\u{1F4CE}" }),
|
|
518
|
+
/* @__PURE__ */ jsxs2("div", { style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: [
|
|
519
|
+
/* @__PURE__ */ jsx3(
|
|
520
|
+
"div",
|
|
521
|
+
{
|
|
522
|
+
style: {
|
|
523
|
+
overflow: "hidden",
|
|
524
|
+
textOverflow: "ellipsis",
|
|
525
|
+
whiteSpace: "nowrap"
|
|
526
|
+
},
|
|
527
|
+
title: name,
|
|
528
|
+
children: truncateFilename(name)
|
|
529
|
+
}
|
|
530
|
+
),
|
|
531
|
+
/* @__PURE__ */ jsx3("div", { style: { fontSize: "11px" }, children: formatFileSize(size) })
|
|
532
|
+
] })
|
|
533
|
+
]
|
|
534
|
+
}
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// src/hooks/useSlashCommands.tsx
|
|
539
|
+
import { useState, useRef as useRef2, useEffect as useEffect2, useCallback } from "react";
|
|
540
|
+
|
|
541
|
+
// src/commands/types.ts
|
|
542
|
+
function generateCommandId() {
|
|
543
|
+
return `cmd_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
544
|
+
}
|
|
545
|
+
function validateCommandName(name) {
|
|
546
|
+
if (!name.trim()) {
|
|
547
|
+
return "Command name is required";
|
|
548
|
+
}
|
|
549
|
+
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
550
|
+
return "Only lowercase letters, numbers, and hyphens allowed (kebab-case)";
|
|
551
|
+
}
|
|
552
|
+
if (name.length > 50) {
|
|
553
|
+
return "Command name must be 50 characters or less";
|
|
554
|
+
}
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// src/components/CommandAutocomplete.tsx
|
|
559
|
+
import { useEffect, useRef } from "react";
|
|
560
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
561
|
+
var MAX_VISIBLE_ITEMS = 8;
|
|
562
|
+
function CommandAutocomplete({
|
|
563
|
+
commands,
|
|
564
|
+
searchPrefix,
|
|
565
|
+
highlightedIndex,
|
|
566
|
+
onSelect,
|
|
567
|
+
onDelete,
|
|
568
|
+
onHighlightChange,
|
|
569
|
+
onClose
|
|
570
|
+
}) {
|
|
571
|
+
const strings = useStrings();
|
|
572
|
+
const theme = useTheme();
|
|
573
|
+
const listRef = useRef(null);
|
|
574
|
+
const itemRefs = useRef([]);
|
|
575
|
+
const filteredCommands = commands.filter(
|
|
576
|
+
(c) => c.name.toLowerCase().startsWith(searchPrefix.toLowerCase())
|
|
577
|
+
).slice(0, MAX_VISIBLE_ITEMS);
|
|
578
|
+
useEffect(() => {
|
|
579
|
+
const item = itemRefs.current[highlightedIndex];
|
|
580
|
+
if (item && listRef.current) {
|
|
581
|
+
const listRect = listRef.current.getBoundingClientRect();
|
|
582
|
+
const itemRect = item.getBoundingClientRect();
|
|
583
|
+
if (itemRect.bottom > listRect.bottom) {
|
|
584
|
+
item.scrollIntoView({ block: "end" });
|
|
585
|
+
} else if (itemRect.top < listRect.top) {
|
|
586
|
+
item.scrollIntoView({ block: "start" });
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}, [highlightedIndex]);
|
|
590
|
+
if (filteredCommands.length === 0) {
|
|
591
|
+
return /* @__PURE__ */ jsx4(
|
|
592
|
+
"div",
|
|
593
|
+
{
|
|
594
|
+
"data-testid": "command-autocomplete",
|
|
595
|
+
style: {
|
|
596
|
+
position: "absolute",
|
|
597
|
+
bottom: "100%",
|
|
598
|
+
left: 0,
|
|
599
|
+
right: 0,
|
|
600
|
+
marginBottom: "8px",
|
|
601
|
+
background: theme.backgroundColor,
|
|
602
|
+
borderRadius: "8px",
|
|
603
|
+
boxShadow: theme.dropdownShadow,
|
|
604
|
+
overflow: "hidden",
|
|
605
|
+
zIndex: 1005
|
|
606
|
+
},
|
|
607
|
+
children: /* @__PURE__ */ jsx4(
|
|
608
|
+
"div",
|
|
609
|
+
{
|
|
610
|
+
style: {
|
|
611
|
+
padding: "12px 16px",
|
|
612
|
+
color: theme.secondaryTextColor,
|
|
613
|
+
fontSize: "13px",
|
|
614
|
+
textAlign: "center"
|
|
615
|
+
},
|
|
616
|
+
children: commands.length === 0 ? strings.commands.noSavedCommands : strings.commands.noMatchingCommands
|
|
617
|
+
}
|
|
618
|
+
)
|
|
619
|
+
}
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
return /* @__PURE__ */ jsx4(
|
|
623
|
+
"div",
|
|
624
|
+
{
|
|
625
|
+
"data-testid": "command-autocomplete",
|
|
626
|
+
ref: listRef,
|
|
627
|
+
style: {
|
|
628
|
+
position: "absolute",
|
|
629
|
+
bottom: "100%",
|
|
630
|
+
left: 0,
|
|
631
|
+
right: 0,
|
|
632
|
+
marginBottom: "8px",
|
|
633
|
+
background: theme.backgroundColor,
|
|
634
|
+
borderRadius: "8px",
|
|
635
|
+
boxShadow: theme.dropdownShadow,
|
|
636
|
+
overflow: "hidden",
|
|
637
|
+
maxHeight: "320px",
|
|
638
|
+
overflowY: "auto",
|
|
639
|
+
zIndex: 1005
|
|
640
|
+
},
|
|
641
|
+
children: filteredCommands.map((cmd, index) => /* @__PURE__ */ jsxs3(
|
|
642
|
+
"div",
|
|
643
|
+
{
|
|
644
|
+
ref: (el) => {
|
|
645
|
+
itemRefs.current[index] = el;
|
|
646
|
+
},
|
|
647
|
+
"data-testid": "command-autocomplete-item",
|
|
648
|
+
onClick: () => onSelect(cmd),
|
|
649
|
+
onMouseEnter: () => onHighlightChange(index),
|
|
650
|
+
style: {
|
|
651
|
+
padding: "10px 14px",
|
|
652
|
+
background: index === highlightedIndex ? theme.hoverBackground : "transparent",
|
|
653
|
+
cursor: "pointer",
|
|
654
|
+
borderBottom: index < filteredCommands.length - 1 ? `1px solid ${theme.hoverBackground}` : "none",
|
|
655
|
+
transition: "background 0.1s",
|
|
656
|
+
display: "flex",
|
|
657
|
+
alignItems: "flex-start",
|
|
658
|
+
gap: "8px"
|
|
659
|
+
},
|
|
660
|
+
children: [
|
|
661
|
+
/* @__PURE__ */ jsxs3("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
662
|
+
/* @__PURE__ */ jsxs3(
|
|
663
|
+
"div",
|
|
664
|
+
{
|
|
665
|
+
style: {
|
|
666
|
+
fontWeight: 600,
|
|
667
|
+
fontSize: "14px",
|
|
668
|
+
color: theme.primaryColor
|
|
669
|
+
},
|
|
670
|
+
children: [
|
|
671
|
+
"/",
|
|
672
|
+
cmd.name
|
|
673
|
+
]
|
|
674
|
+
}
|
|
675
|
+
),
|
|
676
|
+
/* @__PURE__ */ jsx4(
|
|
677
|
+
"div",
|
|
678
|
+
{
|
|
679
|
+
style: {
|
|
680
|
+
marginTop: "4px",
|
|
681
|
+
fontSize: "13px",
|
|
682
|
+
color: theme.secondaryTextColor,
|
|
683
|
+
overflow: "hidden",
|
|
684
|
+
textOverflow: "ellipsis",
|
|
685
|
+
whiteSpace: "nowrap"
|
|
686
|
+
},
|
|
687
|
+
children: cmd.text.length > 60 ? cmd.text.substring(0, 60) + "..." : cmd.text
|
|
688
|
+
}
|
|
689
|
+
)
|
|
690
|
+
] }),
|
|
691
|
+
onDelete && /* @__PURE__ */ jsx4(
|
|
692
|
+
"button",
|
|
693
|
+
{
|
|
694
|
+
"data-testid": "command-delete-button",
|
|
695
|
+
onClick: (e) => {
|
|
696
|
+
e.stopPropagation();
|
|
697
|
+
onDelete(cmd);
|
|
698
|
+
},
|
|
699
|
+
style: {
|
|
700
|
+
padding: "4px",
|
|
701
|
+
background: "transparent",
|
|
702
|
+
border: "none",
|
|
703
|
+
borderRadius: "4px",
|
|
704
|
+
cursor: "pointer",
|
|
705
|
+
color: theme.placeholderTextColor,
|
|
706
|
+
display: "flex",
|
|
707
|
+
alignItems: "center",
|
|
708
|
+
justifyContent: "center",
|
|
709
|
+
flexShrink: 0,
|
|
710
|
+
marginTop: "2px",
|
|
711
|
+
transition: "all 0.15s"
|
|
712
|
+
},
|
|
713
|
+
onMouseEnter: (e) => {
|
|
714
|
+
e.currentTarget.style.color = theme.dangerColor;
|
|
715
|
+
e.currentTarget.style.background = theme.errorBackground;
|
|
716
|
+
},
|
|
717
|
+
onMouseLeave: (e) => {
|
|
718
|
+
e.currentTarget.style.color = theme.placeholderTextColor;
|
|
719
|
+
e.currentTarget.style.background = "transparent";
|
|
720
|
+
},
|
|
721
|
+
title: strings.commands.deleteCommand,
|
|
722
|
+
children: /* @__PURE__ */ jsx4("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx4("path", { d: "M2 4H14M6.5 7V11M9.5 7V11M3 4L4 13C4 13.5304 4.21071 14.0391 4.58579 14.4142C4.96086 14.7893 5.46957 15 6 15H10C10.5304 15 11.0391 14.7893 11.4142 14.4142C11.7893 14.0391 12 13.5304 12 13L13 4M5.5 4V2.5C5.5 2.23478 5.60536 1.98043 5.79289 1.79289C5.98043 1.60536 6.23478 1.5 6.5 1.5H9.5C9.76522 1.5 10.0196 1.60536 10.2071 1.79289C10.3946 1.98043 10.5 2.23478 10.5 2.5V4" }) })
|
|
723
|
+
}
|
|
724
|
+
)
|
|
725
|
+
]
|
|
726
|
+
},
|
|
727
|
+
cmd.id
|
|
728
|
+
))
|
|
729
|
+
}
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
function getFilteredCommandsCount(commands, searchPrefix) {
|
|
733
|
+
return Math.min(
|
|
734
|
+
commands.filter((c) => c.name.toLowerCase().startsWith(searchPrefix.toLowerCase())).length,
|
|
735
|
+
MAX_VISIBLE_ITEMS
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// src/hooks/useSlashCommands.tsx
|
|
740
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
741
|
+
var MAX_VISIBLE_ITEMS2 = 8;
|
|
742
|
+
function useSlashCommands({
|
|
743
|
+
commands,
|
|
744
|
+
onCommandSelect,
|
|
745
|
+
onSaveCommand,
|
|
746
|
+
onRenameCommand,
|
|
747
|
+
onDeleteCommand
|
|
748
|
+
}) {
|
|
749
|
+
const strings = useStrings();
|
|
750
|
+
const theme = useTheme();
|
|
751
|
+
const [showAutocomplete, setShowAutocomplete] = useState(false);
|
|
752
|
+
const [searchPrefix, setSearchPrefix] = useState("");
|
|
753
|
+
const [highlightedIndex, setHighlightedIndex] = useState(0);
|
|
754
|
+
const [savingMessageId, setSavingMessageId] = useState(null);
|
|
755
|
+
const [savingMessageText, setSavingMessageText] = useState("");
|
|
756
|
+
const [commandNameInput, setCommandNameInput] = useState("");
|
|
757
|
+
const [commandSaveError, setCommandSaveError] = useState(null);
|
|
758
|
+
const commandNameInputRef = useRef2(null);
|
|
759
|
+
useEffect2(() => {
|
|
760
|
+
if (savingMessageId) {
|
|
761
|
+
setTimeout(() => commandNameInputRef.current?.focus(), 0);
|
|
762
|
+
}
|
|
763
|
+
}, [savingMessageId]);
|
|
764
|
+
const selectCommand = useCallback((command) => {
|
|
765
|
+
setShowAutocomplete(false);
|
|
766
|
+
onCommandSelect?.(command.text);
|
|
767
|
+
}, [onCommandSelect]);
|
|
768
|
+
const handleInputChange = useCallback((value) => {
|
|
769
|
+
if (value.startsWith("/") && commands.length > 0) {
|
|
770
|
+
setSearchPrefix(value.slice(1));
|
|
771
|
+
setShowAutocomplete(true);
|
|
772
|
+
setHighlightedIndex(0);
|
|
773
|
+
return true;
|
|
774
|
+
} else {
|
|
775
|
+
setShowAutocomplete(false);
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
}, [commands.length]);
|
|
779
|
+
const handleKeyDown = useCallback((e) => {
|
|
780
|
+
if (!showAutocomplete) {
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
const filteredCount = getFilteredCommandsCount(commands, searchPrefix);
|
|
784
|
+
if (e.key === "ArrowDown") {
|
|
785
|
+
e.preventDefault();
|
|
786
|
+
setHighlightedIndex((i) => Math.min(i + 1, filteredCount - 1));
|
|
787
|
+
return true;
|
|
788
|
+
} else if (e.key === "ArrowUp") {
|
|
789
|
+
e.preventDefault();
|
|
790
|
+
setHighlightedIndex((i) => Math.max(i - 1, 0));
|
|
791
|
+
return true;
|
|
792
|
+
} else if (e.key === "Enter" && !e.shiftKey) {
|
|
793
|
+
e.preventDefault();
|
|
794
|
+
const filteredCommands = commands.filter((c) => c.name.toLowerCase().startsWith(searchPrefix.toLowerCase())).slice(0, MAX_VISIBLE_ITEMS2);
|
|
795
|
+
if (filteredCommands[highlightedIndex]) {
|
|
796
|
+
selectCommand(filteredCommands[highlightedIndex]);
|
|
797
|
+
}
|
|
798
|
+
return true;
|
|
799
|
+
} else if (e.key === "Escape") {
|
|
800
|
+
e.preventDefault();
|
|
801
|
+
setShowAutocomplete(false);
|
|
802
|
+
return true;
|
|
803
|
+
}
|
|
804
|
+
return false;
|
|
805
|
+
}, [showAutocomplete, commands, searchPrefix, highlightedIndex, selectCommand]);
|
|
806
|
+
const closeAutocomplete = useCallback(() => {
|
|
807
|
+
setShowAutocomplete(false);
|
|
808
|
+
}, []);
|
|
809
|
+
const handleDeleteCommand = useCallback((command) => {
|
|
810
|
+
if (onDeleteCommand) {
|
|
811
|
+
onDeleteCommand(command.id);
|
|
812
|
+
}
|
|
813
|
+
}, [onDeleteCommand]);
|
|
814
|
+
const startSavingCommand = useCallback((messageId, messageText) => {
|
|
815
|
+
const existingCommand = commands.find((c) => c.text === messageText);
|
|
816
|
+
setSavingMessageId(messageId);
|
|
817
|
+
setSavingMessageText(messageText);
|
|
818
|
+
setCommandNameInput(existingCommand?.name || "");
|
|
819
|
+
setCommandSaveError(null);
|
|
820
|
+
}, [commands]);
|
|
821
|
+
const isSavingCommand = useCallback((messageId) => {
|
|
822
|
+
return savingMessageId === messageId;
|
|
823
|
+
}, [savingMessageId]);
|
|
824
|
+
const cancelInlineSave = useCallback(() => {
|
|
825
|
+
setSavingMessageId(null);
|
|
826
|
+
setSavingMessageText("");
|
|
827
|
+
setCommandNameInput("");
|
|
828
|
+
setCommandSaveError(null);
|
|
829
|
+
}, []);
|
|
830
|
+
const handleInlineSaveCommand = useCallback(async () => {
|
|
831
|
+
if (!savingMessageId || !savingMessageText.trim()) return;
|
|
832
|
+
const name = commandNameInput.trim();
|
|
833
|
+
const validationError = validateCommandName(name);
|
|
834
|
+
if (validationError) {
|
|
835
|
+
setCommandSaveError(validationError);
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
const existingCommand = commands.find((c) => c.text === savingMessageText);
|
|
839
|
+
if (existingCommand) {
|
|
840
|
+
if (existingCommand.name === name) {
|
|
841
|
+
cancelInlineSave();
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
if (commands.some((c) => c.name === name && c.id !== existingCommand.id)) {
|
|
845
|
+
setCommandSaveError(strings.commands.commandNameExists);
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
if (!onRenameCommand) {
|
|
849
|
+
setCommandSaveError(strings.commands.renameNotSupported);
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
try {
|
|
853
|
+
await onRenameCommand(existingCommand.id, name);
|
|
854
|
+
cancelInlineSave();
|
|
855
|
+
} catch (err) {
|
|
856
|
+
setCommandSaveError(err instanceof Error ? err.message : strings.commands.renameFailed);
|
|
857
|
+
}
|
|
858
|
+
} else {
|
|
859
|
+
if (commands.some((c) => c.name === name)) {
|
|
860
|
+
setCommandSaveError(strings.commands.commandNameExists);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
if (!onSaveCommand) {
|
|
864
|
+
setCommandSaveError(strings.commands.saveNotSupported);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
try {
|
|
868
|
+
await onSaveCommand(name, savingMessageText);
|
|
869
|
+
cancelInlineSave();
|
|
870
|
+
} catch (err) {
|
|
871
|
+
setCommandSaveError(err instanceof Error ? err.message : strings.commands.saveFailed);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}, [savingMessageId, savingMessageText, commandNameInput, commands, onRenameCommand, onSaveCommand, cancelInlineSave, strings]);
|
|
875
|
+
const AutocompleteComponent = showAutocomplete && commands.length > 0 ? /* @__PURE__ */ jsx5(
|
|
876
|
+
CommandAutocomplete,
|
|
877
|
+
{
|
|
878
|
+
commands,
|
|
879
|
+
searchPrefix,
|
|
880
|
+
highlightedIndex,
|
|
881
|
+
onSelect: selectCommand,
|
|
882
|
+
onDelete: onDeleteCommand ? handleDeleteCommand : void 0,
|
|
883
|
+
onHighlightChange: setHighlightedIndex,
|
|
884
|
+
onClose: closeAutocomplete
|
|
885
|
+
}
|
|
886
|
+
) : null;
|
|
887
|
+
const renderInlineSaveUI = useCallback(({ messageId, messageText }) => {
|
|
888
|
+
if (savingMessageId !== messageId) {
|
|
889
|
+
return null;
|
|
890
|
+
}
|
|
891
|
+
return /* @__PURE__ */ jsxs4(
|
|
892
|
+
"div",
|
|
893
|
+
{
|
|
894
|
+
"data-testid": "inline-save-command",
|
|
895
|
+
onClick: (e) => e.stopPropagation(),
|
|
896
|
+
style: {
|
|
897
|
+
padding: "6px 10px",
|
|
898
|
+
background: theme.hoverBackground,
|
|
899
|
+
borderRadius: "0 0 12px 12px",
|
|
900
|
+
display: "flex",
|
|
901
|
+
flexDirection: "column",
|
|
902
|
+
gap: "4px"
|
|
903
|
+
},
|
|
904
|
+
children: [
|
|
905
|
+
/* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", gap: "6px" }, children: [
|
|
906
|
+
/* @__PURE__ */ jsx5("span", { style: { color: theme.primaryColor, fontSize: "13px", fontWeight: 500 }, children: "/" }),
|
|
907
|
+
/* @__PURE__ */ jsx5(
|
|
908
|
+
"input",
|
|
909
|
+
{
|
|
910
|
+
ref: commandNameInputRef,
|
|
911
|
+
"data-testid": "command-name-input",
|
|
912
|
+
type: "text",
|
|
913
|
+
value: commandNameInput,
|
|
914
|
+
onChange: (e) => {
|
|
915
|
+
setCommandNameInput(e.target.value);
|
|
916
|
+
setCommandSaveError(null);
|
|
917
|
+
},
|
|
918
|
+
onKeyDown: (e) => {
|
|
919
|
+
if (e.key === "Enter") {
|
|
920
|
+
e.preventDefault();
|
|
921
|
+
handleInlineSaveCommand();
|
|
922
|
+
} else if (e.key === "Escape") {
|
|
923
|
+
cancelInlineSave();
|
|
924
|
+
}
|
|
925
|
+
},
|
|
926
|
+
placeholder: strings.commands.commandNamePlaceholder,
|
|
927
|
+
style: {
|
|
928
|
+
flex: 1,
|
|
929
|
+
border: commandSaveError ? `1px solid ${theme.dangerColor}` : `1px solid ${theme.borderColor}`,
|
|
930
|
+
borderRadius: "6px",
|
|
931
|
+
padding: "5px 8px",
|
|
932
|
+
fontSize: "13px",
|
|
933
|
+
outline: "none",
|
|
934
|
+
background: theme.backgroundColor,
|
|
935
|
+
minWidth: 0
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
),
|
|
939
|
+
/* @__PURE__ */ jsx5(
|
|
940
|
+
"button",
|
|
941
|
+
{
|
|
942
|
+
"data-testid": "save-command-confirm",
|
|
943
|
+
onClick: handleInlineSaveCommand,
|
|
944
|
+
disabled: !commandNameInput.trim(),
|
|
945
|
+
style: {
|
|
946
|
+
padding: "5px",
|
|
947
|
+
border: "none",
|
|
948
|
+
borderRadius: "6px",
|
|
949
|
+
background: "transparent",
|
|
950
|
+
color: commandNameInput.trim() ? theme.primaryColor : theme.dashedBorderColor,
|
|
951
|
+
cursor: commandNameInput.trim() ? "pointer" : "not-allowed",
|
|
952
|
+
display: "flex",
|
|
953
|
+
alignItems: "center",
|
|
954
|
+
justifyContent: "center"
|
|
955
|
+
},
|
|
956
|
+
title: strings.commands.saveCommand,
|
|
957
|
+
children: /* @__PURE__ */ jsxs4("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
958
|
+
/* @__PURE__ */ jsx5("path", { d: "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" }),
|
|
959
|
+
/* @__PURE__ */ jsx5("polyline", { points: "17 21 17 13 7 13 7 21" }),
|
|
960
|
+
/* @__PURE__ */ jsx5("polyline", { points: "7 3 7 8 15 8" })
|
|
961
|
+
] })
|
|
962
|
+
}
|
|
963
|
+
)
|
|
964
|
+
] }),
|
|
965
|
+
commandSaveError && /* @__PURE__ */ jsx5(
|
|
966
|
+
"div",
|
|
967
|
+
{
|
|
968
|
+
"data-testid": "command-save-error",
|
|
969
|
+
style: {
|
|
970
|
+
fontSize: "11px",
|
|
971
|
+
color: theme.dangerColor,
|
|
972
|
+
paddingLeft: "2px"
|
|
973
|
+
},
|
|
974
|
+
children: commandSaveError
|
|
975
|
+
}
|
|
976
|
+
)
|
|
977
|
+
]
|
|
978
|
+
}
|
|
979
|
+
);
|
|
980
|
+
}, [savingMessageId, commandNameInput, commandSaveError, handleInlineSaveCommand, cancelInlineSave, theme, strings]);
|
|
981
|
+
return {
|
|
982
|
+
isAutocompleteVisible: showAutocomplete,
|
|
983
|
+
handleInputChange,
|
|
984
|
+
handleKeyDown,
|
|
985
|
+
closeAutocomplete,
|
|
986
|
+
AutocompleteComponent,
|
|
987
|
+
startSavingCommand,
|
|
988
|
+
isSavingCommand,
|
|
989
|
+
cancelInlineSave,
|
|
990
|
+
renderInlineSaveUI
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// src/hooks/useFileUpload.tsx
|
|
995
|
+
import { useState as useState2, useRef as useRef3, useCallback as useCallback2, useEffect as useEffect3, useMemo } from "react";
|
|
996
|
+
|
|
997
|
+
// src/fileUpload/types.ts
|
|
998
|
+
var DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
999
|
+
|
|
1000
|
+
// src/hooks/useFileUpload.tsx
|
|
1001
|
+
import { v4 as uuidv4 } from "uuid";
|
|
1002
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
1003
|
+
async function generateImagePreview(file) {
|
|
1004
|
+
if (!file.type.startsWith("image/")) {
|
|
1005
|
+
return void 0;
|
|
1006
|
+
}
|
|
1007
|
+
return new Promise((resolve) => {
|
|
1008
|
+
const reader = new FileReader();
|
|
1009
|
+
reader.onload = () => {
|
|
1010
|
+
if (typeof reader.result === "string") {
|
|
1011
|
+
resolve(reader.result);
|
|
1012
|
+
} else {
|
|
1013
|
+
resolve(void 0);
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
reader.onerror = () => resolve(void 0);
|
|
1017
|
+
reader.readAsDataURL(file);
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
function isTypeAccepted(mimeType, acceptedTypes) {
|
|
1021
|
+
if (!acceptedTypes || acceptedTypes.length === 0) {
|
|
1022
|
+
return true;
|
|
1023
|
+
}
|
|
1024
|
+
return acceptedTypes.some((pattern) => {
|
|
1025
|
+
if (pattern.endsWith("/*")) {
|
|
1026
|
+
const prefix = pattern.slice(0, -1);
|
|
1027
|
+
return mimeType.startsWith(prefix);
|
|
1028
|
+
}
|
|
1029
|
+
return mimeType === pattern;
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
function useFileUpload({
|
|
1033
|
+
config,
|
|
1034
|
+
disabled = false,
|
|
1035
|
+
resetDependency
|
|
1036
|
+
}) {
|
|
1037
|
+
const strings = useStrings();
|
|
1038
|
+
const theme = useTheme();
|
|
1039
|
+
const [attachments, setAttachments] = useState2([]);
|
|
1040
|
+
const [isDragging, setIsDragging] = useState2(false);
|
|
1041
|
+
const [fileError, setFileError] = useState2(null);
|
|
1042
|
+
const fileInputRef = useRef3(null);
|
|
1043
|
+
const dragCounterRef = useRef3(0);
|
|
1044
|
+
const enabled = config !== void 0;
|
|
1045
|
+
const maxFileSize = config?.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
|
|
1046
|
+
const acceptedTypes = config?.acceptedTypes;
|
|
1047
|
+
useEffect3(() => {
|
|
1048
|
+
if (fileError) {
|
|
1049
|
+
const timer = setTimeout(() => setFileError(null), 3e3);
|
|
1050
|
+
return () => clearTimeout(timer);
|
|
1051
|
+
}
|
|
1052
|
+
}, [fileError]);
|
|
1053
|
+
useEffect3(() => {
|
|
1054
|
+
setAttachments([]);
|
|
1055
|
+
setFileError(null);
|
|
1056
|
+
}, [resetDependency]);
|
|
1057
|
+
const handleFiles = useCallback2(async (files) => {
|
|
1058
|
+
const fileArray = Array.from(files);
|
|
1059
|
+
for (const file of fileArray) {
|
|
1060
|
+
if (file.size > maxFileSize) {
|
|
1061
|
+
const errorMsg = strings.fileUpload.fileSizeError.replace("{filename}", file.name).replace("{maxSize}", String(Math.round(maxFileSize / (1024 * 1024))));
|
|
1062
|
+
setFileError(errorMsg);
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
if (!isTypeAccepted(file.type, acceptedTypes)) {
|
|
1066
|
+
const errorMsg = strings.fileUpload.fileTypeError.replace("{type}", file.type);
|
|
1067
|
+
setFileError(errorMsg);
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
const preview = await generateImagePreview(file);
|
|
1071
|
+
setAttachments((prev) => [
|
|
1072
|
+
...prev,
|
|
1073
|
+
{
|
|
1074
|
+
id: uuidv4(),
|
|
1075
|
+
file,
|
|
1076
|
+
preview
|
|
1077
|
+
}
|
|
1078
|
+
]);
|
|
1079
|
+
}
|
|
1080
|
+
}, [maxFileSize, acceptedTypes, strings]);
|
|
1081
|
+
const removeAttachment = useCallback2((id) => {
|
|
1082
|
+
setAttachments((prev) => prev.filter((a) => a.id !== id));
|
|
1083
|
+
}, []);
|
|
1084
|
+
const clearAttachments = useCallback2(() => {
|
|
1085
|
+
setAttachments([]);
|
|
1086
|
+
}, []);
|
|
1087
|
+
const openFilePicker = useCallback2(() => {
|
|
1088
|
+
fileInputRef.current?.click();
|
|
1089
|
+
}, []);
|
|
1090
|
+
const handleFileInputChange = useCallback2((e) => {
|
|
1091
|
+
const files = e.target.files;
|
|
1092
|
+
if (files && files.length > 0) {
|
|
1093
|
+
handleFiles(files);
|
|
1094
|
+
}
|
|
1095
|
+
e.target.value = "";
|
|
1096
|
+
}, [handleFiles]);
|
|
1097
|
+
const handleDragEnter = useCallback2((e) => {
|
|
1098
|
+
e.preventDefault();
|
|
1099
|
+
e.stopPropagation();
|
|
1100
|
+
if (enabled && !disabled) {
|
|
1101
|
+
dragCounterRef.current++;
|
|
1102
|
+
if (dragCounterRef.current === 1) {
|
|
1103
|
+
setIsDragging(true);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}, [enabled, disabled]);
|
|
1107
|
+
const handleDragOver = useCallback2((e) => {
|
|
1108
|
+
e.preventDefault();
|
|
1109
|
+
e.stopPropagation();
|
|
1110
|
+
}, []);
|
|
1111
|
+
const handleDragLeave = useCallback2((e) => {
|
|
1112
|
+
e.preventDefault();
|
|
1113
|
+
e.stopPropagation();
|
|
1114
|
+
dragCounterRef.current--;
|
|
1115
|
+
if (dragCounterRef.current === 0) {
|
|
1116
|
+
setIsDragging(false);
|
|
1117
|
+
}
|
|
1118
|
+
}, []);
|
|
1119
|
+
const handleDrop = useCallback2((e) => {
|
|
1120
|
+
e.preventDefault();
|
|
1121
|
+
e.stopPropagation();
|
|
1122
|
+
dragCounterRef.current = 0;
|
|
1123
|
+
setIsDragging(false);
|
|
1124
|
+
if (!enabled || disabled) return;
|
|
1125
|
+
const files = e.dataTransfer.files;
|
|
1126
|
+
if (files.length > 0) {
|
|
1127
|
+
handleFiles(files);
|
|
1128
|
+
}
|
|
1129
|
+
}, [enabled, disabled, handleFiles]);
|
|
1130
|
+
const getDropZoneProps = useCallback2(() => ({
|
|
1131
|
+
onDragEnter: handleDragEnter,
|
|
1132
|
+
onDragOver: handleDragOver,
|
|
1133
|
+
onDragLeave: handleDragLeave,
|
|
1134
|
+
onDrop: handleDrop
|
|
1135
|
+
}), [handleDragEnter, handleDragOver, handleDragLeave, handleDrop]);
|
|
1136
|
+
const DropZoneOverlay = useMemo(() => {
|
|
1137
|
+
if (!isDragging || !enabled) return null;
|
|
1138
|
+
return /* @__PURE__ */ jsx6(
|
|
1139
|
+
"div",
|
|
1140
|
+
{
|
|
1141
|
+
style: {
|
|
1142
|
+
position: "absolute",
|
|
1143
|
+
inset: 0,
|
|
1144
|
+
background: theme.primaryColorTranslucent,
|
|
1145
|
+
border: `3px dashed ${theme.primaryColor}`,
|
|
1146
|
+
borderRadius: "inherit",
|
|
1147
|
+
display: "flex",
|
|
1148
|
+
alignItems: "center",
|
|
1149
|
+
justifyContent: "center",
|
|
1150
|
+
zIndex: 1010,
|
|
1151
|
+
pointerEvents: "none"
|
|
1152
|
+
},
|
|
1153
|
+
children: /* @__PURE__ */ jsx6(
|
|
1154
|
+
"div",
|
|
1155
|
+
{
|
|
1156
|
+
style: {
|
|
1157
|
+
background: theme.backgroundColor,
|
|
1158
|
+
padding: "16px 24px",
|
|
1159
|
+
borderRadius: "12px",
|
|
1160
|
+
boxShadow: theme.buttonShadow
|
|
1161
|
+
},
|
|
1162
|
+
children: /* @__PURE__ */ jsx6("span", { style: { color: theme.primaryColor, fontWeight: 600, fontSize: "16px" }, children: strings.fileUpload.dropFilesHere })
|
|
1163
|
+
}
|
|
1164
|
+
)
|
|
1165
|
+
}
|
|
1166
|
+
);
|
|
1167
|
+
}, [isDragging, enabled, theme, strings]);
|
|
1168
|
+
return {
|
|
1169
|
+
attachments,
|
|
1170
|
+
isDragging,
|
|
1171
|
+
fileError,
|
|
1172
|
+
enabled,
|
|
1173
|
+
maxFileSize,
|
|
1174
|
+
acceptedTypes,
|
|
1175
|
+
fileInputRef,
|
|
1176
|
+
handleFiles,
|
|
1177
|
+
removeAttachment,
|
|
1178
|
+
clearAttachments,
|
|
1179
|
+
openFilePicker,
|
|
1180
|
+
handleFileInputChange,
|
|
1181
|
+
handleDragEnter,
|
|
1182
|
+
handleDragOver,
|
|
1183
|
+
handleDragLeave,
|
|
1184
|
+
handleDrop,
|
|
1185
|
+
getDropZoneProps,
|
|
1186
|
+
DropZoneOverlay
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// src/hooks/useDropdownState.tsx
|
|
1191
|
+
import { useState as useState3, useCallback as useCallback3, useMemo as useMemo2 } from "react";
|
|
1192
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1193
|
+
function useDropdownState(options = {}) {
|
|
1194
|
+
const { backdropZIndex = 1002, initialOpen = false } = options;
|
|
1195
|
+
const [isOpen, setIsOpen] = useState3(initialOpen);
|
|
1196
|
+
const open = useCallback3(() => {
|
|
1197
|
+
setIsOpen(true);
|
|
1198
|
+
}, []);
|
|
1199
|
+
const close = useCallback3(() => {
|
|
1200
|
+
setIsOpen(false);
|
|
1201
|
+
}, []);
|
|
1202
|
+
const toggle = useCallback3(() => {
|
|
1203
|
+
setIsOpen((prev) => !prev);
|
|
1204
|
+
}, []);
|
|
1205
|
+
const Backdrop = useMemo2(() => {
|
|
1206
|
+
if (!isOpen) return null;
|
|
1207
|
+
return /* @__PURE__ */ jsx7(
|
|
1208
|
+
"div",
|
|
1209
|
+
{
|
|
1210
|
+
onClick: close,
|
|
1211
|
+
style: {
|
|
1212
|
+
position: "fixed",
|
|
1213
|
+
top: 0,
|
|
1214
|
+
left: 0,
|
|
1215
|
+
right: 0,
|
|
1216
|
+
bottom: 0,
|
|
1217
|
+
zIndex: backdropZIndex
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
);
|
|
1221
|
+
}, [isOpen, close, backdropZIndex]);
|
|
1222
|
+
return {
|
|
1223
|
+
isOpen,
|
|
1224
|
+
open,
|
|
1225
|
+
close,
|
|
1226
|
+
toggle,
|
|
1227
|
+
Backdrop
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// src/components/UseAIChatPanel.tsx
|
|
1232
|
+
import { Fragment, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1233
|
+
function getTextContent(content) {
|
|
1234
|
+
if (typeof content === "string") {
|
|
1235
|
+
return content;
|
|
1236
|
+
}
|
|
1237
|
+
return content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
1238
|
+
}
|
|
1239
|
+
function hasFileContent(content) {
|
|
1240
|
+
return Array.isArray(content) && content.some((part) => part.type === "file");
|
|
1241
|
+
}
|
|
1242
|
+
function UseAIChatPanel({
|
|
1243
|
+
onSendMessage,
|
|
1244
|
+
messages,
|
|
1245
|
+
loading,
|
|
1246
|
+
connected,
|
|
1247
|
+
streamingText = "",
|
|
1248
|
+
currentChatId,
|
|
1249
|
+
onNewChat,
|
|
1250
|
+
onLoadChat,
|
|
1251
|
+
onDeleteChat,
|
|
1252
|
+
onListChats,
|
|
1253
|
+
suggestions,
|
|
1254
|
+
availableAgents,
|
|
1255
|
+
defaultAgent,
|
|
1256
|
+
selectedAgent,
|
|
1257
|
+
onAgentChange,
|
|
1258
|
+
fileUploadConfig,
|
|
1259
|
+
commands = [],
|
|
1260
|
+
onSaveCommand,
|
|
1261
|
+
onRenameCommand,
|
|
1262
|
+
onDeleteCommand,
|
|
1263
|
+
closeButton
|
|
1264
|
+
}) {
|
|
1265
|
+
const strings = useStrings();
|
|
1266
|
+
const theme = useTheme();
|
|
1267
|
+
const [input, setInput] = useState4("");
|
|
1268
|
+
const chatHistoryDropdown = useDropdownState();
|
|
1269
|
+
const agentDropdown = useDropdownState();
|
|
1270
|
+
const [chatHistory, setChatHistory] = useState4([]);
|
|
1271
|
+
const messagesEndRef = useRef4(null);
|
|
1272
|
+
const [displayedSuggestions, setDisplayedSuggestions] = useState4([]);
|
|
1273
|
+
const [hoveredMessageId, setHoveredMessageId] = useState4(null);
|
|
1274
|
+
const {
|
|
1275
|
+
attachments,
|
|
1276
|
+
fileError,
|
|
1277
|
+
enabled: fileUploadEnabled,
|
|
1278
|
+
acceptedTypes,
|
|
1279
|
+
fileInputRef,
|
|
1280
|
+
removeAttachment,
|
|
1281
|
+
clearAttachments,
|
|
1282
|
+
openFilePicker,
|
|
1283
|
+
handleFileInputChange,
|
|
1284
|
+
getDropZoneProps,
|
|
1285
|
+
DropZoneOverlay
|
|
1286
|
+
} = useFileUpload({
|
|
1287
|
+
config: fileUploadConfig,
|
|
1288
|
+
disabled: loading,
|
|
1289
|
+
resetDependency: currentChatId
|
|
1290
|
+
});
|
|
1291
|
+
const slashCommands = useSlashCommands({
|
|
1292
|
+
commands,
|
|
1293
|
+
onCommandSelect: (text) => setInput(text),
|
|
1294
|
+
onSaveCommand,
|
|
1295
|
+
onRenameCommand,
|
|
1296
|
+
onDeleteCommand
|
|
1297
|
+
});
|
|
1298
|
+
useEffect4(() => {
|
|
1299
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
1300
|
+
}, [messages]);
|
|
1301
|
+
useEffect4(() => {
|
|
1302
|
+
if (!suggestions || suggestions.length === 0) {
|
|
1303
|
+
setDisplayedSuggestions([]);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
const shuffled = [...suggestions].sort(() => Math.random() - 0.5);
|
|
1307
|
+
setDisplayedSuggestions(shuffled.slice(0, 4));
|
|
1308
|
+
}, [messages.length, suggestions]);
|
|
1309
|
+
const handleSend = () => {
|
|
1310
|
+
const hasContent = input.trim() || attachments.length > 0;
|
|
1311
|
+
if (!hasContent || !connected || loading) return;
|
|
1312
|
+
onSendMessage(input, attachments.length > 0 ? attachments : void 0);
|
|
1313
|
+
setInput("");
|
|
1314
|
+
clearAttachments();
|
|
1315
|
+
slashCommands.closeAutocomplete();
|
|
1316
|
+
};
|
|
1317
|
+
const handleInputChange = (e) => {
|
|
1318
|
+
const value = e.target.value;
|
|
1319
|
+
setInput(value);
|
|
1320
|
+
slashCommands.handleInputChange(value);
|
|
1321
|
+
};
|
|
1322
|
+
const handleKeyDown = (e) => {
|
|
1323
|
+
if (slashCommands.handleKeyDown(e)) {
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing && !(e.keyCode === 229)) {
|
|
1327
|
+
e.preventDefault();
|
|
1328
|
+
handleSend();
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
const handleNewChat = async () => {
|
|
1332
|
+
if (onNewChat) {
|
|
1333
|
+
await onNewChat();
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
const handleDeleteChat = async () => {
|
|
1337
|
+
if (onDeleteChat && currentChatId && confirm(strings.header.deleteConfirm)) {
|
|
1338
|
+
await onDeleteChat(currentChatId);
|
|
1339
|
+
if (onNewChat) {
|
|
1340
|
+
await onNewChat();
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
const handleLoadChat = async (chatId) => {
|
|
1345
|
+
if (onLoadChat) {
|
|
1346
|
+
await onLoadChat(chatId);
|
|
1347
|
+
chatHistoryDropdown.close();
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
return /* @__PURE__ */ jsxs5(
|
|
1351
|
+
"div",
|
|
1352
|
+
{
|
|
1353
|
+
onClick: () => {
|
|
1354
|
+
slashCommands.cancelInlineSave();
|
|
1355
|
+
},
|
|
1356
|
+
...getDropZoneProps(),
|
|
1357
|
+
style: {
|
|
1358
|
+
width: "100%",
|
|
1359
|
+
height: "100%",
|
|
1360
|
+
background: theme.backgroundColor,
|
|
1361
|
+
display: "flex",
|
|
1362
|
+
flexDirection: "column",
|
|
1363
|
+
fontFamily: theme.fontFamily,
|
|
1364
|
+
position: "relative"
|
|
1365
|
+
},
|
|
1366
|
+
children: [
|
|
1367
|
+
DropZoneOverlay,
|
|
1368
|
+
/* @__PURE__ */ jsxs5(
|
|
1369
|
+
"div",
|
|
1370
|
+
{
|
|
1371
|
+
style: {
|
|
1372
|
+
padding: "12px 16px",
|
|
1373
|
+
borderBottom: `1px solid ${theme.borderColor}`,
|
|
1374
|
+
background: theme.backgroundColor,
|
|
1375
|
+
display: "flex",
|
|
1376
|
+
alignItems: "center",
|
|
1377
|
+
justifyContent: "space-between",
|
|
1378
|
+
gap: "12px"
|
|
1379
|
+
},
|
|
1380
|
+
children: [
|
|
1381
|
+
/* @__PURE__ */ jsx8("div", { style: { flex: 1, minWidth: 0, position: "relative" }, children: onListChats ? /* @__PURE__ */ jsxs5(
|
|
1382
|
+
"button",
|
|
1383
|
+
{
|
|
1384
|
+
"data-testid": "chat-history-dropdown-button",
|
|
1385
|
+
onClick: async () => {
|
|
1386
|
+
const chats = await onListChats();
|
|
1387
|
+
setChatHistory(chats);
|
|
1388
|
+
chatHistoryDropdown.toggle();
|
|
1389
|
+
},
|
|
1390
|
+
style: {
|
|
1391
|
+
background: "transparent",
|
|
1392
|
+
border: "none",
|
|
1393
|
+
padding: "6px 8px",
|
|
1394
|
+
cursor: "pointer",
|
|
1395
|
+
display: "flex",
|
|
1396
|
+
alignItems: "center",
|
|
1397
|
+
gap: "8px",
|
|
1398
|
+
fontSize: "14px",
|
|
1399
|
+
fontWeight: "600",
|
|
1400
|
+
color: theme.textColor,
|
|
1401
|
+
borderRadius: "6px",
|
|
1402
|
+
transition: "background 0.2s",
|
|
1403
|
+
width: "100%",
|
|
1404
|
+
textAlign: "left",
|
|
1405
|
+
overflow: "hidden"
|
|
1406
|
+
},
|
|
1407
|
+
onMouseEnter: (e) => {
|
|
1408
|
+
e.currentTarget.style.background = theme.hoverBackground;
|
|
1409
|
+
},
|
|
1410
|
+
onMouseLeave: (e) => {
|
|
1411
|
+
e.currentTarget.style.background = "transparent";
|
|
1412
|
+
},
|
|
1413
|
+
children: [
|
|
1414
|
+
/* @__PURE__ */ jsx8("span", { style: {
|
|
1415
|
+
overflow: "hidden",
|
|
1416
|
+
textOverflow: "ellipsis",
|
|
1417
|
+
whiteSpace: "nowrap",
|
|
1418
|
+
flex: 1,
|
|
1419
|
+
minWidth: 0
|
|
1420
|
+
}, children: (() => {
|
|
1421
|
+
if (messages.length > 0) {
|
|
1422
|
+
const firstUserMsg = messages.find((m) => m.role === "user");
|
|
1423
|
+
if (firstUserMsg) {
|
|
1424
|
+
const textContent = getTextContent(firstUserMsg.content);
|
|
1425
|
+
const maxLength = 30;
|
|
1426
|
+
return textContent.length > maxLength ? textContent.substring(0, maxLength) + "..." : textContent || strings.header.newChat;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
return strings.header.newChat;
|
|
1430
|
+
})() }),
|
|
1431
|
+
/* @__PURE__ */ jsx8("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx8("path", { d: "M3 4.5L6 7.5L9 4.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) })
|
|
1432
|
+
]
|
|
1433
|
+
}
|
|
1434
|
+
) : /* @__PURE__ */ jsx8("div", { style: { fontSize: "14px", fontWeight: "600", color: theme.textColor, padding: "6px 8px" }, children: strings.header.aiAssistant }) }),
|
|
1435
|
+
/* @__PURE__ */ jsxs5("div", { style: { display: "flex", alignItems: "center", gap: "4px" }, children: [
|
|
1436
|
+
availableAgents && availableAgents.length > 1 && onAgentChange && /* @__PURE__ */ jsxs5("div", { style: { position: "relative" }, children: [
|
|
1437
|
+
/* @__PURE__ */ jsxs5(
|
|
1438
|
+
"button",
|
|
1439
|
+
{
|
|
1440
|
+
"data-testid": "agent-selector",
|
|
1441
|
+
onClick: agentDropdown.toggle,
|
|
1442
|
+
style: {
|
|
1443
|
+
background: "transparent",
|
|
1444
|
+
border: "none",
|
|
1445
|
+
padding: "6px 8px",
|
|
1446
|
+
cursor: "pointer",
|
|
1447
|
+
display: "flex",
|
|
1448
|
+
alignItems: "center",
|
|
1449
|
+
gap: "4px",
|
|
1450
|
+
fontSize: "13px",
|
|
1451
|
+
fontWeight: "500",
|
|
1452
|
+
color: theme.secondaryTextColor,
|
|
1453
|
+
borderRadius: "6px",
|
|
1454
|
+
transition: "all 0.2s"
|
|
1455
|
+
},
|
|
1456
|
+
onMouseEnter: (e) => {
|
|
1457
|
+
e.currentTarget.style.background = theme.hoverBackground;
|
|
1458
|
+
e.currentTarget.style.color = theme.textColor;
|
|
1459
|
+
},
|
|
1460
|
+
onMouseLeave: (e) => {
|
|
1461
|
+
e.currentTarget.style.background = "transparent";
|
|
1462
|
+
e.currentTarget.style.color = theme.secondaryTextColor;
|
|
1463
|
+
},
|
|
1464
|
+
title: "Select AI model",
|
|
1465
|
+
children: [
|
|
1466
|
+
/* @__PURE__ */ jsx8("span", { style: {
|
|
1467
|
+
overflow: "hidden",
|
|
1468
|
+
textOverflow: "ellipsis",
|
|
1469
|
+
whiteSpace: "nowrap",
|
|
1470
|
+
maxWidth: "120px"
|
|
1471
|
+
}, children: (() => {
|
|
1472
|
+
const agent = availableAgents.find((a) => a.id === (selectedAgent ?? defaultAgent));
|
|
1473
|
+
return agent?.name || "AI";
|
|
1474
|
+
})() }),
|
|
1475
|
+
/* @__PURE__ */ jsx8("svg", { width: "10", height: "10", viewBox: "0 0 12 12", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx8("path", { d: "M3 4.5L6 7.5L9 4.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) })
|
|
1476
|
+
]
|
|
1477
|
+
}
|
|
1478
|
+
),
|
|
1479
|
+
agentDropdown.isOpen && /* @__PURE__ */ jsx8(
|
|
1480
|
+
"div",
|
|
1481
|
+
{
|
|
1482
|
+
style: {
|
|
1483
|
+
position: "absolute",
|
|
1484
|
+
top: "100%",
|
|
1485
|
+
left: "50%",
|
|
1486
|
+
transform: "translateX(-50%)",
|
|
1487
|
+
marginTop: "4px",
|
|
1488
|
+
minWidth: "180px",
|
|
1489
|
+
maxWidth: "280px",
|
|
1490
|
+
background: theme.backgroundColor,
|
|
1491
|
+
borderRadius: "8px",
|
|
1492
|
+
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.15)",
|
|
1493
|
+
zIndex: 1003,
|
|
1494
|
+
overflow: "hidden",
|
|
1495
|
+
padding: "4px"
|
|
1496
|
+
},
|
|
1497
|
+
children: availableAgents.map((agent) => {
|
|
1498
|
+
const isSelected = agent.id === (selectedAgent ?? defaultAgent);
|
|
1499
|
+
return /* @__PURE__ */ jsxs5(
|
|
1500
|
+
"div",
|
|
1501
|
+
{
|
|
1502
|
+
"data-testid": "agent-option",
|
|
1503
|
+
onClick: () => {
|
|
1504
|
+
onAgentChange(agent.id === defaultAgent ? null : agent.id);
|
|
1505
|
+
agentDropdown.close();
|
|
1506
|
+
},
|
|
1507
|
+
style: {
|
|
1508
|
+
padding: "8px 12px",
|
|
1509
|
+
background: isSelected ? theme.activeBackground : "transparent",
|
|
1510
|
+
borderRadius: "6px",
|
|
1511
|
+
cursor: "pointer",
|
|
1512
|
+
transition: "background 0.15s",
|
|
1513
|
+
display: "flex",
|
|
1514
|
+
alignItems: "center",
|
|
1515
|
+
justifyContent: "space-between",
|
|
1516
|
+
gap: "8px"
|
|
1517
|
+
},
|
|
1518
|
+
onMouseEnter: (e) => {
|
|
1519
|
+
if (!isSelected) {
|
|
1520
|
+
e.currentTarget.style.background = theme.hoverBackground;
|
|
1521
|
+
}
|
|
1522
|
+
},
|
|
1523
|
+
onMouseLeave: (e) => {
|
|
1524
|
+
if (!isSelected) {
|
|
1525
|
+
e.currentTarget.style.background = "transparent";
|
|
1526
|
+
}
|
|
1527
|
+
},
|
|
1528
|
+
children: [
|
|
1529
|
+
/* @__PURE__ */ jsxs5("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
1530
|
+
/* @__PURE__ */ jsx8("div", { style: {
|
|
1531
|
+
fontSize: "13px",
|
|
1532
|
+
fontWeight: isSelected ? "600" : "500",
|
|
1533
|
+
color: isSelected ? theme.primaryColor : theme.textColor
|
|
1534
|
+
}, children: agent.name }),
|
|
1535
|
+
agent.annotation && /* @__PURE__ */ jsx8("div", { style: {
|
|
1536
|
+
fontSize: "11px",
|
|
1537
|
+
color: theme.secondaryTextColor,
|
|
1538
|
+
marginTop: "2px",
|
|
1539
|
+
overflow: "hidden",
|
|
1540
|
+
textOverflow: "ellipsis",
|
|
1541
|
+
whiteSpace: "nowrap"
|
|
1542
|
+
}, children: agent.annotation })
|
|
1543
|
+
] }),
|
|
1544
|
+
isSelected && /* @__PURE__ */ jsx8("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx8("path", { d: "M2 7L5.5 10.5L12 4", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
|
|
1545
|
+
]
|
|
1546
|
+
},
|
|
1547
|
+
agent.id
|
|
1548
|
+
);
|
|
1549
|
+
})
|
|
1550
|
+
}
|
|
1551
|
+
)
|
|
1552
|
+
] }),
|
|
1553
|
+
onNewChat && /* @__PURE__ */ jsx8(
|
|
1554
|
+
"button",
|
|
1555
|
+
{
|
|
1556
|
+
"data-testid": "new-chat-button",
|
|
1557
|
+
onClick: handleNewChat,
|
|
1558
|
+
style: {
|
|
1559
|
+
background: "transparent",
|
|
1560
|
+
border: "none",
|
|
1561
|
+
borderRadius: "6px",
|
|
1562
|
+
padding: "6px 8px",
|
|
1563
|
+
color: theme.secondaryTextColor,
|
|
1564
|
+
fontSize: "13px",
|
|
1565
|
+
cursor: "pointer",
|
|
1566
|
+
transition: "all 0.2s",
|
|
1567
|
+
display: "flex",
|
|
1568
|
+
alignItems: "center",
|
|
1569
|
+
gap: "4px"
|
|
1570
|
+
},
|
|
1571
|
+
onMouseEnter: (e) => {
|
|
1572
|
+
e.currentTarget.style.background = theme.hoverBackground;
|
|
1573
|
+
e.currentTarget.style.color = theme.textColor;
|
|
1574
|
+
},
|
|
1575
|
+
onMouseLeave: (e) => {
|
|
1576
|
+
e.currentTarget.style.background = "transparent";
|
|
1577
|
+
e.currentTarget.style.color = theme.secondaryTextColor;
|
|
1578
|
+
},
|
|
1579
|
+
title: strings.header.newChat,
|
|
1580
|
+
children: /* @__PURE__ */ jsx8("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx8("path", { d: "M8 3.5V12.5M3.5 8H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) })
|
|
1581
|
+
}
|
|
1582
|
+
),
|
|
1583
|
+
onDeleteChat && messages.length > 0 && /* @__PURE__ */ jsx8(
|
|
1584
|
+
"button",
|
|
1585
|
+
{
|
|
1586
|
+
"data-testid": "delete-chat-button",
|
|
1587
|
+
onClick: handleDeleteChat,
|
|
1588
|
+
style: {
|
|
1589
|
+
background: "transparent",
|
|
1590
|
+
border: "none",
|
|
1591
|
+
borderRadius: "6px",
|
|
1592
|
+
padding: "6px 8px",
|
|
1593
|
+
color: theme.secondaryTextColor,
|
|
1594
|
+
fontSize: "13px",
|
|
1595
|
+
cursor: "pointer",
|
|
1596
|
+
transition: "all 0.2s"
|
|
1597
|
+
},
|
|
1598
|
+
onMouseEnter: (e) => {
|
|
1599
|
+
e.currentTarget.style.background = theme.hoverBackground;
|
|
1600
|
+
e.currentTarget.style.color = theme.textColor;
|
|
1601
|
+
},
|
|
1602
|
+
onMouseLeave: (e) => {
|
|
1603
|
+
e.currentTarget.style.background = "transparent";
|
|
1604
|
+
e.currentTarget.style.color = theme.secondaryTextColor;
|
|
1605
|
+
},
|
|
1606
|
+
title: strings.header.deleteChat,
|
|
1607
|
+
children: /* @__PURE__ */ jsx8("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx8("path", { d: "M2 4H14M6.5 7V11M9.5 7V11M3 4L4 13C4 13.5304 4.21071 14.0391 4.58579 14.4142C4.96086 14.7893 5.46957 15 6 15H10C10.5304 15 11.0391 14.7893 11.4142 14.4142C11.7893 14.0391 12 13.5304 12 13L13 4M5.5 4V2.5C5.5 2.23478 5.60536 1.98043 5.79289 1.79289C5.98043 1.60536 6.23478 1.5 6.5 1.5H9.5C9.76522 1.5 10.0196 1.60536 10.2071 1.79289C10.3946 1.98043 10.5 2.23478 10.5 2.5V4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) })
|
|
1608
|
+
}
|
|
1609
|
+
),
|
|
1610
|
+
closeButton
|
|
1611
|
+
] })
|
|
1612
|
+
]
|
|
1613
|
+
}
|
|
1614
|
+
),
|
|
1615
|
+
chatHistoryDropdown.isOpen && onListChats && /* @__PURE__ */ jsx8(
|
|
1616
|
+
"div",
|
|
1617
|
+
{
|
|
1618
|
+
style: {
|
|
1619
|
+
position: "absolute",
|
|
1620
|
+
top: "60px",
|
|
1621
|
+
left: "16px",
|
|
1622
|
+
width: "320px",
|
|
1623
|
+
maxHeight: "400px",
|
|
1624
|
+
background: theme.backgroundColor,
|
|
1625
|
+
borderRadius: "8px",
|
|
1626
|
+
boxShadow: theme.panelShadow,
|
|
1627
|
+
zIndex: 1003,
|
|
1628
|
+
display: "flex",
|
|
1629
|
+
flexDirection: "column",
|
|
1630
|
+
overflow: "hidden"
|
|
1631
|
+
},
|
|
1632
|
+
children: /* @__PURE__ */ jsx8(
|
|
1633
|
+
"div",
|
|
1634
|
+
{
|
|
1635
|
+
style: {
|
|
1636
|
+
flex: 1,
|
|
1637
|
+
overflowY: "auto",
|
|
1638
|
+
padding: "8px"
|
|
1639
|
+
},
|
|
1640
|
+
children: chatHistory.length === 0 ? /* @__PURE__ */ jsx8(
|
|
1641
|
+
"div",
|
|
1642
|
+
{
|
|
1643
|
+
style: {
|
|
1644
|
+
textAlign: "center",
|
|
1645
|
+
color: theme.secondaryTextColor,
|
|
1646
|
+
padding: "32px 16px",
|
|
1647
|
+
fontSize: "13px"
|
|
1648
|
+
},
|
|
1649
|
+
children: /* @__PURE__ */ jsx8("p", { style: { margin: 0 }, children: strings.chatHistory.noChatHistory })
|
|
1650
|
+
}
|
|
1651
|
+
) : chatHistory.map((chat) => /* @__PURE__ */ jsxs5(
|
|
1652
|
+
"div",
|
|
1653
|
+
{
|
|
1654
|
+
"data-testid": "chat-history-item",
|
|
1655
|
+
onClick: () => handleLoadChat(chat.id),
|
|
1656
|
+
style: {
|
|
1657
|
+
padding: "10px 12px",
|
|
1658
|
+
marginBottom: "4px",
|
|
1659
|
+
background: currentChatId === chat.id ? theme.activeBackground : "transparent",
|
|
1660
|
+
borderRadius: "6px",
|
|
1661
|
+
cursor: "pointer",
|
|
1662
|
+
transition: "background 0.15s"
|
|
1663
|
+
},
|
|
1664
|
+
onMouseEnter: (e) => {
|
|
1665
|
+
if (currentChatId !== chat.id) {
|
|
1666
|
+
e.currentTarget.style.background = theme.hoverBackground;
|
|
1667
|
+
}
|
|
1668
|
+
},
|
|
1669
|
+
onMouseLeave: (e) => {
|
|
1670
|
+
if (currentChatId !== chat.id) {
|
|
1671
|
+
e.currentTarget.style.background = "transparent";
|
|
1672
|
+
}
|
|
1673
|
+
},
|
|
1674
|
+
children: [
|
|
1675
|
+
/* @__PURE__ */ jsx8("div", { style: { fontSize: "13px", fontWeight: "500", color: theme.textColor, marginBottom: "4px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: chat.title || strings.header.newChat }),
|
|
1676
|
+
/* @__PURE__ */ jsxs5("div", { style: { fontSize: "11px", color: theme.secondaryTextColor }, children: [
|
|
1677
|
+
new Date(chat.updatedAt).toLocaleDateString([], { month: "short", day: "numeric" }),
|
|
1678
|
+
currentChatId === chat.id && /* @__PURE__ */ jsxs5("span", { style: {
|
|
1679
|
+
marginLeft: "8px",
|
|
1680
|
+
color: theme.primaryColor,
|
|
1681
|
+
fontWeight: "600"
|
|
1682
|
+
}, children: [
|
|
1683
|
+
"\u2022 ",
|
|
1684
|
+
strings.chatHistory.active
|
|
1685
|
+
] })
|
|
1686
|
+
] })
|
|
1687
|
+
]
|
|
1688
|
+
},
|
|
1689
|
+
chat.id
|
|
1690
|
+
))
|
|
1691
|
+
}
|
|
1692
|
+
)
|
|
1693
|
+
}
|
|
1694
|
+
),
|
|
1695
|
+
chatHistoryDropdown.Backdrop,
|
|
1696
|
+
agentDropdown.Backdrop,
|
|
1697
|
+
/* @__PURE__ */ jsxs5(
|
|
1698
|
+
"div",
|
|
1699
|
+
{
|
|
1700
|
+
style: {
|
|
1701
|
+
flex: 1,
|
|
1702
|
+
overflowY: "auto",
|
|
1703
|
+
padding: "16px",
|
|
1704
|
+
display: "flex",
|
|
1705
|
+
flexDirection: "column",
|
|
1706
|
+
gap: "12px"
|
|
1707
|
+
},
|
|
1708
|
+
children: [
|
|
1709
|
+
messages.length === 0 && /* @__PURE__ */ jsxs5(
|
|
1710
|
+
"div",
|
|
1711
|
+
{
|
|
1712
|
+
style: {
|
|
1713
|
+
display: "flex",
|
|
1714
|
+
flexDirection: "column",
|
|
1715
|
+
alignItems: "center",
|
|
1716
|
+
padding: "40px 20px",
|
|
1717
|
+
gap: "20px"
|
|
1718
|
+
},
|
|
1719
|
+
children: [
|
|
1720
|
+
/* @__PURE__ */ jsxs5("div", { style: { textAlign: "center", color: theme.secondaryTextColor, fontSize: "14px" }, children: [
|
|
1721
|
+
/* @__PURE__ */ jsx8("p", { style: { margin: 0, fontSize: "32px", marginBottom: "12px" }, children: "\u{1F4AC}" }),
|
|
1722
|
+
/* @__PURE__ */ jsx8("p", { style: { margin: 0 }, children: strings.emptyChat.startConversation }),
|
|
1723
|
+
/* @__PURE__ */ jsx8("p", { style: { margin: "8px 0 0", fontSize: "12px" }, children: strings.emptyChat.askMeToHelp })
|
|
1724
|
+
] }),
|
|
1725
|
+
displayedSuggestions.length > 0 && /* @__PURE__ */ jsx8(
|
|
1726
|
+
"div",
|
|
1727
|
+
{
|
|
1728
|
+
style: {
|
|
1729
|
+
display: "grid",
|
|
1730
|
+
gridTemplateColumns: "repeat(2, 1fr)",
|
|
1731
|
+
gap: "8px",
|
|
1732
|
+
width: "100%",
|
|
1733
|
+
maxWidth: "320px"
|
|
1734
|
+
},
|
|
1735
|
+
children: displayedSuggestions.map((suggestion, index) => /* @__PURE__ */ jsx8(
|
|
1736
|
+
"button",
|
|
1737
|
+
{
|
|
1738
|
+
"data-testid": "chat-suggestion-button",
|
|
1739
|
+
onClick: () => {
|
|
1740
|
+
if (connected && !loading) {
|
|
1741
|
+
onSendMessage(suggestion);
|
|
1742
|
+
}
|
|
1743
|
+
},
|
|
1744
|
+
disabled: !connected || loading,
|
|
1745
|
+
style: {
|
|
1746
|
+
padding: "10px 14px",
|
|
1747
|
+
background: theme.backgroundColor,
|
|
1748
|
+
border: `1px solid ${theme.borderColor}`,
|
|
1749
|
+
borderRadius: "8px",
|
|
1750
|
+
fontSize: "13px",
|
|
1751
|
+
color: theme.textColor,
|
|
1752
|
+
cursor: connected && !loading ? "pointer" : "not-allowed",
|
|
1753
|
+
textAlign: "left",
|
|
1754
|
+
transition: "all 0.2s",
|
|
1755
|
+
lineHeight: "1.4",
|
|
1756
|
+
opacity: connected && !loading ? 1 : 0.5
|
|
1757
|
+
},
|
|
1758
|
+
onMouseEnter: (e) => {
|
|
1759
|
+
if (connected && !loading) {
|
|
1760
|
+
e.currentTarget.style.background = theme.hoverBackground;
|
|
1761
|
+
e.currentTarget.style.transform = "translateY(-1px)";
|
|
1762
|
+
e.currentTarget.style.boxShadow = "0 2px 8px rgba(0, 0, 0, 0.08)";
|
|
1763
|
+
}
|
|
1764
|
+
},
|
|
1765
|
+
onMouseLeave: (e) => {
|
|
1766
|
+
e.currentTarget.style.background = theme.backgroundColor;
|
|
1767
|
+
e.currentTarget.style.transform = "translateY(0)";
|
|
1768
|
+
e.currentTarget.style.boxShadow = "none";
|
|
1769
|
+
},
|
|
1770
|
+
children: suggestion
|
|
1771
|
+
},
|
|
1772
|
+
index
|
|
1773
|
+
))
|
|
1774
|
+
}
|
|
1775
|
+
)
|
|
1776
|
+
]
|
|
1777
|
+
}
|
|
1778
|
+
),
|
|
1779
|
+
messages.map((message) => /* @__PURE__ */ jsxs5(
|
|
1780
|
+
"div",
|
|
1781
|
+
{
|
|
1782
|
+
"data-testid": `chat-message-${message.role}`,
|
|
1783
|
+
className: `chat-message chat-message-${message.role}`,
|
|
1784
|
+
style: {
|
|
1785
|
+
display: "flex",
|
|
1786
|
+
flexDirection: "column",
|
|
1787
|
+
alignItems: message.role === "user" ? "flex-end" : "flex-start"
|
|
1788
|
+
},
|
|
1789
|
+
onMouseEnter: () => message.role === "user" && setHoveredMessageId(message.id),
|
|
1790
|
+
onMouseLeave: () => setHoveredMessageId(null),
|
|
1791
|
+
children: [
|
|
1792
|
+
/* @__PURE__ */ jsxs5(
|
|
1793
|
+
"div",
|
|
1794
|
+
{
|
|
1795
|
+
style: {
|
|
1796
|
+
position: "relative",
|
|
1797
|
+
maxWidth: "80%"
|
|
1798
|
+
},
|
|
1799
|
+
children: [
|
|
1800
|
+
message.role === "user" && hoveredMessageId === message.id && onSaveCommand && !slashCommands.isSavingCommand(message.id) && /* @__PURE__ */ jsx8(
|
|
1801
|
+
"button",
|
|
1802
|
+
{
|
|
1803
|
+
"data-testid": "save-command-button",
|
|
1804
|
+
onClick: (e) => {
|
|
1805
|
+
e.stopPropagation();
|
|
1806
|
+
const messageText = getTextContent(message.content);
|
|
1807
|
+
slashCommands.startSavingCommand(message.id, messageText);
|
|
1808
|
+
},
|
|
1809
|
+
title: "Save as slash command",
|
|
1810
|
+
style: {
|
|
1811
|
+
position: "absolute",
|
|
1812
|
+
top: "-8px",
|
|
1813
|
+
right: "-8px",
|
|
1814
|
+
width: "24px",
|
|
1815
|
+
height: "24px",
|
|
1816
|
+
borderRadius: "50%",
|
|
1817
|
+
border: "none",
|
|
1818
|
+
background: theme.backgroundColor,
|
|
1819
|
+
boxShadow: "0 2px 6px rgba(0, 0, 0, 0.15)",
|
|
1820
|
+
cursor: "pointer",
|
|
1821
|
+
display: "flex",
|
|
1822
|
+
alignItems: "center",
|
|
1823
|
+
justifyContent: "center",
|
|
1824
|
+
color: theme.primaryColor,
|
|
1825
|
+
transition: "all 0.15s",
|
|
1826
|
+
zIndex: 10
|
|
1827
|
+
},
|
|
1828
|
+
onMouseEnter: (e) => {
|
|
1829
|
+
e.currentTarget.style.transform = "scale(1.1)";
|
|
1830
|
+
e.currentTarget.style.boxShadow = "0 3px 8px rgba(0, 0, 0, 0.2)";
|
|
1831
|
+
},
|
|
1832
|
+
onMouseLeave: (e) => {
|
|
1833
|
+
e.currentTarget.style.transform = "scale(1)";
|
|
1834
|
+
e.currentTarget.style.boxShadow = "0 2px 6px rgba(0, 0, 0, 0.15)";
|
|
1835
|
+
},
|
|
1836
|
+
children: /* @__PURE__ */ jsxs5("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1837
|
+
/* @__PURE__ */ jsx8("path", { d: "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" }),
|
|
1838
|
+
/* @__PURE__ */ jsx8("polyline", { points: "17 21 17 13 7 13 7 21" }),
|
|
1839
|
+
/* @__PURE__ */ jsx8("polyline", { points: "7 3 7 8 15 8" })
|
|
1840
|
+
] })
|
|
1841
|
+
}
|
|
1842
|
+
),
|
|
1843
|
+
/* @__PURE__ */ jsxs5(
|
|
1844
|
+
"div",
|
|
1845
|
+
{
|
|
1846
|
+
"data-testid": "chat-message-content",
|
|
1847
|
+
className: `chat-message-content${message.role === "assistant" ? " markdown-content" : ""}`,
|
|
1848
|
+
style: {
|
|
1849
|
+
padding: "10px 14px",
|
|
1850
|
+
borderRadius: slashCommands.isSavingCommand(message.id) ? "12px 12px 0 0" : "12px",
|
|
1851
|
+
background: message.displayMode === "error" ? theme.errorBackground : message.role === "user" ? theme.primaryGradient : theme.assistantMessageBackground,
|
|
1852
|
+
color: message.displayMode === "error" ? theme.errorTextColor : message.role === "user" ? "white" : theme.textColor,
|
|
1853
|
+
fontSize: "14px",
|
|
1854
|
+
lineHeight: "1.5",
|
|
1855
|
+
wordWrap: "break-word"
|
|
1856
|
+
},
|
|
1857
|
+
children: [
|
|
1858
|
+
message.role === "user" && hasFileContent(message.content) && /* @__PURE__ */ jsx8("div", { style: { display: "flex", flexWrap: "wrap", gap: "6px", marginBottom: "8px" }, children: message.content.filter((part) => part.type === "file").map((part, idx) => /* @__PURE__ */ jsx8(
|
|
1859
|
+
FilePlaceholder,
|
|
1860
|
+
{
|
|
1861
|
+
name: part.file.name,
|
|
1862
|
+
size: part.file.size
|
|
1863
|
+
},
|
|
1864
|
+
idx
|
|
1865
|
+
)) }),
|
|
1866
|
+
message.role === "assistant" ? /* @__PURE__ */ jsx8(MarkdownContent, { content: getTextContent(message.content) }) : getTextContent(message.content)
|
|
1867
|
+
]
|
|
1868
|
+
}
|
|
1869
|
+
),
|
|
1870
|
+
slashCommands.renderInlineSaveUI({
|
|
1871
|
+
messageId: message.id,
|
|
1872
|
+
messageText: getTextContent(message.content)
|
|
1873
|
+
})
|
|
1874
|
+
]
|
|
1875
|
+
}
|
|
1876
|
+
),
|
|
1877
|
+
/* @__PURE__ */ jsx8(
|
|
1878
|
+
"div",
|
|
1879
|
+
{
|
|
1880
|
+
style: {
|
|
1881
|
+
fontSize: "11px",
|
|
1882
|
+
color: theme.secondaryTextColor,
|
|
1883
|
+
marginTop: "4px",
|
|
1884
|
+
padding: "0 4px"
|
|
1885
|
+
},
|
|
1886
|
+
children: message.timestamp.toLocaleTimeString([], {
|
|
1887
|
+
hour: "2-digit",
|
|
1888
|
+
minute: "2-digit"
|
|
1889
|
+
})
|
|
1890
|
+
}
|
|
1891
|
+
)
|
|
1892
|
+
]
|
|
1893
|
+
},
|
|
1894
|
+
message.id
|
|
1895
|
+
)),
|
|
1896
|
+
loading && /* @__PURE__ */ jsx8(
|
|
1897
|
+
"div",
|
|
1898
|
+
{
|
|
1899
|
+
style: {
|
|
1900
|
+
display: "flex",
|
|
1901
|
+
alignItems: "flex-start"
|
|
1902
|
+
},
|
|
1903
|
+
children: /* @__PURE__ */ jsx8(
|
|
1904
|
+
"div",
|
|
1905
|
+
{
|
|
1906
|
+
className: "markdown-content",
|
|
1907
|
+
style: {
|
|
1908
|
+
padding: "10px 14px",
|
|
1909
|
+
borderRadius: "12px",
|
|
1910
|
+
background: theme.assistantMessageBackground,
|
|
1911
|
+
fontSize: "14px",
|
|
1912
|
+
lineHeight: "1.5",
|
|
1913
|
+
color: theme.textColor,
|
|
1914
|
+
maxWidth: "80%"
|
|
1915
|
+
},
|
|
1916
|
+
children: streamingText ? /* @__PURE__ */ jsx8(MarkdownContent, { content: streamingText }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1917
|
+
/* @__PURE__ */ jsx8("span", { style: { opacity: 0.6 }, children: strings.input.thinking }),
|
|
1918
|
+
/* @__PURE__ */ jsx8("span", { className: "dots", style: { marginLeft: "4px" }, children: "..." })
|
|
1919
|
+
] })
|
|
1920
|
+
}
|
|
1921
|
+
)
|
|
1922
|
+
}
|
|
1923
|
+
),
|
|
1924
|
+
/* @__PURE__ */ jsx8("div", { ref: messagesEndRef })
|
|
1925
|
+
]
|
|
1926
|
+
}
|
|
1927
|
+
),
|
|
1928
|
+
/* @__PURE__ */ jsxs5(
|
|
1929
|
+
"div",
|
|
1930
|
+
{
|
|
1931
|
+
style: {
|
|
1932
|
+
padding: "16px",
|
|
1933
|
+
borderTop: `1px solid ${theme.borderColor}`
|
|
1934
|
+
},
|
|
1935
|
+
children: [
|
|
1936
|
+
fileError && /* @__PURE__ */ jsx8(
|
|
1937
|
+
"div",
|
|
1938
|
+
{
|
|
1939
|
+
"data-testid": "file-error",
|
|
1940
|
+
style: {
|
|
1941
|
+
marginBottom: "8px",
|
|
1942
|
+
padding: "8px 12px",
|
|
1943
|
+
background: theme.errorBackground,
|
|
1944
|
+
color: theme.errorTextColor,
|
|
1945
|
+
borderRadius: "6px",
|
|
1946
|
+
fontSize: "13px"
|
|
1947
|
+
},
|
|
1948
|
+
children: fileError
|
|
1949
|
+
}
|
|
1950
|
+
),
|
|
1951
|
+
attachments.length > 0 && /* @__PURE__ */ jsx8(
|
|
1952
|
+
"div",
|
|
1953
|
+
{
|
|
1954
|
+
"data-testid": "file-attachments",
|
|
1955
|
+
style: {
|
|
1956
|
+
display: "flex",
|
|
1957
|
+
flexWrap: "wrap",
|
|
1958
|
+
gap: "8px",
|
|
1959
|
+
marginBottom: "8px"
|
|
1960
|
+
},
|
|
1961
|
+
children: attachments.map((attachment) => /* @__PURE__ */ jsx8(
|
|
1962
|
+
FileChip,
|
|
1963
|
+
{
|
|
1964
|
+
attachment,
|
|
1965
|
+
onRemove: () => removeAttachment(attachment.id),
|
|
1966
|
+
disabled: loading
|
|
1967
|
+
},
|
|
1968
|
+
attachment.id
|
|
1969
|
+
))
|
|
1970
|
+
}
|
|
1971
|
+
),
|
|
1972
|
+
/* @__PURE__ */ jsxs5(
|
|
1973
|
+
"div",
|
|
1974
|
+
{
|
|
1975
|
+
style: {
|
|
1976
|
+
display: "flex",
|
|
1977
|
+
gap: "8px",
|
|
1978
|
+
position: "relative"
|
|
1979
|
+
},
|
|
1980
|
+
children: [
|
|
1981
|
+
slashCommands.AutocompleteComponent,
|
|
1982
|
+
/* @__PURE__ */ jsx8(
|
|
1983
|
+
"input",
|
|
1984
|
+
{
|
|
1985
|
+
ref: fileInputRef,
|
|
1986
|
+
type: "file",
|
|
1987
|
+
multiple: true,
|
|
1988
|
+
style: { display: "none" },
|
|
1989
|
+
onChange: handleFileInputChange,
|
|
1990
|
+
accept: acceptedTypes?.join(",")
|
|
1991
|
+
}
|
|
1992
|
+
),
|
|
1993
|
+
/* @__PURE__ */ jsxs5(
|
|
1994
|
+
"div",
|
|
1995
|
+
{
|
|
1996
|
+
style: {
|
|
1997
|
+
flex: 1,
|
|
1998
|
+
display: "flex",
|
|
1999
|
+
alignItems: "center",
|
|
2000
|
+
border: `1px solid ${theme.borderColor}`,
|
|
2001
|
+
borderRadius: "8px",
|
|
2002
|
+
background: theme.backgroundColor,
|
|
2003
|
+
overflow: "hidden"
|
|
2004
|
+
},
|
|
2005
|
+
children: [
|
|
2006
|
+
/* @__PURE__ */ jsx8(
|
|
2007
|
+
"textarea",
|
|
2008
|
+
{
|
|
2009
|
+
"data-testid": "chat-input",
|
|
2010
|
+
className: "chat-input",
|
|
2011
|
+
value: input,
|
|
2012
|
+
onChange: handleInputChange,
|
|
2013
|
+
onKeyDown: handleKeyDown,
|
|
2014
|
+
placeholder: connected ? strings.input.placeholder : strings.input.connectingPlaceholder,
|
|
2015
|
+
disabled: !connected || loading,
|
|
2016
|
+
style: {
|
|
2017
|
+
flex: 1,
|
|
2018
|
+
padding: "10px 12px",
|
|
2019
|
+
border: "none",
|
|
2020
|
+
fontSize: "14px",
|
|
2021
|
+
resize: "none",
|
|
2022
|
+
minHeight: "44px",
|
|
2023
|
+
maxHeight: "120px",
|
|
2024
|
+
fontFamily: "inherit",
|
|
2025
|
+
outline: "none",
|
|
2026
|
+
background: "transparent"
|
|
2027
|
+
},
|
|
2028
|
+
rows: 1
|
|
2029
|
+
}
|
|
2030
|
+
),
|
|
2031
|
+
fileUploadEnabled && /* @__PURE__ */ jsx8(
|
|
2032
|
+
"button",
|
|
2033
|
+
{
|
|
2034
|
+
"data-testid": "file-picker-button",
|
|
2035
|
+
onClick: openFilePicker,
|
|
2036
|
+
disabled: !connected || loading,
|
|
2037
|
+
style: {
|
|
2038
|
+
padding: "6px",
|
|
2039
|
+
marginRight: "6px",
|
|
2040
|
+
background: "transparent",
|
|
2041
|
+
border: "none",
|
|
2042
|
+
borderRadius: "6px",
|
|
2043
|
+
cursor: connected && !loading ? "pointer" : "not-allowed",
|
|
2044
|
+
color: theme.secondaryTextColor,
|
|
2045
|
+
display: "flex",
|
|
2046
|
+
alignItems: "center",
|
|
2047
|
+
justifyContent: "center",
|
|
2048
|
+
transition: "all 0.15s",
|
|
2049
|
+
opacity: connected && !loading ? 1 : 0.5
|
|
2050
|
+
},
|
|
2051
|
+
onMouseEnter: (e) => {
|
|
2052
|
+
if (connected && !loading) {
|
|
2053
|
+
e.currentTarget.style.color = theme.primaryColor;
|
|
2054
|
+
e.currentTarget.style.background = theme.activeBackground;
|
|
2055
|
+
}
|
|
2056
|
+
},
|
|
2057
|
+
onMouseLeave: (e) => {
|
|
2058
|
+
e.currentTarget.style.color = theme.secondaryTextColor;
|
|
2059
|
+
e.currentTarget.style.background = "transparent";
|
|
2060
|
+
},
|
|
2061
|
+
title: strings.fileUpload.attachFiles,
|
|
2062
|
+
children: /* @__PURE__ */ jsxs5("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
2063
|
+
/* @__PURE__ */ jsx8("circle", { cx: "12", cy: "12", r: "10" }),
|
|
2064
|
+
/* @__PURE__ */ jsx8("line", { x1: "12", y1: "8", x2: "12", y2: "16" }),
|
|
2065
|
+
/* @__PURE__ */ jsx8("line", { x1: "8", y1: "12", x2: "16", y2: "12" })
|
|
2066
|
+
] })
|
|
2067
|
+
}
|
|
2068
|
+
)
|
|
2069
|
+
]
|
|
2070
|
+
}
|
|
2071
|
+
),
|
|
2072
|
+
/* @__PURE__ */ jsx8(
|
|
2073
|
+
"button",
|
|
2074
|
+
{
|
|
2075
|
+
"data-testid": "chat-send-button",
|
|
2076
|
+
className: "chat-send-button",
|
|
2077
|
+
onClick: handleSend,
|
|
2078
|
+
disabled: !connected || loading || !input.trim() && attachments.length === 0,
|
|
2079
|
+
style: {
|
|
2080
|
+
padding: "10px 16px",
|
|
2081
|
+
background: connected && !loading && (input.trim() || attachments.length > 0) ? theme.primaryGradient : theme.buttonDisabledBackground,
|
|
2082
|
+
color: connected && !loading && (input.trim() || attachments.length > 0) ? "white" : theme.secondaryTextColor,
|
|
2083
|
+
border: "none",
|
|
2084
|
+
borderRadius: "8px",
|
|
2085
|
+
cursor: connected && !loading && (input.trim() || attachments.length > 0) ? "pointer" : "not-allowed",
|
|
2086
|
+
fontSize: "14px",
|
|
2087
|
+
fontWeight: "600",
|
|
2088
|
+
minWidth: "60px",
|
|
2089
|
+
transition: "all 0.2s"
|
|
2090
|
+
},
|
|
2091
|
+
children: strings.input.send
|
|
2092
|
+
}
|
|
2093
|
+
)
|
|
2094
|
+
]
|
|
2095
|
+
}
|
|
2096
|
+
)
|
|
2097
|
+
]
|
|
2098
|
+
}
|
|
2099
|
+
),
|
|
2100
|
+
/* @__PURE__ */ jsx8("style", { children: `
|
|
2101
|
+
/* Markdown content styles */
|
|
2102
|
+
.markdown-content > :first-child {
|
|
2103
|
+
margin-top: 0 !important;
|
|
2104
|
+
}
|
|
2105
|
+
.markdown-content > :last-child {
|
|
2106
|
+
margin-bottom: 0 !important;
|
|
2107
|
+
}
|
|
2108
|
+
.markdown-content p:last-child {
|
|
2109
|
+
margin-bottom: 0 !important;
|
|
2110
|
+
}
|
|
2111
|
+
.markdown-content ul:last-child,
|
|
2112
|
+
.markdown-content ol:last-child {
|
|
2113
|
+
margin-bottom: 0 !important;
|
|
2114
|
+
}
|
|
2115
|
+
.markdown-content pre:last-child {
|
|
2116
|
+
margin-bottom: 0 !important;
|
|
2117
|
+
}
|
|
2118
|
+
` })
|
|
2119
|
+
]
|
|
2120
|
+
}
|
|
2121
|
+
);
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
// src/components/UseAIFloatingChatWrapper.tsx
|
|
2125
|
+
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2126
|
+
function UseAIFloatingChatWrapper({
|
|
2127
|
+
isOpen,
|
|
2128
|
+
onClose,
|
|
2129
|
+
children
|
|
2130
|
+
}) {
|
|
2131
|
+
const theme = useTheme();
|
|
2132
|
+
if (!isOpen) return null;
|
|
2133
|
+
return /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
2134
|
+
/* @__PURE__ */ jsx9(
|
|
2135
|
+
"div",
|
|
2136
|
+
{
|
|
2137
|
+
style: {
|
|
2138
|
+
position: "fixed",
|
|
2139
|
+
top: 0,
|
|
2140
|
+
left: 0,
|
|
2141
|
+
right: 0,
|
|
2142
|
+
bottom: 0,
|
|
2143
|
+
background: theme.backdropColor,
|
|
2144
|
+
zIndex: 999,
|
|
2145
|
+
animation: "fadeIn 0.2s"
|
|
2146
|
+
},
|
|
2147
|
+
onClick: onClose
|
|
2148
|
+
}
|
|
2149
|
+
),
|
|
2150
|
+
/* @__PURE__ */ jsx9(
|
|
2151
|
+
"div",
|
|
2152
|
+
{
|
|
2153
|
+
style: {
|
|
2154
|
+
position: "fixed",
|
|
2155
|
+
bottom: "24px",
|
|
2156
|
+
right: "24px",
|
|
2157
|
+
width: "380px",
|
|
2158
|
+
height: "600px",
|
|
2159
|
+
maxHeight: "calc(100vh - 48px)",
|
|
2160
|
+
borderRadius: "16px",
|
|
2161
|
+
boxShadow: theme.panelShadow,
|
|
2162
|
+
zIndex: 1001,
|
|
2163
|
+
animation: "slideIn 0.3s ease-out",
|
|
2164
|
+
overflow: "hidden"
|
|
2165
|
+
},
|
|
2166
|
+
children
|
|
2167
|
+
}
|
|
2168
|
+
),
|
|
2169
|
+
/* @__PURE__ */ jsx9("style", { children: `
|
|
2170
|
+
@keyframes fadeIn {
|
|
2171
|
+
from { opacity: 0; }
|
|
2172
|
+
to { opacity: 1; }
|
|
2173
|
+
}
|
|
2174
|
+
@keyframes slideIn {
|
|
2175
|
+
from {
|
|
2176
|
+
transform: translateY(20px);
|
|
2177
|
+
opacity: 0;
|
|
2178
|
+
}
|
|
2179
|
+
to {
|
|
2180
|
+
transform: translateY(0);
|
|
2181
|
+
opacity: 1;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
` })
|
|
2185
|
+
] });
|
|
2186
|
+
}
|
|
2187
|
+
function CloseButton({ onClick }) {
|
|
2188
|
+
const theme = useTheme();
|
|
2189
|
+
return /* @__PURE__ */ jsx9(
|
|
2190
|
+
"button",
|
|
2191
|
+
{
|
|
2192
|
+
"data-testid": "chat-close-button",
|
|
2193
|
+
className: "chat-close-button",
|
|
2194
|
+
onClick,
|
|
2195
|
+
style: {
|
|
2196
|
+
background: "transparent",
|
|
2197
|
+
border: "none",
|
|
2198
|
+
borderRadius: "6px",
|
|
2199
|
+
padding: "6px 8px",
|
|
2200
|
+
cursor: "pointer",
|
|
2201
|
+
color: theme.secondaryTextColor,
|
|
2202
|
+
fontSize: "20px",
|
|
2203
|
+
display: "flex",
|
|
2204
|
+
alignItems: "center",
|
|
2205
|
+
justifyContent: "center",
|
|
2206
|
+
transition: "all 0.2s",
|
|
2207
|
+
lineHeight: 1
|
|
2208
|
+
},
|
|
2209
|
+
onMouseEnter: (e) => {
|
|
2210
|
+
e.currentTarget.style.background = theme.hoverBackground;
|
|
2211
|
+
e.currentTarget.style.color = theme.textColor;
|
|
2212
|
+
},
|
|
2213
|
+
onMouseLeave: (e) => {
|
|
2214
|
+
e.currentTarget.style.background = "transparent";
|
|
2215
|
+
e.currentTarget.style.color = theme.secondaryTextColor;
|
|
2216
|
+
},
|
|
2217
|
+
children: "\xD7"
|
|
2218
|
+
}
|
|
2219
|
+
);
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
// src/components/UseAIChat.tsx
|
|
2223
|
+
import { createContext as createContext3, useContext as useContext3 } from "react";
|
|
2224
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
2225
|
+
var __UseAIChatContext = createContext3(null);
|
|
2226
|
+
function useChatUIContext() {
|
|
2227
|
+
const context = useContext3(__UseAIChatContext);
|
|
2228
|
+
if (!context) {
|
|
2229
|
+
throw new Error(
|
|
2230
|
+
"UseAIChat must be used within a UseAIProvider. Make sure UseAIChat is a descendant of UseAIProvider."
|
|
2231
|
+
);
|
|
2232
|
+
}
|
|
2233
|
+
return context;
|
|
2234
|
+
}
|
|
2235
|
+
function UseAIChat({ floating = false }) {
|
|
2236
|
+
const ctx = useChatUIContext();
|
|
2237
|
+
const chatPanelProps = {
|
|
2238
|
+
onSendMessage: ctx.sendMessage,
|
|
2239
|
+
messages: ctx.messages,
|
|
2240
|
+
loading: ctx.loading,
|
|
2241
|
+
connected: ctx.connected,
|
|
2242
|
+
streamingText: ctx.streamingText,
|
|
2243
|
+
currentChatId: ctx.history.currentId,
|
|
2244
|
+
onNewChat: ctx.history.create,
|
|
2245
|
+
onLoadChat: ctx.history.load,
|
|
2246
|
+
onDeleteChat: ctx.history.delete,
|
|
2247
|
+
onListChats: ctx.history.list,
|
|
2248
|
+
suggestions: ctx.suggestions,
|
|
2249
|
+
availableAgents: ctx.agents.available,
|
|
2250
|
+
defaultAgent: ctx.agents.default,
|
|
2251
|
+
selectedAgent: ctx.agents.selected,
|
|
2252
|
+
onAgentChange: ctx.agents.set,
|
|
2253
|
+
fileUploadConfig: ctx.fileUploadConfig,
|
|
2254
|
+
commands: ctx.commands.list,
|
|
2255
|
+
onSaveCommand: ctx.commands.save,
|
|
2256
|
+
onRenameCommand: ctx.commands.rename,
|
|
2257
|
+
onDeleteCommand: ctx.commands.delete
|
|
2258
|
+
};
|
|
2259
|
+
if (floating) {
|
|
2260
|
+
return /* @__PURE__ */ jsx10(
|
|
2261
|
+
UseAIFloatingChatWrapper,
|
|
2262
|
+
{
|
|
2263
|
+
isOpen: ctx.ui.isOpen,
|
|
2264
|
+
onClose: () => ctx.ui.setOpen(false),
|
|
2265
|
+
children: /* @__PURE__ */ jsx10(
|
|
2266
|
+
UseAIChatPanel,
|
|
2267
|
+
{
|
|
2268
|
+
...chatPanelProps,
|
|
2269
|
+
closeButton: /* @__PURE__ */ jsx10(CloseButton, { onClick: () => ctx.ui.setOpen(false) })
|
|
2270
|
+
}
|
|
2271
|
+
)
|
|
2272
|
+
}
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
return /* @__PURE__ */ jsx10(UseAIChatPanel, { ...chatPanelProps });
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
// src/client.ts
|
|
2279
|
+
import { io } from "socket.io-client";
|
|
2280
|
+
import { EventType as EventType2 } from "@meetsmore-oss/use-ai-core";
|
|
2281
|
+
import { v4 as uuidv42 } from "uuid";
|
|
2282
|
+
var UseAIClient = class {
|
|
2283
|
+
/**
|
|
2284
|
+
* Creates a new UseAI client instance.
|
|
2285
|
+
*
|
|
2286
|
+
* @param serverUrl - The WebSocket URL of the UseAI server
|
|
2287
|
+
*/
|
|
2288
|
+
constructor(serverUrl) {
|
|
2289
|
+
this.serverUrl = serverUrl;
|
|
2290
|
+
}
|
|
2291
|
+
socket = null;
|
|
2292
|
+
eventHandlers = /* @__PURE__ */ new Map();
|
|
2293
|
+
reconnectAttempts = 0;
|
|
2294
|
+
maxReconnectAttempts = 5;
|
|
2295
|
+
reconnectDelay = 1e3;
|
|
2296
|
+
// Session state
|
|
2297
|
+
_threadId = null;
|
|
2298
|
+
_tools = [];
|
|
2299
|
+
_messages = [];
|
|
2300
|
+
_state = null;
|
|
2301
|
+
// MCP headers provider
|
|
2302
|
+
mcpHeadersProvider;
|
|
2303
|
+
// Agent selection
|
|
2304
|
+
_availableAgents = [];
|
|
2305
|
+
_defaultAgent = null;
|
|
2306
|
+
_selectedAgent = null;
|
|
2307
|
+
agentsChangeHandlers = /* @__PURE__ */ new Set();
|
|
2308
|
+
// Connection state handlers
|
|
2309
|
+
connectionStateHandlers = /* @__PURE__ */ new Set();
|
|
2310
|
+
// Text message assembly
|
|
2311
|
+
_currentMessageId = null;
|
|
2312
|
+
_currentMessageContent = "";
|
|
2313
|
+
// Assistant message assembly (for tracking full conversation history)
|
|
2314
|
+
_currentAssistantMessage = null;
|
|
2315
|
+
_currentAssistantToolCalls = [];
|
|
2316
|
+
// Tool call assembly
|
|
2317
|
+
currentToolCalls = /* @__PURE__ */ new Map();
|
|
2318
|
+
/**
|
|
2319
|
+
* Establishes a Socket.IO connection to the server.
|
|
2320
|
+
* Connection state changes are notified via onConnectionStateChange().
|
|
2321
|
+
* Socket.IO handles reconnection automatically.
|
|
2322
|
+
*/
|
|
2323
|
+
connect() {
|
|
2324
|
+
this.socket = io(this.serverUrl, {
|
|
2325
|
+
transports: ["polling", "websocket"],
|
|
2326
|
+
reconnection: true,
|
|
2327
|
+
reconnectionAttempts: this.maxReconnectAttempts,
|
|
2328
|
+
reconnectionDelay: this.reconnectDelay,
|
|
2329
|
+
withCredentials: true
|
|
2330
|
+
});
|
|
2331
|
+
this.socket.on("connect", () => {
|
|
2332
|
+
console.log("[UseAI] Connected to server");
|
|
2333
|
+
console.log("[UseAI] Transport:", this.socket?.io?.engine?.transport?.name);
|
|
2334
|
+
this.reconnectAttempts = 0;
|
|
2335
|
+
const engine = this.socket?.io?.engine;
|
|
2336
|
+
if (engine) {
|
|
2337
|
+
engine.on("upgrade", (transport) => {
|
|
2338
|
+
console.log("[UseAI] Upgraded to transport:", transport.name);
|
|
2339
|
+
});
|
|
2340
|
+
engine.on("upgradeError", (err) => {
|
|
2341
|
+
console.warn("[UseAI] Upgrade error:", err.message);
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
this.connectionStateHandlers.forEach((handler) => handler(true));
|
|
2345
|
+
});
|
|
2346
|
+
this.socket.on("event", (aguiEvent) => {
|
|
2347
|
+
try {
|
|
2348
|
+
console.log("[Client] Received event:", aguiEvent.type);
|
|
2349
|
+
this.handleEvent(aguiEvent);
|
|
2350
|
+
} catch (error) {
|
|
2351
|
+
console.error("[UseAI] Error handling event:", error);
|
|
2352
|
+
}
|
|
2353
|
+
});
|
|
2354
|
+
this.socket.on("agents", (data) => {
|
|
2355
|
+
console.log("[Client] Received available agents:", data);
|
|
2356
|
+
this._availableAgents = data.agents;
|
|
2357
|
+
this._defaultAgent = data.defaultAgent;
|
|
2358
|
+
this.agentsChangeHandlers.forEach((handler) => handler(data.agents, data.defaultAgent));
|
|
2359
|
+
});
|
|
2360
|
+
this.socket.on("connect_error", (error) => {
|
|
2361
|
+
console.warn("[UseAI] Connection error:", error.message);
|
|
2362
|
+
});
|
|
2363
|
+
this.socket.on("disconnect", (reason) => {
|
|
2364
|
+
console.log("[UseAI] Disconnected:", reason);
|
|
2365
|
+
this.connectionStateHandlers.forEach((handler) => handler(false));
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
handleEvent(event) {
|
|
2369
|
+
if (event.type === EventType2.RUN_STARTED) {
|
|
2370
|
+
this._currentAssistantMessage = {
|
|
2371
|
+
id: uuidv42(),
|
|
2372
|
+
role: "assistant",
|
|
2373
|
+
content: ""
|
|
2374
|
+
};
|
|
2375
|
+
this._currentAssistantToolCalls = [];
|
|
2376
|
+
}
|
|
2377
|
+
if (event.type === EventType2.TEXT_MESSAGE_START) {
|
|
2378
|
+
const e = event;
|
|
2379
|
+
this._currentMessageId = e.messageId;
|
|
2380
|
+
this._currentMessageContent = "";
|
|
2381
|
+
} else if (event.type === EventType2.TEXT_MESSAGE_CONTENT) {
|
|
2382
|
+
const e = event;
|
|
2383
|
+
this._currentMessageContent += e.delta;
|
|
2384
|
+
} else if (event.type === EventType2.TEXT_MESSAGE_END) {
|
|
2385
|
+
if (this._currentAssistantMessage) {
|
|
2386
|
+
this._currentAssistantMessage.content = this._currentMessageContent;
|
|
2387
|
+
}
|
|
2388
|
+
this._currentMessageId = null;
|
|
2389
|
+
} else if (event.type === EventType2.TOOL_CALL_START) {
|
|
2390
|
+
const e = event;
|
|
2391
|
+
this.currentToolCalls.set(e.toolCallId, {
|
|
2392
|
+
name: e.toolCallName,
|
|
2393
|
+
args: ""
|
|
2394
|
+
});
|
|
2395
|
+
} else if (event.type === EventType2.TOOL_CALL_ARGS) {
|
|
2396
|
+
const e = event;
|
|
2397
|
+
const toolCall = this.currentToolCalls.get(e.toolCallId);
|
|
2398
|
+
if (toolCall) {
|
|
2399
|
+
toolCall.args += e.delta;
|
|
2400
|
+
}
|
|
2401
|
+
} else if (event.type === EventType2.TOOL_CALL_END) {
|
|
2402
|
+
const e = event;
|
|
2403
|
+
const toolCall = this.currentToolCalls.get(e.toolCallId);
|
|
2404
|
+
if (toolCall) {
|
|
2405
|
+
this._currentAssistantToolCalls.push({
|
|
2406
|
+
id: e.toolCallId,
|
|
2407
|
+
type: "function",
|
|
2408
|
+
function: {
|
|
2409
|
+
name: toolCall.name,
|
|
2410
|
+
arguments: toolCall.args
|
|
2411
|
+
}
|
|
2412
|
+
});
|
|
2413
|
+
}
|
|
2414
|
+
} else if (event.type === EventType2.RUN_FINISHED) {
|
|
2415
|
+
if (this._currentAssistantMessage) {
|
|
2416
|
+
const assistantMessage = {
|
|
2417
|
+
id: this._currentAssistantMessage.id,
|
|
2418
|
+
role: "assistant",
|
|
2419
|
+
content: this._currentAssistantMessage.content || ""
|
|
2420
|
+
};
|
|
2421
|
+
if (this._currentAssistantToolCalls.length > 0) {
|
|
2422
|
+
assistantMessage.toolCalls = this._currentAssistantToolCalls;
|
|
2423
|
+
}
|
|
2424
|
+
this._messages.push(assistantMessage);
|
|
2425
|
+
this._currentAssistantMessage = null;
|
|
2426
|
+
this._currentAssistantToolCalls = [];
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
this.eventHandlers.forEach((handler) => handler(event));
|
|
2430
|
+
}
|
|
2431
|
+
/**
|
|
2432
|
+
* Registers available tools and optional state with the server.
|
|
2433
|
+
* This updates the session state for future agent runs.
|
|
2434
|
+
*
|
|
2435
|
+
* @param tools - Array of tool definitions to register
|
|
2436
|
+
* @param state - Optional state object to provide to the AI.
|
|
2437
|
+
*/
|
|
2438
|
+
registerTools(tools, state) {
|
|
2439
|
+
this._tools = tools;
|
|
2440
|
+
if (state !== void 0) {
|
|
2441
|
+
this._state = state;
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Updates the state without re-registering tools.
|
|
2446
|
+
* Call this before sendPrompt to ensure the AI sees the latest UI state.
|
|
2447
|
+
*
|
|
2448
|
+
* @param state - The current state object to provide to the AI
|
|
2449
|
+
*/
|
|
2450
|
+
updateState(state) {
|
|
2451
|
+
this._state = state;
|
|
2452
|
+
}
|
|
2453
|
+
/**
|
|
2454
|
+
* Sets the MCP headers provider.
|
|
2455
|
+
* The provider will be called each time a message is sent to get fresh headers.
|
|
2456
|
+
*
|
|
2457
|
+
* @param provider - Function that returns MCP headers configuration
|
|
2458
|
+
*/
|
|
2459
|
+
setMcpHeadersProvider(provider) {
|
|
2460
|
+
this.mcpHeadersProvider = provider;
|
|
2461
|
+
}
|
|
2462
|
+
/**
|
|
2463
|
+
* Sends a user prompt to the AI.
|
|
2464
|
+
*
|
|
2465
|
+
* @param prompt - The user's prompt/question (text part)
|
|
2466
|
+
* @param multimodalContent - Optional multimodal content (text, images, files)
|
|
2467
|
+
*/
|
|
2468
|
+
async sendPrompt(prompt, multimodalContent) {
|
|
2469
|
+
let messageContent = prompt;
|
|
2470
|
+
if (multimodalContent && multimodalContent.length > 0) {
|
|
2471
|
+
messageContent = multimodalContent.map((part) => {
|
|
2472
|
+
if (part.type === "text") {
|
|
2473
|
+
return { type: "text", text: part.text };
|
|
2474
|
+
} else if (part.type === "image") {
|
|
2475
|
+
return { type: "image", url: part.url };
|
|
2476
|
+
} else {
|
|
2477
|
+
return {
|
|
2478
|
+
type: "file",
|
|
2479
|
+
url: part.url,
|
|
2480
|
+
mimeType: part.mimeType
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
const userMessage = {
|
|
2486
|
+
id: uuidv42(),
|
|
2487
|
+
role: "user",
|
|
2488
|
+
content: messageContent
|
|
2489
|
+
// Type cast needed for Message type compatibility
|
|
2490
|
+
};
|
|
2491
|
+
this._messages.push(userMessage);
|
|
2492
|
+
let mcpHeaders;
|
|
2493
|
+
if (this.mcpHeadersProvider) {
|
|
2494
|
+
try {
|
|
2495
|
+
mcpHeaders = await this.mcpHeadersProvider();
|
|
2496
|
+
} catch (error) {
|
|
2497
|
+
console.error("[UseAIClient] Failed to get MCP headers:", error);
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
const runInput = {
|
|
2501
|
+
threadId: this.threadId,
|
|
2502
|
+
// Use getter to ensure non-null
|
|
2503
|
+
runId: uuidv42(),
|
|
2504
|
+
messages: this._messages,
|
|
2505
|
+
tools: this._tools.map((t) => ({
|
|
2506
|
+
name: t.name,
|
|
2507
|
+
description: t.description,
|
|
2508
|
+
parameters: t.parameters
|
|
2509
|
+
})),
|
|
2510
|
+
state: this._state,
|
|
2511
|
+
context: [],
|
|
2512
|
+
forwardedProps: {
|
|
2513
|
+
...mcpHeaders ? { mcpHeaders } : {},
|
|
2514
|
+
...this._selectedAgent ? { agent: this._selectedAgent } : {}
|
|
2515
|
+
}
|
|
2516
|
+
};
|
|
2517
|
+
this.send({
|
|
2518
|
+
type: "run_agent",
|
|
2519
|
+
data: runInput
|
|
2520
|
+
});
|
|
2521
|
+
}
|
|
2522
|
+
/**
|
|
2523
|
+
* Sends the result of a tool execution back to the server.
|
|
2524
|
+
*
|
|
2525
|
+
* @param toolCallId - The ID of the tool call being responded to
|
|
2526
|
+
* @param result - The result returned by the tool execution
|
|
2527
|
+
* @param state - Optional updated state to send back to the AI
|
|
2528
|
+
*/
|
|
2529
|
+
sendToolResponse(toolCallId, result, state) {
|
|
2530
|
+
if (state !== void 0) {
|
|
2531
|
+
this._state = state;
|
|
2532
|
+
}
|
|
2533
|
+
const toolResultMessage = {
|
|
2534
|
+
type: "tool_result",
|
|
2535
|
+
data: {
|
|
2536
|
+
messageId: uuidv42(),
|
|
2537
|
+
toolCallId,
|
|
2538
|
+
content: JSON.stringify(result),
|
|
2539
|
+
role: "tool"
|
|
2540
|
+
}
|
|
2541
|
+
};
|
|
2542
|
+
const toolResultMsg = {
|
|
2543
|
+
id: toolResultMessage.data.messageId,
|
|
2544
|
+
role: "tool",
|
|
2545
|
+
content: toolResultMessage.data.content,
|
|
2546
|
+
toolCallId
|
|
2547
|
+
};
|
|
2548
|
+
this._messages.push(toolResultMsg);
|
|
2549
|
+
this.send(toolResultMessage);
|
|
2550
|
+
}
|
|
2551
|
+
/**
|
|
2552
|
+
* Retrieves accumulated tool call data for a specific tool call ID.
|
|
2553
|
+
* Used to get the complete tool name and arguments after they've been streamed
|
|
2554
|
+
* across multiple TOOL_CALL_ARGS events.
|
|
2555
|
+
*
|
|
2556
|
+
* @param toolCallId - The ID of the tool call
|
|
2557
|
+
* @returns Object with tool name and accumulated arguments, or undefined if not found
|
|
2558
|
+
*/
|
|
2559
|
+
getToolCallData(toolCallId) {
|
|
2560
|
+
return this.currentToolCalls.get(toolCallId);
|
|
2561
|
+
}
|
|
2562
|
+
/**
|
|
2563
|
+
* Registers an AG-UI event handler for receiving server events.
|
|
2564
|
+
*
|
|
2565
|
+
* @param id - Unique identifier for this handler
|
|
2566
|
+
* @param handler - Callback function to handle incoming AG-UI events
|
|
2567
|
+
* @returns Cleanup function to unregister the handler
|
|
2568
|
+
*/
|
|
2569
|
+
onEvent(id, handler) {
|
|
2570
|
+
this.eventHandlers.set(id, handler);
|
|
2571
|
+
return () => {
|
|
2572
|
+
this.eventHandlers.delete(id);
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
/**
|
|
2576
|
+
* Helper method to listen for text message content.
|
|
2577
|
+
* Aggregates TEXT_MESSAGE_CONTENT events and calls handler with complete messages.
|
|
2578
|
+
*
|
|
2579
|
+
* @param handler - Callback function to handle complete text messages
|
|
2580
|
+
* @returns Cleanup function
|
|
2581
|
+
*/
|
|
2582
|
+
onTextMessage(handler) {
|
|
2583
|
+
return this.onEvent("text-message-handler", (event) => {
|
|
2584
|
+
if (event.type === EventType2.TEXT_MESSAGE_END && this._currentMessageContent) {
|
|
2585
|
+
handler(this._currentMessageContent);
|
|
2586
|
+
}
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
/**
|
|
2590
|
+
* Helper method to listen for tool call requests.
|
|
2591
|
+
* Aggregates TOOL_CALL_* events and calls handler with complete tool calls.
|
|
2592
|
+
*
|
|
2593
|
+
* @param handler - Callback function to handle tool calls
|
|
2594
|
+
* @returns Cleanup function
|
|
2595
|
+
*/
|
|
2596
|
+
onToolCall(handler) {
|
|
2597
|
+
return this.onEvent("tool-call-handler", (event) => {
|
|
2598
|
+
if (event.type === EventType2.TOOL_CALL_END) {
|
|
2599
|
+
const e = event;
|
|
2600
|
+
const toolCall = this.currentToolCalls.get(e.toolCallId);
|
|
2601
|
+
if (toolCall) {
|
|
2602
|
+
try {
|
|
2603
|
+
const args = JSON.parse(toolCall.args);
|
|
2604
|
+
handler(e.toolCallId, toolCall.name, args);
|
|
2605
|
+
this.currentToolCalls.delete(e.toolCallId);
|
|
2606
|
+
} catch (error) {
|
|
2607
|
+
console.error("Error parsing tool call args:", error);
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
});
|
|
2612
|
+
}
|
|
2613
|
+
/**
|
|
2614
|
+
* Gets the current accumulated message content (useful during streaming).
|
|
2615
|
+
*/
|
|
2616
|
+
get currentMessageContent() {
|
|
2617
|
+
return this._currentMessageContent;
|
|
2618
|
+
}
|
|
2619
|
+
/**
|
|
2620
|
+
* Gets the current thread ID for this session.
|
|
2621
|
+
* Generates a new one if not set.
|
|
2622
|
+
*/
|
|
2623
|
+
get threadId() {
|
|
2624
|
+
if (!this._threadId) {
|
|
2625
|
+
this._threadId = uuidv42();
|
|
2626
|
+
}
|
|
2627
|
+
return this._threadId;
|
|
2628
|
+
}
|
|
2629
|
+
/**
|
|
2630
|
+
* Gets the current conversation messages.
|
|
2631
|
+
*/
|
|
2632
|
+
get messages() {
|
|
2633
|
+
return this._messages;
|
|
2634
|
+
}
|
|
2635
|
+
/**
|
|
2636
|
+
* Gets the current state.
|
|
2637
|
+
*/
|
|
2638
|
+
get state() {
|
|
2639
|
+
return this._state;
|
|
2640
|
+
}
|
|
2641
|
+
/**
|
|
2642
|
+
* Gets the list of available agents from the server.
|
|
2643
|
+
*/
|
|
2644
|
+
get availableAgents() {
|
|
2645
|
+
return this._availableAgents;
|
|
2646
|
+
}
|
|
2647
|
+
/**
|
|
2648
|
+
* Gets the default agent ID from the server.
|
|
2649
|
+
*/
|
|
2650
|
+
get defaultAgent() {
|
|
2651
|
+
return this._defaultAgent;
|
|
2652
|
+
}
|
|
2653
|
+
/**
|
|
2654
|
+
* Gets the currently selected agent ID.
|
|
2655
|
+
* Returns null if using server default.
|
|
2656
|
+
*/
|
|
2657
|
+
get selectedAgent() {
|
|
2658
|
+
return this._selectedAgent;
|
|
2659
|
+
}
|
|
2660
|
+
/**
|
|
2661
|
+
* Gets the effective agent ID (selected or default).
|
|
2662
|
+
*/
|
|
2663
|
+
get currentAgent() {
|
|
2664
|
+
return this._selectedAgent ?? this._defaultAgent;
|
|
2665
|
+
}
|
|
2666
|
+
/**
|
|
2667
|
+
* Sets the agent to use for requests.
|
|
2668
|
+
* Pass null to use the server default.
|
|
2669
|
+
*
|
|
2670
|
+
* @param agentId - The agent ID to use, or null for server default
|
|
2671
|
+
*/
|
|
2672
|
+
setAgent(agentId) {
|
|
2673
|
+
this._selectedAgent = agentId;
|
|
2674
|
+
console.log("[Client] Agent set to:", agentId ?? "server default");
|
|
2675
|
+
}
|
|
2676
|
+
/**
|
|
2677
|
+
* Subscribes to agent changes (when server sends available agents).
|
|
2678
|
+
*
|
|
2679
|
+
* @param handler - Callback function receiving agents list and default agent
|
|
2680
|
+
* @returns Cleanup function to unsubscribe
|
|
2681
|
+
*/
|
|
2682
|
+
onAgentsChange(handler) {
|
|
2683
|
+
this.agentsChangeHandlers.add(handler);
|
|
2684
|
+
if (this._availableAgents.length > 0) {
|
|
2685
|
+
handler(this._availableAgents, this._defaultAgent);
|
|
2686
|
+
}
|
|
2687
|
+
return () => {
|
|
2688
|
+
this.agentsChangeHandlers.delete(handler);
|
|
2689
|
+
};
|
|
2690
|
+
}
|
|
2691
|
+
/**
|
|
2692
|
+
* Subscribes to connection state changes.
|
|
2693
|
+
* This is called on both initial connection and reconnection.
|
|
2694
|
+
*
|
|
2695
|
+
* @param handler - Callback function receiving connection state (true = connected, false = disconnected)
|
|
2696
|
+
* @returns Cleanup function to unsubscribe
|
|
2697
|
+
*/
|
|
2698
|
+
onConnectionStateChange(handler) {
|
|
2699
|
+
this.connectionStateHandlers.add(handler);
|
|
2700
|
+
handler(this.isConnected());
|
|
2701
|
+
return () => {
|
|
2702
|
+
this.connectionStateHandlers.delete(handler);
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2705
|
+
/**
|
|
2706
|
+
* Sets the thread ID for this session.
|
|
2707
|
+
* When the thread ID changes, conversation state is cleared to prevent history bleeding.
|
|
2708
|
+
* Use this when switching between different chat conversations.
|
|
2709
|
+
*
|
|
2710
|
+
* @param threadId - The thread/chat ID to use (typically the chatId)
|
|
2711
|
+
*/
|
|
2712
|
+
setThreadId(threadId) {
|
|
2713
|
+
if (this._threadId !== threadId) {
|
|
2714
|
+
console.log("[Client] ThreadId changed, clearing conversation state", {
|
|
2715
|
+
oldThreadId: this._threadId,
|
|
2716
|
+
newThreadId: threadId
|
|
2717
|
+
});
|
|
2718
|
+
this._messages = [];
|
|
2719
|
+
this._currentMessageContent = "";
|
|
2720
|
+
this._currentMessageId = null;
|
|
2721
|
+
this.currentToolCalls.clear();
|
|
2722
|
+
this._currentAssistantMessage = null;
|
|
2723
|
+
this._currentAssistantToolCalls = [];
|
|
2724
|
+
}
|
|
2725
|
+
this._threadId = threadId;
|
|
2726
|
+
}
|
|
2727
|
+
/**
|
|
2728
|
+
* Loads messages into the conversation history (for resuming from storage).
|
|
2729
|
+
* @param messages - Array of messages to load
|
|
2730
|
+
*/
|
|
2731
|
+
loadMessages(messages) {
|
|
2732
|
+
this._messages = messages;
|
|
2733
|
+
}
|
|
2734
|
+
/**
|
|
2735
|
+
* Clears the conversation history and resets the thread.
|
|
2736
|
+
*/
|
|
2737
|
+
clearConversation() {
|
|
2738
|
+
this._messages = [];
|
|
2739
|
+
this._threadId = null;
|
|
2740
|
+
this._currentMessageContent = "";
|
|
2741
|
+
this._currentMessageId = null;
|
|
2742
|
+
this.currentToolCalls.clear();
|
|
2743
|
+
this._currentAssistantMessage = null;
|
|
2744
|
+
this._currentAssistantToolCalls = [];
|
|
2745
|
+
}
|
|
2746
|
+
send(message) {
|
|
2747
|
+
if (this.socket && this.socket.connected) {
|
|
2748
|
+
this.socket.emit("message", message);
|
|
2749
|
+
} else {
|
|
2750
|
+
console.error("Socket.IO is not connected");
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
/**
|
|
2754
|
+
* Closes the Socket.IO connection to the server.
|
|
2755
|
+
*/
|
|
2756
|
+
disconnect() {
|
|
2757
|
+
if (this.socket) {
|
|
2758
|
+
this.socket.disconnect();
|
|
2759
|
+
this.socket = null;
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
/**
|
|
2763
|
+
* Checks if the client is currently connected to the server.
|
|
2764
|
+
*
|
|
2765
|
+
* @returns true if connected, false otherwise
|
|
2766
|
+
*/
|
|
2767
|
+
isConnected() {
|
|
2768
|
+
return this.socket !== null && this.socket.connected;
|
|
2769
|
+
}
|
|
2770
|
+
};
|
|
2771
|
+
|
|
2772
|
+
// src/defineTool.ts
|
|
2773
|
+
import { z } from "zod";
|
|
2774
|
+
function defineTool(description, schemaOrFn, fnOrOptions, options) {
|
|
2775
|
+
const isNoParamFunction = typeof schemaOrFn === "function";
|
|
2776
|
+
const schema = isNoParamFunction ? z.object({}) : schemaOrFn;
|
|
2777
|
+
let actualFn;
|
|
2778
|
+
let actualOptions;
|
|
2779
|
+
if (isNoParamFunction) {
|
|
2780
|
+
actualFn = schemaOrFn;
|
|
2781
|
+
actualOptions = fnOrOptions || {};
|
|
2782
|
+
} else {
|
|
2783
|
+
actualFn = fnOrOptions;
|
|
2784
|
+
actualOptions = options || {};
|
|
2785
|
+
}
|
|
2786
|
+
const jsonSchema = z.toJSONSchema(schema);
|
|
2787
|
+
return {
|
|
2788
|
+
description,
|
|
2789
|
+
_jsonSchema: jsonSchema,
|
|
2790
|
+
_zodSchema: schema,
|
|
2791
|
+
fn: actualFn,
|
|
2792
|
+
_options: actualOptions,
|
|
2793
|
+
_toToolDefinition(name) {
|
|
2794
|
+
const parameters = {
|
|
2795
|
+
type: "object",
|
|
2796
|
+
properties: this._jsonSchema.properties || {}
|
|
2797
|
+
};
|
|
2798
|
+
if (this._jsonSchema.required && this._jsonSchema.required.length > 0) {
|
|
2799
|
+
parameters.required = this._jsonSchema.required;
|
|
2800
|
+
}
|
|
2801
|
+
if (this._jsonSchema.additionalProperties !== void 0) {
|
|
2802
|
+
parameters.additionalProperties = this._jsonSchema.additionalProperties;
|
|
2803
|
+
}
|
|
2804
|
+
const toolDef = {
|
|
2805
|
+
name,
|
|
2806
|
+
description,
|
|
2807
|
+
parameters
|
|
2808
|
+
};
|
|
2809
|
+
if (this._options.confirmationRequired) {
|
|
2810
|
+
toolDef.confirmationRequired = true;
|
|
2811
|
+
}
|
|
2812
|
+
return toolDef;
|
|
2813
|
+
},
|
|
2814
|
+
async _execute(input) {
|
|
2815
|
+
const validated = this._zodSchema.parse(input);
|
|
2816
|
+
return await actualFn(validated);
|
|
2817
|
+
}
|
|
2818
|
+
};
|
|
2819
|
+
}
|
|
2820
|
+
function convertToolsToDefinitions(tools) {
|
|
2821
|
+
return Object.entries(tools).map(([name, tool]) => tool._toToolDefinition(name));
|
|
2822
|
+
}
|
|
2823
|
+
async function executeDefinedTool(tools, toolName, input) {
|
|
2824
|
+
const tool = tools[toolName];
|
|
2825
|
+
if (!tool) {
|
|
2826
|
+
throw new Error(`Tool "${toolName}" not found`);
|
|
2827
|
+
}
|
|
2828
|
+
return await tool._execute(input);
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
// src/providers/chatRepository/LocalStorageChatRepository.ts
|
|
2832
|
+
var STORAGE_KEY_PREFIX = "use-ai:chat:";
|
|
2833
|
+
var STORAGE_INDEX_KEY = "use-ai:chat-index";
|
|
2834
|
+
var MAX_CHATS = 20;
|
|
2835
|
+
var LocalStorageChatRepository = class {
|
|
2836
|
+
storage;
|
|
2837
|
+
maxChats;
|
|
2838
|
+
/**
|
|
2839
|
+
* Creates a new LocalStorageChatRepository.
|
|
2840
|
+
*
|
|
2841
|
+
* @param storage - Storage implementation to use (defaults to browser `localStorage`)
|
|
2842
|
+
* @param maxChats - Maximum number of chats to keep (defaults to 20). Oldest chats are automatically deleted when this limit is exceeded.
|
|
2843
|
+
*/
|
|
2844
|
+
constructor(storage = localStorage, maxChats = MAX_CHATS) {
|
|
2845
|
+
this.storage = storage;
|
|
2846
|
+
this.maxChats = maxChats;
|
|
2847
|
+
}
|
|
2848
|
+
async createChat(options) {
|
|
2849
|
+
const id = generateChatId();
|
|
2850
|
+
const now = /* @__PURE__ */ new Date();
|
|
2851
|
+
const chat = {
|
|
2852
|
+
id,
|
|
2853
|
+
title: options?.title,
|
|
2854
|
+
messages: [],
|
|
2855
|
+
createdAt: now,
|
|
2856
|
+
updatedAt: now
|
|
2857
|
+
};
|
|
2858
|
+
await this.enforceMaxChatsLimit();
|
|
2859
|
+
await this.saveChat(chat);
|
|
2860
|
+
await this.addToIndex(id);
|
|
2861
|
+
return id;
|
|
2862
|
+
}
|
|
2863
|
+
async loadChat(id) {
|
|
2864
|
+
try {
|
|
2865
|
+
const key = this.getChatKey(id);
|
|
2866
|
+
const data = this.storage.getItem(key);
|
|
2867
|
+
if (!data) {
|
|
2868
|
+
return null;
|
|
2869
|
+
}
|
|
2870
|
+
const chat = JSON.parse(data);
|
|
2871
|
+
chat.createdAt = new Date(chat.createdAt);
|
|
2872
|
+
chat.updatedAt = new Date(chat.updatedAt);
|
|
2873
|
+
chat.messages = chat.messages.map((msg) => ({
|
|
2874
|
+
...msg,
|
|
2875
|
+
createdAt: new Date(msg.createdAt)
|
|
2876
|
+
}));
|
|
2877
|
+
return chat;
|
|
2878
|
+
} catch (error) {
|
|
2879
|
+
console.error(`Failed to load chat ${id}:`, error);
|
|
2880
|
+
return null;
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
async saveChat(chat) {
|
|
2884
|
+
try {
|
|
2885
|
+
const key = this.getChatKey(chat.id);
|
|
2886
|
+
const data = JSON.stringify({
|
|
2887
|
+
...chat,
|
|
2888
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2889
|
+
});
|
|
2890
|
+
this.storage.setItem(key, data);
|
|
2891
|
+
} catch (error) {
|
|
2892
|
+
console.error(`Failed to save chat ${chat.id}:`, error);
|
|
2893
|
+
throw new Error(`Failed to save chat: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
async deleteChat(id) {
|
|
2897
|
+
try {
|
|
2898
|
+
const key = this.getChatKey(id);
|
|
2899
|
+
this.storage.removeItem(key);
|
|
2900
|
+
await this.removeFromIndex(id);
|
|
2901
|
+
} catch (error) {
|
|
2902
|
+
console.error(`Failed to delete chat ${id}:`, error);
|
|
2903
|
+
throw new Error(`Failed to delete chat: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
async listChats(options) {
|
|
2907
|
+
try {
|
|
2908
|
+
const ids = await this.getIndex();
|
|
2909
|
+
const chats = [];
|
|
2910
|
+
for (const id of ids) {
|
|
2911
|
+
const chat = await this.loadChat(id);
|
|
2912
|
+
if (chat) {
|
|
2913
|
+
const { messages, ...metadata } = chat;
|
|
2914
|
+
chats.push(metadata);
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
chats.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
2918
|
+
const { limit, offset = 0 } = options ?? {};
|
|
2919
|
+
const start = offset;
|
|
2920
|
+
const end = limit ? offset + limit : void 0;
|
|
2921
|
+
return chats.slice(start, end);
|
|
2922
|
+
} catch (error) {
|
|
2923
|
+
console.error("Failed to list chats:", error);
|
|
2924
|
+
return [];
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
async deleteAll() {
|
|
2928
|
+
try {
|
|
2929
|
+
const ids = await this.getIndex();
|
|
2930
|
+
for (const id of ids) {
|
|
2931
|
+
const key = this.getChatKey(id);
|
|
2932
|
+
this.storage.removeItem(key);
|
|
2933
|
+
}
|
|
2934
|
+
this.storage.removeItem(STORAGE_INDEX_KEY);
|
|
2935
|
+
} catch (error) {
|
|
2936
|
+
console.error("Failed to clear all chats:", error);
|
|
2937
|
+
throw new Error(`Failed to clear all chats: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
getChatKey(id) {
|
|
2941
|
+
return `${STORAGE_KEY_PREFIX}${id}`;
|
|
2942
|
+
}
|
|
2943
|
+
async getIndex() {
|
|
2944
|
+
try {
|
|
2945
|
+
const data = this.storage.getItem(STORAGE_INDEX_KEY);
|
|
2946
|
+
return data ? JSON.parse(data) : [];
|
|
2947
|
+
} catch (error) {
|
|
2948
|
+
console.error("Failed to load chat index:", error);
|
|
2949
|
+
return [];
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
async addToIndex(id) {
|
|
2953
|
+
const index = await this.getIndex();
|
|
2954
|
+
if (!index.includes(id)) {
|
|
2955
|
+
index.push(id);
|
|
2956
|
+
this.storage.setItem(STORAGE_INDEX_KEY, JSON.stringify(index));
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
async removeFromIndex(id) {
|
|
2960
|
+
const index = await this.getIndex();
|
|
2961
|
+
const filtered = index.filter((chatId) => chatId !== id);
|
|
2962
|
+
this.storage.setItem(STORAGE_INDEX_KEY, JSON.stringify(filtered));
|
|
2963
|
+
}
|
|
2964
|
+
async enforceMaxChatsLimit() {
|
|
2965
|
+
const chats = await this.listChats();
|
|
2966
|
+
if (chats.length >= this.maxChats) {
|
|
2967
|
+
const sortedChats = [...chats].sort(
|
|
2968
|
+
(a, b) => a.updatedAt.getTime() - b.updatedAt.getTime()
|
|
2969
|
+
);
|
|
2970
|
+
const numToDelete = chats.length - this.maxChats + 1;
|
|
2971
|
+
for (let i = 0; i < numToDelete; i++) {
|
|
2972
|
+
await this.deleteChat(sortedChats[i].id);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
};
|
|
2977
|
+
|
|
2978
|
+
// src/fileUpload/EmbedFileUploadBackend.ts
|
|
2979
|
+
var EmbedFileUploadBackend = class {
|
|
2980
|
+
/**
|
|
2981
|
+
* Converts a File to a base64 data URL.
|
|
2982
|
+
*
|
|
2983
|
+
* @param file - The File object to convert
|
|
2984
|
+
* @returns Promise resolving to a base64 data URL (e.g., "data:image/png;base64,...")
|
|
2985
|
+
* @throws Error if file reading fails
|
|
2986
|
+
*/
|
|
2987
|
+
async prepareForSend(file) {
|
|
2988
|
+
return new Promise((resolve, reject) => {
|
|
2989
|
+
const reader = new FileReader();
|
|
2990
|
+
reader.onload = () => {
|
|
2991
|
+
if (typeof reader.result === "string") {
|
|
2992
|
+
resolve(reader.result);
|
|
2993
|
+
} else {
|
|
2994
|
+
reject(new Error("Failed to read file as data URL"));
|
|
2995
|
+
}
|
|
2996
|
+
};
|
|
2997
|
+
reader.onerror = () => {
|
|
2998
|
+
reject(new Error(`Failed to read file: ${file.name}`));
|
|
2999
|
+
};
|
|
3000
|
+
reader.readAsDataURL(file);
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
3003
|
+
};
|
|
3004
|
+
|
|
3005
|
+
// src/hooks/useChatManagement.ts
|
|
3006
|
+
import { useState as useState5, useCallback as useCallback4, useRef as useRef5, useEffect as useEffect5 } from "react";
|
|
3007
|
+
var CHAT_TITLE_MAX_LENGTH = 50;
|
|
3008
|
+
function generateChatTitle(message) {
|
|
3009
|
+
return message.length > CHAT_TITLE_MAX_LENGTH ? message.substring(0, CHAT_TITLE_MAX_LENGTH) + "..." : message;
|
|
3010
|
+
}
|
|
3011
|
+
function getTextFromContent(content) {
|
|
3012
|
+
if (typeof content === "string") {
|
|
3013
|
+
return content;
|
|
3014
|
+
}
|
|
3015
|
+
return content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
3016
|
+
}
|
|
3017
|
+
function transformMessagesToUI(storageMessages) {
|
|
3018
|
+
return storageMessages.map((msg) => ({
|
|
3019
|
+
id: msg.id,
|
|
3020
|
+
role: msg.role,
|
|
3021
|
+
content: msg.content,
|
|
3022
|
+
timestamp: msg.createdAt,
|
|
3023
|
+
displayMode: msg.displayMode
|
|
3024
|
+
}));
|
|
3025
|
+
}
|
|
3026
|
+
function transformMessagesToClientFormat(uiMessages) {
|
|
3027
|
+
return uiMessages.map((msg) => {
|
|
3028
|
+
const textContent = getTextFromContent(msg.content);
|
|
3029
|
+
return {
|
|
3030
|
+
id: msg.id,
|
|
3031
|
+
role: msg.role,
|
|
3032
|
+
content: textContent
|
|
3033
|
+
};
|
|
3034
|
+
});
|
|
3035
|
+
}
|
|
3036
|
+
function useChatManagement({
|
|
3037
|
+
repository,
|
|
3038
|
+
clientRef
|
|
3039
|
+
}) {
|
|
3040
|
+
const [currentChatId, setCurrentChatId] = useState5(null);
|
|
3041
|
+
const [pendingChatId, setPendingChatId] = useState5(null);
|
|
3042
|
+
const [messages, setMessages] = useState5([]);
|
|
3043
|
+
const currentChatIdSnapshot = useRef5(null);
|
|
3044
|
+
const pendingChatIdSnapshot = useRef5(null);
|
|
3045
|
+
useEffect5(() => {
|
|
3046
|
+
currentChatIdSnapshot.current = currentChatId;
|
|
3047
|
+
}, [currentChatId]);
|
|
3048
|
+
useEffect5(() => {
|
|
3049
|
+
pendingChatIdSnapshot.current = pendingChatId;
|
|
3050
|
+
}, [pendingChatId]);
|
|
3051
|
+
const loadChatMessages = useCallback4(async (chatId) => {
|
|
3052
|
+
try {
|
|
3053
|
+
const chat = await repository.loadChat(chatId);
|
|
3054
|
+
if (chat) {
|
|
3055
|
+
const loadedMessages = transformMessagesToUI(chat.messages);
|
|
3056
|
+
console.log("[ChatManagement] Loaded", loadedMessages.length, "messages from storage for chat:", chatId);
|
|
3057
|
+
return loadedMessages;
|
|
3058
|
+
} else {
|
|
3059
|
+
console.log("[ChatManagement] Chat not found in storage:", chatId);
|
|
3060
|
+
return [];
|
|
3061
|
+
}
|
|
3062
|
+
} catch (error) {
|
|
3063
|
+
console.error("[ChatManagement] Failed to load chat messages:", error);
|
|
3064
|
+
return [];
|
|
3065
|
+
}
|
|
3066
|
+
}, [repository]);
|
|
3067
|
+
const reloadMessages = useCallback4(async (chatId) => {
|
|
3068
|
+
const loadedMessages = await loadChatMessages(chatId);
|
|
3069
|
+
setMessages(loadedMessages);
|
|
3070
|
+
}, [loadChatMessages]);
|
|
3071
|
+
const createNewChat = useCallback4(async () => {
|
|
3072
|
+
console.log("[ChatManagement] createNewChat called - currentChatId:", currentChatId, "pendingChatId:", pendingChatId, "messages.length:", messages.length);
|
|
3073
|
+
if (pendingChatId && messages.length === 0) {
|
|
3074
|
+
console.log("[ChatManagement] Pending chat is already blank, not creating new chat");
|
|
3075
|
+
return pendingChatId;
|
|
3076
|
+
}
|
|
3077
|
+
if (currentChatId && !pendingChatId && messages.length === 0) {
|
|
3078
|
+
console.log("[ChatManagement] Current chat is already blank, not creating new chat");
|
|
3079
|
+
return currentChatId;
|
|
3080
|
+
}
|
|
3081
|
+
console.log("[ChatManagement] Creating new chat...");
|
|
3082
|
+
const chatId = await repository.createChat();
|
|
3083
|
+
setPendingChatId(chatId);
|
|
3084
|
+
setMessages([]);
|
|
3085
|
+
if (clientRef.current) {
|
|
3086
|
+
clientRef.current.setThreadId(chatId);
|
|
3087
|
+
console.log("[ChatManagement] Set threadId to new chatId:", chatId);
|
|
3088
|
+
}
|
|
3089
|
+
console.log("[ChatManagement] Created pending chat:", chatId, "(will activate on first message)");
|
|
3090
|
+
return chatId;
|
|
3091
|
+
}, [currentChatId, pendingChatId, messages, repository, clientRef]);
|
|
3092
|
+
const loadChat = useCallback4(async (chatId) => {
|
|
3093
|
+
setPendingChatId(chatId);
|
|
3094
|
+
await reloadMessages(chatId);
|
|
3095
|
+
if (clientRef.current) {
|
|
3096
|
+
clientRef.current.setThreadId(chatId);
|
|
3097
|
+
console.log("[ChatManagement] Set threadId to chatId:", chatId);
|
|
3098
|
+
}
|
|
3099
|
+
console.log("[ChatManagement] Loaded pending chat:", chatId, "(will activate on first message)");
|
|
3100
|
+
}, [reloadMessages, clientRef]);
|
|
3101
|
+
const deleteChat = useCallback4(async (chatId) => {
|
|
3102
|
+
await repository.deleteChat(chatId);
|
|
3103
|
+
if (currentChatId === chatId) {
|
|
3104
|
+
setCurrentChatId(null);
|
|
3105
|
+
setMessages([]);
|
|
3106
|
+
}
|
|
3107
|
+
if (pendingChatId === chatId) {
|
|
3108
|
+
setPendingChatId(null);
|
|
3109
|
+
setMessages([]);
|
|
3110
|
+
}
|
|
3111
|
+
console.log("[ChatManagement] Deleted chat:", chatId);
|
|
3112
|
+
}, [currentChatId, pendingChatId, repository]);
|
|
3113
|
+
const listChats = useCallback4(async () => {
|
|
3114
|
+
return await repository.listChats();
|
|
3115
|
+
}, [repository]);
|
|
3116
|
+
const clearCurrentChat = useCallback4(async () => {
|
|
3117
|
+
setMessages([]);
|
|
3118
|
+
if (currentChatId) {
|
|
3119
|
+
const chat = await repository.loadChat(currentChatId);
|
|
3120
|
+
if (chat) {
|
|
3121
|
+
chat.messages = [];
|
|
3122
|
+
await repository.saveChat(chat);
|
|
3123
|
+
console.log("[ChatManagement] Cleared current chat:", currentChatId);
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
}, [currentChatId, repository]);
|
|
3127
|
+
const activatePendingChat = useCallback4(() => {
|
|
3128
|
+
if (!pendingChatId) return null;
|
|
3129
|
+
console.log("[ChatManagement] Activating pending chat:", pendingChatId);
|
|
3130
|
+
if (clientRef.current && messages.length > 0) {
|
|
3131
|
+
clientRef.current.loadMessages(transformMessagesToClientFormat(messages));
|
|
3132
|
+
console.log("[ChatManagement] Loaded", messages.length, "existing messages into client");
|
|
3133
|
+
}
|
|
3134
|
+
setCurrentChatId(pendingChatId);
|
|
3135
|
+
setPendingChatId(null);
|
|
3136
|
+
return pendingChatId;
|
|
3137
|
+
}, [pendingChatId, messages, clientRef]);
|
|
3138
|
+
const saveUserMessage = useCallback4(async (chatId, content) => {
|
|
3139
|
+
try {
|
|
3140
|
+
const chat = await repository.loadChat(chatId);
|
|
3141
|
+
if (!chat) {
|
|
3142
|
+
console.error("[ChatManagement] Chat not found:", chatId);
|
|
3143
|
+
return false;
|
|
3144
|
+
}
|
|
3145
|
+
const { generateMessageId: generateMessageId2 } = await import("./types-TVUXB3NB.js");
|
|
3146
|
+
chat.messages.push({
|
|
3147
|
+
id: generateMessageId2(),
|
|
3148
|
+
role: "user",
|
|
3149
|
+
content,
|
|
3150
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
3151
|
+
});
|
|
3152
|
+
if (!chat.title) {
|
|
3153
|
+
const text = getTextFromContent(content);
|
|
3154
|
+
if (text) {
|
|
3155
|
+
chat.title = generateChatTitle(text);
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
await repository.saveChat(chat);
|
|
3159
|
+
console.log("[ChatManagement] Saved user message to storage");
|
|
3160
|
+
await reloadMessages(chatId);
|
|
3161
|
+
return true;
|
|
3162
|
+
} catch (error) {
|
|
3163
|
+
console.error("[ChatManagement] Failed to save user message:", error);
|
|
3164
|
+
return false;
|
|
3165
|
+
}
|
|
3166
|
+
}, [repository, reloadMessages]);
|
|
3167
|
+
const saveAIResponse = useCallback4(async (content, displayMode) => {
|
|
3168
|
+
const currentChatIdValue = currentChatIdSnapshot.current;
|
|
3169
|
+
const pendingChatIdValue = pendingChatIdSnapshot.current;
|
|
3170
|
+
const displayedChatId2 = pendingChatIdValue || currentChatIdValue;
|
|
3171
|
+
if (!currentChatIdValue) {
|
|
3172
|
+
console.warn("[ChatManagement] No current chat ID, cannot save AI response");
|
|
3173
|
+
return;
|
|
3174
|
+
}
|
|
3175
|
+
try {
|
|
3176
|
+
const chat = await repository.loadChat(currentChatIdValue);
|
|
3177
|
+
if (!chat) {
|
|
3178
|
+
console.error("[ChatManagement] Chat not found:", currentChatIdValue);
|
|
3179
|
+
return;
|
|
3180
|
+
}
|
|
3181
|
+
const { generateMessageId: generateMessageId2 } = await import("./types-TVUXB3NB.js");
|
|
3182
|
+
chat.messages.push({
|
|
3183
|
+
id: generateMessageId2(),
|
|
3184
|
+
role: "assistant",
|
|
3185
|
+
content,
|
|
3186
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
3187
|
+
displayMode
|
|
3188
|
+
});
|
|
3189
|
+
if (!chat.title) {
|
|
3190
|
+
const firstUserMessage = chat.messages.find((msg) => msg.role === "user");
|
|
3191
|
+
if (firstUserMessage) {
|
|
3192
|
+
const textContent = getTextFromContent(firstUserMessage.content);
|
|
3193
|
+
if (textContent) {
|
|
3194
|
+
chat.title = generateChatTitle(textContent);
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
await repository.saveChat(chat);
|
|
3199
|
+
console.log("[ChatManagement] Saved AI response to storage for chatId:", currentChatIdValue);
|
|
3200
|
+
if (displayedChatId2 === currentChatIdValue) {
|
|
3201
|
+
await reloadMessages(currentChatIdValue);
|
|
3202
|
+
}
|
|
3203
|
+
} catch (error) {
|
|
3204
|
+
console.error("[ChatManagement] Failed to save AI response:", error);
|
|
3205
|
+
}
|
|
3206
|
+
}, [repository, reloadMessages]);
|
|
3207
|
+
const initializedRef = useRef5(false);
|
|
3208
|
+
useEffect5(() => {
|
|
3209
|
+
if (currentChatId === null && pendingChatId === null && !initializedRef.current) {
|
|
3210
|
+
initializedRef.current = true;
|
|
3211
|
+
(async () => {
|
|
3212
|
+
try {
|
|
3213
|
+
const chats = await repository.listChats({ limit: 1 });
|
|
3214
|
+
if (chats.length > 0) {
|
|
3215
|
+
const mostRecentChatId = chats[0].id;
|
|
3216
|
+
console.log("[ChatManagement] Loading most recent chat on mount:", mostRecentChatId);
|
|
3217
|
+
const loadedMessages = await loadChatMessages(mostRecentChatId);
|
|
3218
|
+
setCurrentChatId(mostRecentChatId);
|
|
3219
|
+
setMessages(loadedMessages);
|
|
3220
|
+
if (clientRef.current) {
|
|
3221
|
+
clientRef.current.setThreadId(mostRecentChatId);
|
|
3222
|
+
clientRef.current.loadMessages(transformMessagesToClientFormat(loadedMessages));
|
|
3223
|
+
console.log("[ChatManagement] Set threadId and loaded messages for chat:", mostRecentChatId);
|
|
3224
|
+
}
|
|
3225
|
+
console.log("[ChatManagement] Loaded and activated chat on mount:", mostRecentChatId);
|
|
3226
|
+
} else {
|
|
3227
|
+
console.log("[ChatManagement] No existing chats, creating new one");
|
|
3228
|
+
await createNewChat();
|
|
3229
|
+
}
|
|
3230
|
+
} catch (err) {
|
|
3231
|
+
console.error("[ChatManagement] Failed to initialize chat:", err);
|
|
3232
|
+
initializedRef.current = false;
|
|
3233
|
+
}
|
|
3234
|
+
})();
|
|
3235
|
+
}
|
|
3236
|
+
}, [currentChatId, pendingChatId, createNewChat, repository, loadChatMessages, clientRef]);
|
|
3237
|
+
const displayedChatId = pendingChatId || currentChatId;
|
|
3238
|
+
return {
|
|
3239
|
+
currentChatId,
|
|
3240
|
+
pendingChatId,
|
|
3241
|
+
messages,
|
|
3242
|
+
displayedChatId,
|
|
3243
|
+
createNewChat,
|
|
3244
|
+
loadChat,
|
|
3245
|
+
deleteChat,
|
|
3246
|
+
listChats,
|
|
3247
|
+
clearCurrentChat,
|
|
3248
|
+
activatePendingChat,
|
|
3249
|
+
saveUserMessage,
|
|
3250
|
+
saveAIResponse,
|
|
3251
|
+
reloadMessages,
|
|
3252
|
+
currentChatIdSnapshot,
|
|
3253
|
+
pendingChatIdSnapshot
|
|
3254
|
+
};
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
// src/hooks/useAgentSelection.ts
|
|
3258
|
+
import { useState as useState6, useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo3 } from "react";
|
|
3259
|
+
function filterAgents(serverAgents, defaultAgentId, visibleAgentIds) {
|
|
3260
|
+
const getDefaultAgentFallback = () => {
|
|
3261
|
+
const defaultAgentInfo = serverAgents.find((a) => a.id === defaultAgentId);
|
|
3262
|
+
return defaultAgentInfo ? [defaultAgentInfo] : serverAgents;
|
|
3263
|
+
};
|
|
3264
|
+
if (visibleAgentIds === void 0) {
|
|
3265
|
+
return serverAgents;
|
|
3266
|
+
}
|
|
3267
|
+
if (visibleAgentIds.length === 0) {
|
|
3268
|
+
console.warn("[AgentSelection] visibleAgentIds is empty array, falling back to default agent");
|
|
3269
|
+
return getDefaultAgentFallback();
|
|
3270
|
+
}
|
|
3271
|
+
const serverAgentMap = new Map(serverAgents.map((a) => [a.id, a]));
|
|
3272
|
+
const matchedAgents = visibleAgentIds.filter((id) => serverAgentMap.has(id)).map((id) => serverAgentMap.get(id));
|
|
3273
|
+
if (matchedAgents.length === 0) {
|
|
3274
|
+
console.warn("[AgentSelection] No agents in visibleAgentIds match server agents, falling back to default agent");
|
|
3275
|
+
return getDefaultAgentFallback();
|
|
3276
|
+
}
|
|
3277
|
+
return matchedAgents;
|
|
3278
|
+
}
|
|
3279
|
+
function useAgentSelection({
|
|
3280
|
+
clientRef,
|
|
3281
|
+
connected,
|
|
3282
|
+
visibleAgentIds
|
|
3283
|
+
}) {
|
|
3284
|
+
const [serverAgents, setServerAgents] = useState6([]);
|
|
3285
|
+
const [defaultAgent, setDefaultAgent] = useState6(null);
|
|
3286
|
+
const [selectedAgent, setSelectedAgent] = useState6(null);
|
|
3287
|
+
const availableAgents = useMemo3(
|
|
3288
|
+
() => filterAgents(serverAgents, defaultAgent, visibleAgentIds),
|
|
3289
|
+
[serverAgents, defaultAgent, visibleAgentIds]
|
|
3290
|
+
);
|
|
3291
|
+
const setAgent = useCallback5((agentId) => {
|
|
3292
|
+
setSelectedAgent(agentId);
|
|
3293
|
+
if (clientRef.current) {
|
|
3294
|
+
clientRef.current.setAgent(agentId);
|
|
3295
|
+
}
|
|
3296
|
+
console.log("[AgentSelection] Agent set to:", agentId ?? "server default");
|
|
3297
|
+
}, [clientRef]);
|
|
3298
|
+
useEffect6(() => {
|
|
3299
|
+
const client = clientRef.current;
|
|
3300
|
+
if (!client || !connected) return;
|
|
3301
|
+
const unsubscribe = client.onAgentsChange((agents, defaultAgentId) => {
|
|
3302
|
+
console.log("[AgentSelection] Received agents:", agents, "default:", defaultAgentId);
|
|
3303
|
+
setServerAgents(agents);
|
|
3304
|
+
setDefaultAgent(defaultAgentId);
|
|
3305
|
+
});
|
|
3306
|
+
return unsubscribe;
|
|
3307
|
+
}, [clientRef, connected]);
|
|
3308
|
+
useEffect6(() => {
|
|
3309
|
+
if (selectedAgent === null && availableAgents.length > 0 && !availableAgents.some((a) => a.id === defaultAgent)) {
|
|
3310
|
+
const firstAgentId = availableAgents[0].id;
|
|
3311
|
+
setAgent(firstAgentId);
|
|
3312
|
+
}
|
|
3313
|
+
}, [availableAgents, selectedAgent, defaultAgent, setAgent]);
|
|
3314
|
+
return {
|
|
3315
|
+
availableAgents,
|
|
3316
|
+
defaultAgent,
|
|
3317
|
+
selectedAgent,
|
|
3318
|
+
setAgent
|
|
3319
|
+
};
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
// src/hooks/useCommandManagement.ts
|
|
3323
|
+
import { useState as useState7, useCallback as useCallback6, useRef as useRef6, useEffect as useEffect7 } from "react";
|
|
3324
|
+
|
|
3325
|
+
// src/commands/LocalStorageCommandRepository.ts
|
|
3326
|
+
var STORAGE_KEY_PREFIX2 = "use-ai:command:";
|
|
3327
|
+
var STORAGE_INDEX_KEY2 = "use-ai:command-index";
|
|
3328
|
+
var DEFAULT_MAX_COMMANDS = 50;
|
|
3329
|
+
var LocalStorageCommandRepository = class {
|
|
3330
|
+
storage;
|
|
3331
|
+
maxCommands;
|
|
3332
|
+
constructor(storage = localStorage, maxCommands = DEFAULT_MAX_COMMANDS) {
|
|
3333
|
+
this.storage = storage;
|
|
3334
|
+
this.maxCommands = maxCommands;
|
|
3335
|
+
}
|
|
3336
|
+
async createCommand(options) {
|
|
3337
|
+
const name = options.name.trim();
|
|
3338
|
+
const existing = await this.loadCommandByName(name);
|
|
3339
|
+
if (existing) {
|
|
3340
|
+
throw new Error(`Command "${name}" already exists`);
|
|
3341
|
+
}
|
|
3342
|
+
const id = generateCommandId();
|
|
3343
|
+
const command = {
|
|
3344
|
+
id,
|
|
3345
|
+
name,
|
|
3346
|
+
text: options.text,
|
|
3347
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
3348
|
+
};
|
|
3349
|
+
await this.enforceMaxCommandsLimit();
|
|
3350
|
+
this.saveCommandToStorage(command);
|
|
3351
|
+
await this.addToIndex(id);
|
|
3352
|
+
return id;
|
|
3353
|
+
}
|
|
3354
|
+
async loadCommand(id) {
|
|
3355
|
+
try {
|
|
3356
|
+
const data = this.storage.getItem(`${STORAGE_KEY_PREFIX2}${id}`);
|
|
3357
|
+
if (!data) return null;
|
|
3358
|
+
return this.deserializeCommand(data);
|
|
3359
|
+
} catch {
|
|
3360
|
+
return null;
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
async loadCommandByName(name) {
|
|
3364
|
+
const trimmedName = name.trim();
|
|
3365
|
+
const commands = await this.listCommands();
|
|
3366
|
+
return commands.find((c) => c.name === trimmedName) || null;
|
|
3367
|
+
}
|
|
3368
|
+
async updateCommand(command) {
|
|
3369
|
+
this.saveCommandToStorage(command);
|
|
3370
|
+
}
|
|
3371
|
+
async deleteCommand(id) {
|
|
3372
|
+
this.storage.removeItem(`${STORAGE_KEY_PREFIX2}${id}`);
|
|
3373
|
+
await this.removeFromIndex(id);
|
|
3374
|
+
}
|
|
3375
|
+
async listCommands(options) {
|
|
3376
|
+
const ids = this.getIndex();
|
|
3377
|
+
const commands = [];
|
|
3378
|
+
for (const id of ids) {
|
|
3379
|
+
const cmd = await this.loadCommand(id);
|
|
3380
|
+
if (cmd) {
|
|
3381
|
+
if (!options?.namePrefix || cmd.name.startsWith(options.namePrefix.toLowerCase())) {
|
|
3382
|
+
commands.push(cmd);
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
commands.sort((a, b) => {
|
|
3387
|
+
if (a.lastUsedAt && b.lastUsedAt) {
|
|
3388
|
+
return b.lastUsedAt.getTime() - a.lastUsedAt.getTime();
|
|
3389
|
+
}
|
|
3390
|
+
if (a.lastUsedAt) return -1;
|
|
3391
|
+
if (b.lastUsedAt) return 1;
|
|
3392
|
+
return a.name.localeCompare(b.name);
|
|
3393
|
+
});
|
|
3394
|
+
return options?.limit ? commands.slice(0, options.limit) : commands;
|
|
3395
|
+
}
|
|
3396
|
+
async deleteAll() {
|
|
3397
|
+
const ids = this.getIndex();
|
|
3398
|
+
for (const id of ids) {
|
|
3399
|
+
this.storage.removeItem(`${STORAGE_KEY_PREFIX2}${id}`);
|
|
3400
|
+
}
|
|
3401
|
+
this.storage.removeItem(STORAGE_INDEX_KEY2);
|
|
3402
|
+
}
|
|
3403
|
+
saveCommandToStorage(command) {
|
|
3404
|
+
this.storage.setItem(
|
|
3405
|
+
`${STORAGE_KEY_PREFIX2}${command.id}`,
|
|
3406
|
+
JSON.stringify(command)
|
|
3407
|
+
);
|
|
3408
|
+
}
|
|
3409
|
+
deserializeCommand(data) {
|
|
3410
|
+
const parsed = JSON.parse(data);
|
|
3411
|
+
parsed.createdAt = new Date(parsed.createdAt);
|
|
3412
|
+
if (parsed.lastUsedAt) {
|
|
3413
|
+
parsed.lastUsedAt = new Date(parsed.lastUsedAt);
|
|
3414
|
+
}
|
|
3415
|
+
return parsed;
|
|
3416
|
+
}
|
|
3417
|
+
getIndex() {
|
|
3418
|
+
const data = this.storage.getItem(STORAGE_INDEX_KEY2);
|
|
3419
|
+
return data ? JSON.parse(data) : [];
|
|
3420
|
+
}
|
|
3421
|
+
async addToIndex(id) {
|
|
3422
|
+
const index = this.getIndex();
|
|
3423
|
+
if (!index.includes(id)) {
|
|
3424
|
+
index.push(id);
|
|
3425
|
+
this.storage.setItem(STORAGE_INDEX_KEY2, JSON.stringify(index));
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
async removeFromIndex(id) {
|
|
3429
|
+
const index = this.getIndex();
|
|
3430
|
+
this.storage.setItem(
|
|
3431
|
+
STORAGE_INDEX_KEY2,
|
|
3432
|
+
JSON.stringify(index.filter((i) => i !== id))
|
|
3433
|
+
);
|
|
3434
|
+
}
|
|
3435
|
+
async enforceMaxCommandsLimit() {
|
|
3436
|
+
const commands = await this.listCommands();
|
|
3437
|
+
if (commands.length >= this.maxCommands) {
|
|
3438
|
+
const sorted = [...commands].sort(
|
|
3439
|
+
(a, b) => a.createdAt.getTime() - b.createdAt.getTime()
|
|
3440
|
+
);
|
|
3441
|
+
const numToDelete = commands.length - this.maxCommands + 1;
|
|
3442
|
+
for (let i = 0; i < numToDelete; i++) {
|
|
3443
|
+
await this.deleteCommand(sorted[i].id);
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
};
|
|
3448
|
+
|
|
3449
|
+
// src/hooks/useCommandManagement.ts
|
|
3450
|
+
function useCommandManagement({
|
|
3451
|
+
repository
|
|
3452
|
+
} = {}) {
|
|
3453
|
+
const repositoryRef = useRef6(
|
|
3454
|
+
repository || new LocalStorageCommandRepository()
|
|
3455
|
+
);
|
|
3456
|
+
const [commands, setCommands] = useState7([]);
|
|
3457
|
+
const refreshCommands = useCallback6(async () => {
|
|
3458
|
+
try {
|
|
3459
|
+
const cmdList = await repositoryRef.current.listCommands();
|
|
3460
|
+
setCommands(cmdList);
|
|
3461
|
+
console.log("[CommandManagement] Loaded", cmdList.length, "commands");
|
|
3462
|
+
} catch (err) {
|
|
3463
|
+
console.error("[CommandManagement] Failed to load commands:", err);
|
|
3464
|
+
}
|
|
3465
|
+
}, []);
|
|
3466
|
+
const saveCommand = useCallback6(async (name, text) => {
|
|
3467
|
+
const id = await repositoryRef.current.createCommand({ name, text });
|
|
3468
|
+
await refreshCommands();
|
|
3469
|
+
console.log("[CommandManagement] Saved command:", name);
|
|
3470
|
+
return id;
|
|
3471
|
+
}, [refreshCommands]);
|
|
3472
|
+
const renameCommand = useCallback6(async (id, newName) => {
|
|
3473
|
+
const command = await repositoryRef.current.loadCommand(id);
|
|
3474
|
+
if (!command) throw new Error(`Command ${id} not found`);
|
|
3475
|
+
command.name = newName.trim();
|
|
3476
|
+
await repositoryRef.current.updateCommand(command);
|
|
3477
|
+
await refreshCommands();
|
|
3478
|
+
console.log("[CommandManagement] Renamed command:", id, "to", newName);
|
|
3479
|
+
}, [refreshCommands]);
|
|
3480
|
+
const deleteCommand = useCallback6(async (id) => {
|
|
3481
|
+
await repositoryRef.current.deleteCommand(id);
|
|
3482
|
+
await refreshCommands();
|
|
3483
|
+
console.log("[CommandManagement] Deleted command:", id);
|
|
3484
|
+
}, [refreshCommands]);
|
|
3485
|
+
useEffect7(() => {
|
|
3486
|
+
refreshCommands();
|
|
3487
|
+
}, [refreshCommands]);
|
|
3488
|
+
return {
|
|
3489
|
+
commands,
|
|
3490
|
+
refreshCommands,
|
|
3491
|
+
saveCommand,
|
|
3492
|
+
renameCommand,
|
|
3493
|
+
deleteCommand
|
|
3494
|
+
};
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
// src/hooks/useToolRegistry.ts
|
|
3498
|
+
import { useState as useState8, useCallback as useCallback7, useRef as useRef7, useMemo as useMemo4 } from "react";
|
|
3499
|
+
function useToolRegistry() {
|
|
3500
|
+
const toolRegistryRef = useRef7(/* @__PURE__ */ new Map());
|
|
3501
|
+
const [toolRegistryVersion, setToolRegistryVersion] = useState8(0);
|
|
3502
|
+
const toolOwnershipRef = useRef7(/* @__PURE__ */ new Map());
|
|
3503
|
+
const invisibleRef = useRef7(/* @__PURE__ */ new Set());
|
|
3504
|
+
const registerTools = useCallback7((id, tools, options) => {
|
|
3505
|
+
const existingTools = toolRegistryRef.current.get(id);
|
|
3506
|
+
toolRegistryRef.current.set(id, tools);
|
|
3507
|
+
if (existingTools) {
|
|
3508
|
+
const existingKeys = Object.keys(existingTools).sort().join(",");
|
|
3509
|
+
const newKeys = Object.keys(tools).sort().join(",");
|
|
3510
|
+
if (existingKeys !== newKeys) {
|
|
3511
|
+
setToolRegistryVersion((v) => v + 1);
|
|
3512
|
+
}
|
|
3513
|
+
} else {
|
|
3514
|
+
setToolRegistryVersion((v) => v + 1);
|
|
3515
|
+
}
|
|
3516
|
+
Object.keys(tools).forEach((toolName) => {
|
|
3517
|
+
toolOwnershipRef.current.set(toolName, id);
|
|
3518
|
+
});
|
|
3519
|
+
if (options?.invisible) {
|
|
3520
|
+
invisibleRef.current.add(id);
|
|
3521
|
+
} else {
|
|
3522
|
+
invisibleRef.current.delete(id);
|
|
3523
|
+
}
|
|
3524
|
+
}, []);
|
|
3525
|
+
const unregisterTools = useCallback7((id) => {
|
|
3526
|
+
const tools = toolRegistryRef.current.get(id);
|
|
3527
|
+
if (tools) {
|
|
3528
|
+
Object.keys(tools).forEach((toolName) => {
|
|
3529
|
+
toolOwnershipRef.current.delete(toolName);
|
|
3530
|
+
});
|
|
3531
|
+
}
|
|
3532
|
+
toolRegistryRef.current.delete(id);
|
|
3533
|
+
setToolRegistryVersion((v) => v + 1);
|
|
3534
|
+
invisibleRef.current.delete(id);
|
|
3535
|
+
}, []);
|
|
3536
|
+
const isInvisible = useCallback7((id) => {
|
|
3537
|
+
return invisibleRef.current.has(id);
|
|
3538
|
+
}, []);
|
|
3539
|
+
const aggregatedTools = useMemo4(() => {
|
|
3540
|
+
const tools = {};
|
|
3541
|
+
toolRegistryRef.current.forEach((toolSet) => {
|
|
3542
|
+
Object.assign(tools, toolSet);
|
|
3543
|
+
});
|
|
3544
|
+
return tools;
|
|
3545
|
+
}, [toolRegistryVersion]);
|
|
3546
|
+
const hasTools = toolRegistryRef.current.size > 0;
|
|
3547
|
+
const aggregatedToolsRef = useRef7(aggregatedTools);
|
|
3548
|
+
aggregatedToolsRef.current = aggregatedTools;
|
|
3549
|
+
return {
|
|
3550
|
+
registerTools,
|
|
3551
|
+
unregisterTools,
|
|
3552
|
+
isInvisible,
|
|
3553
|
+
aggregatedTools,
|
|
3554
|
+
hasTools,
|
|
3555
|
+
aggregatedToolsRef,
|
|
3556
|
+
toolOwnershipRef
|
|
3557
|
+
};
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
// src/hooks/usePromptState.ts
|
|
3561
|
+
import { useState as useState9, useCallback as useCallback8, useRef as useRef8, useMemo as useMemo5, useEffect as useEffect8 } from "react";
|
|
3562
|
+
function usePromptState({
|
|
3563
|
+
systemPrompt,
|
|
3564
|
+
clientRef,
|
|
3565
|
+
connected
|
|
3566
|
+
}) {
|
|
3567
|
+
const promptsRef = useRef8(/* @__PURE__ */ new Map());
|
|
3568
|
+
const suggestionsRef = useRef8(/* @__PURE__ */ new Map());
|
|
3569
|
+
const waitersRef = useRef8(/* @__PURE__ */ new Map());
|
|
3570
|
+
const [suggestionsVersion, setSuggestionsVersion] = useState9(0);
|
|
3571
|
+
const buildStateFromPrompts = useCallback8(() => {
|
|
3572
|
+
const promptParts = [];
|
|
3573
|
+
if (systemPrompt) {
|
|
3574
|
+
promptParts.push(systemPrompt);
|
|
3575
|
+
}
|
|
3576
|
+
for (const [, prompt] of promptsRef.current.entries()) {
|
|
3577
|
+
if (prompt) {
|
|
3578
|
+
promptParts.push(prompt);
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
return promptParts.length > 0 ? { context: promptParts.join("\n\n---\n\n") } : null;
|
|
3582
|
+
}, [systemPrompt]);
|
|
3583
|
+
useEffect8(() => {
|
|
3584
|
+
if (connected && clientRef.current && systemPrompt) {
|
|
3585
|
+
clientRef.current.updateState(buildStateFromPrompts());
|
|
3586
|
+
}
|
|
3587
|
+
}, [connected, clientRef, systemPrompt, buildStateFromPrompts]);
|
|
3588
|
+
const updatePrompt = useCallback8((id, prompt, suggestions) => {
|
|
3589
|
+
if (prompt) {
|
|
3590
|
+
promptsRef.current.set(id, prompt);
|
|
3591
|
+
} else {
|
|
3592
|
+
promptsRef.current.delete(id);
|
|
3593
|
+
}
|
|
3594
|
+
const hadSuggestions = suggestionsRef.current.has(id);
|
|
3595
|
+
if (suggestions && suggestions.length > 0) {
|
|
3596
|
+
suggestionsRef.current.set(id, suggestions);
|
|
3597
|
+
if (!hadSuggestions) setSuggestionsVersion((v) => v + 1);
|
|
3598
|
+
} else {
|
|
3599
|
+
suggestionsRef.current.delete(id);
|
|
3600
|
+
if (hadSuggestions) setSuggestionsVersion((v) => v + 1);
|
|
3601
|
+
}
|
|
3602
|
+
if (clientRef.current) {
|
|
3603
|
+
clientRef.current.updateState(buildStateFromPrompts());
|
|
3604
|
+
}
|
|
3605
|
+
}, [buildStateFromPrompts, clientRef, connected]);
|
|
3606
|
+
const registerWaiter = useCallback8((id, waiter) => {
|
|
3607
|
+
waitersRef.current.set(id, waiter);
|
|
3608
|
+
}, []);
|
|
3609
|
+
const unregisterWaiter = useCallback8((id) => {
|
|
3610
|
+
waitersRef.current.delete(id);
|
|
3611
|
+
}, []);
|
|
3612
|
+
const getWaiter = useCallback8((id) => {
|
|
3613
|
+
return waitersRef.current.get(id);
|
|
3614
|
+
}, []);
|
|
3615
|
+
const aggregatedSuggestions = useMemo5(() => {
|
|
3616
|
+
const allSuggestions = [];
|
|
3617
|
+
suggestionsRef.current.forEach((suggestions) => {
|
|
3618
|
+
allSuggestions.push(...suggestions);
|
|
3619
|
+
});
|
|
3620
|
+
return allSuggestions;
|
|
3621
|
+
}, [suggestionsVersion]);
|
|
3622
|
+
return {
|
|
3623
|
+
updatePrompt,
|
|
3624
|
+
registerWaiter,
|
|
3625
|
+
unregisterWaiter,
|
|
3626
|
+
getWaiter,
|
|
3627
|
+
aggregatedSuggestions,
|
|
3628
|
+
promptsRef
|
|
3629
|
+
};
|
|
3630
|
+
}
|
|
3631
|
+
|
|
3632
|
+
// src/providers/useAIProvider.tsx
|
|
3633
|
+
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3634
|
+
var __UseAIContext = createContext4(null);
|
|
3635
|
+
var hasWarnedAboutMissingProvider = false;
|
|
3636
|
+
var noOpContextValue = {
|
|
3637
|
+
serverUrl: "",
|
|
3638
|
+
connected: false,
|
|
3639
|
+
client: null,
|
|
3640
|
+
tools: {
|
|
3641
|
+
register: () => {
|
|
3642
|
+
},
|
|
3643
|
+
unregister: () => {
|
|
3644
|
+
}
|
|
3645
|
+
},
|
|
3646
|
+
prompts: {
|
|
3647
|
+
update: () => {
|
|
3648
|
+
},
|
|
3649
|
+
registerWaiter: () => {
|
|
3650
|
+
},
|
|
3651
|
+
unregisterWaiter: () => {
|
|
3652
|
+
}
|
|
3653
|
+
},
|
|
3654
|
+
chat: {
|
|
3655
|
+
currentId: null,
|
|
3656
|
+
create: async () => "",
|
|
3657
|
+
load: async () => {
|
|
3658
|
+
},
|
|
3659
|
+
delete: async () => {
|
|
3660
|
+
},
|
|
3661
|
+
list: async () => [],
|
|
3662
|
+
clear: async () => {
|
|
3663
|
+
}
|
|
3664
|
+
},
|
|
3665
|
+
agents: {
|
|
3666
|
+
available: [],
|
|
3667
|
+
default: null,
|
|
3668
|
+
selected: null,
|
|
3669
|
+
set: () => {
|
|
3670
|
+
}
|
|
3671
|
+
},
|
|
3672
|
+
commands: {
|
|
3673
|
+
list: [],
|
|
3674
|
+
refresh: async () => {
|
|
3675
|
+
},
|
|
3676
|
+
save: async () => "",
|
|
3677
|
+
rename: async () => {
|
|
3678
|
+
},
|
|
3679
|
+
delete: async () => {
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
};
|
|
3683
|
+
var DEFAULT_FILE_UPLOAD_CONFIG = {
|
|
3684
|
+
backend: new EmbedFileUploadBackend(),
|
|
3685
|
+
maxFileSize: 10 * 1024 * 1024,
|
|
3686
|
+
// 10MB
|
|
3687
|
+
acceptedTypes: ["image/*", "application/pdf"]
|
|
3688
|
+
};
|
|
3689
|
+
function UseAIProvider({
|
|
3690
|
+
serverUrl,
|
|
3691
|
+
children,
|
|
3692
|
+
systemPrompt,
|
|
3693
|
+
CustomButton,
|
|
3694
|
+
CustomChat,
|
|
3695
|
+
chatRepository,
|
|
3696
|
+
mcpHeadersProvider,
|
|
3697
|
+
fileUploadConfig: fileUploadConfigProp,
|
|
3698
|
+
commandRepository,
|
|
3699
|
+
renderChat = true,
|
|
3700
|
+
theme: customTheme,
|
|
3701
|
+
strings: customStrings,
|
|
3702
|
+
visibleAgentIds
|
|
3703
|
+
}) {
|
|
3704
|
+
const fileUploadConfig = fileUploadConfigProp === false ? void 0 : fileUploadConfigProp ?? DEFAULT_FILE_UPLOAD_CONFIG;
|
|
3705
|
+
const theme = { ...defaultTheme, ...customTheme };
|
|
3706
|
+
const strings = { ...defaultStrings, ...customStrings };
|
|
3707
|
+
const [connected, setConnected] = useState10(false);
|
|
3708
|
+
const [isChatOpen, setIsChatOpen] = useState10(false);
|
|
3709
|
+
const [loading, setLoading] = useState10(false);
|
|
3710
|
+
const [streamingText, setStreamingText] = useState10("");
|
|
3711
|
+
const streamingChatIdRef = useRef9(null);
|
|
3712
|
+
const clientRef = useRef9(null);
|
|
3713
|
+
const repositoryRef = useRef9(
|
|
3714
|
+
chatRepository || new LocalStorageChatRepository()
|
|
3715
|
+
);
|
|
3716
|
+
const {
|
|
3717
|
+
registerTools,
|
|
3718
|
+
unregisterTools,
|
|
3719
|
+
isInvisible,
|
|
3720
|
+
aggregatedTools,
|
|
3721
|
+
hasTools,
|
|
3722
|
+
aggregatedToolsRef,
|
|
3723
|
+
toolOwnershipRef
|
|
3724
|
+
} = useToolRegistry();
|
|
3725
|
+
const {
|
|
3726
|
+
updatePrompt,
|
|
3727
|
+
registerWaiter,
|
|
3728
|
+
unregisterWaiter,
|
|
3729
|
+
getWaiter,
|
|
3730
|
+
aggregatedSuggestions,
|
|
3731
|
+
promptsRef
|
|
3732
|
+
} = usePromptState({
|
|
3733
|
+
systemPrompt,
|
|
3734
|
+
clientRef,
|
|
3735
|
+
connected
|
|
3736
|
+
});
|
|
3737
|
+
const chatManagement = useChatManagement({
|
|
3738
|
+
repository: repositoryRef.current,
|
|
3739
|
+
clientRef
|
|
3740
|
+
});
|
|
3741
|
+
const {
|
|
3742
|
+
currentChatId,
|
|
3743
|
+
pendingChatId,
|
|
3744
|
+
messages,
|
|
3745
|
+
displayedChatId,
|
|
3746
|
+
createNewChat,
|
|
3747
|
+
loadChat,
|
|
3748
|
+
deleteChat,
|
|
3749
|
+
listChats,
|
|
3750
|
+
clearCurrentChat,
|
|
3751
|
+
activatePendingChat,
|
|
3752
|
+
saveUserMessage,
|
|
3753
|
+
saveAIResponse
|
|
3754
|
+
} = chatManagement;
|
|
3755
|
+
const {
|
|
3756
|
+
availableAgents,
|
|
3757
|
+
defaultAgent,
|
|
3758
|
+
selectedAgent,
|
|
3759
|
+
setAgent
|
|
3760
|
+
} = useAgentSelection({ clientRef, connected, visibleAgentIds });
|
|
3761
|
+
const {
|
|
3762
|
+
commands,
|
|
3763
|
+
refreshCommands,
|
|
3764
|
+
saveCommand,
|
|
3765
|
+
renameCommand,
|
|
3766
|
+
deleteCommand
|
|
3767
|
+
} = useCommandManagement({ repository: commandRepository });
|
|
3768
|
+
useEffect9(() => {
|
|
3769
|
+
console.log("[UseAIProvider] Initializing client with serverUrl:", serverUrl);
|
|
3770
|
+
const client = new UseAIClient(serverUrl);
|
|
3771
|
+
if (mcpHeadersProvider) {
|
|
3772
|
+
client.setMcpHeadersProvider(mcpHeadersProvider);
|
|
3773
|
+
}
|
|
3774
|
+
const unsubscribeConnection = client.onConnectionStateChange((isConnected) => {
|
|
3775
|
+
console.log("[UseAIProvider] Connection state changed:", isConnected);
|
|
3776
|
+
setConnected(isConnected);
|
|
3777
|
+
});
|
|
3778
|
+
console.log("[UseAIProvider] Connecting...");
|
|
3779
|
+
client.connect();
|
|
3780
|
+
const unsubscribe = client.onEvent("globalChat", async (event) => {
|
|
3781
|
+
if (event.type === EventType.TOOL_CALL_END) {
|
|
3782
|
+
const toolCallEnd = event;
|
|
3783
|
+
const toolCallId = toolCallEnd.toolCallId;
|
|
3784
|
+
const toolCallData = client["currentToolCalls"].get(toolCallId);
|
|
3785
|
+
if (!toolCallData) {
|
|
3786
|
+
console.error(`[Provider] Tool call ${toolCallId} not found`);
|
|
3787
|
+
return;
|
|
3788
|
+
}
|
|
3789
|
+
const name = toolCallData.name;
|
|
3790
|
+
const input = JSON.parse(toolCallData.args);
|
|
3791
|
+
if (!aggregatedToolsRef.current[name]) {
|
|
3792
|
+
console.log(`[Provider] Tool "${name}" not found in useAI tools, skipping (likely a workflow tool)`);
|
|
3793
|
+
return;
|
|
3794
|
+
}
|
|
3795
|
+
try {
|
|
3796
|
+
const ownerId = toolOwnershipRef.current.get(name);
|
|
3797
|
+
console.log(`[useAI] Tool "${name}" owned by component:`, ownerId);
|
|
3798
|
+
console.log("[useAI] Executing tool...");
|
|
3799
|
+
const result = await executeDefinedTool(aggregatedToolsRef.current, name, input);
|
|
3800
|
+
const isErrorResult = result && typeof result === "object" && ("error" in result || result.success === false);
|
|
3801
|
+
const ownerIsInvisible = ownerId ? isInvisible(ownerId) : false;
|
|
3802
|
+
if (ownerId && !isErrorResult && !ownerIsInvisible) {
|
|
3803
|
+
const waiter = getWaiter(ownerId);
|
|
3804
|
+
if (waiter) {
|
|
3805
|
+
console.log(`[useAI] Waiting for prompt change from ${ownerId}...`);
|
|
3806
|
+
await waiter();
|
|
3807
|
+
console.log("[useAI] Prompt change wait complete");
|
|
3808
|
+
}
|
|
3809
|
+
} else if (isErrorResult) {
|
|
3810
|
+
console.log("[useAI] Tool returned error, skipping prompt wait");
|
|
3811
|
+
} else if (ownerIsInvisible) {
|
|
3812
|
+
console.log("[useAI] Component is invisible, skipping prompt wait");
|
|
3813
|
+
}
|
|
3814
|
+
let updatedState = null;
|
|
3815
|
+
if (ownerId) {
|
|
3816
|
+
const prompt = promptsRef.current.get(ownerId);
|
|
3817
|
+
if (prompt) {
|
|
3818
|
+
updatedState = { context: prompt };
|
|
3819
|
+
console.log(`[useAI] Updated state from ${ownerId}`);
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
client.sendToolResponse(toolCallId, result, updatedState);
|
|
3823
|
+
} catch (err) {
|
|
3824
|
+
console.error("Tool execution error:", err);
|
|
3825
|
+
client.sendToolResponse(toolCallId, {
|
|
3826
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
3827
|
+
});
|
|
3828
|
+
}
|
|
3829
|
+
} else if (event.type === EventType.TEXT_MESSAGE_CONTENT) {
|
|
3830
|
+
const contentEvent = event;
|
|
3831
|
+
setStreamingText((prev) => prev + contentEvent.delta);
|
|
3832
|
+
} else if (event.type === EventType.TEXT_MESSAGE_END) {
|
|
3833
|
+
const content = client.currentMessageContent;
|
|
3834
|
+
if (content) {
|
|
3835
|
+
console.log("[Provider] Received text message:", content.substring(0, 100));
|
|
3836
|
+
saveAIResponse(content);
|
|
3837
|
+
setStreamingText("");
|
|
3838
|
+
streamingChatIdRef.current = null;
|
|
3839
|
+
setLoading(false);
|
|
3840
|
+
}
|
|
3841
|
+
} else if (event.type === EventType.RUN_ERROR) {
|
|
3842
|
+
const errorEvent = event;
|
|
3843
|
+
const errorCode = errorEvent.message;
|
|
3844
|
+
console.error("[Provider] Run error:", errorCode);
|
|
3845
|
+
const userMessage = strings.errors[errorCode] || strings.errors[ErrorCode.UNKNOWN_ERROR];
|
|
3846
|
+
saveAIResponse(userMessage, "error");
|
|
3847
|
+
setStreamingText("");
|
|
3848
|
+
streamingChatIdRef.current = null;
|
|
3849
|
+
setLoading(false);
|
|
3850
|
+
}
|
|
3851
|
+
});
|
|
3852
|
+
clientRef.current = client;
|
|
3853
|
+
return () => {
|
|
3854
|
+
unsubscribeConnection();
|
|
3855
|
+
unsubscribe();
|
|
3856
|
+
client.disconnect();
|
|
3857
|
+
};
|
|
3858
|
+
}, [serverUrl]);
|
|
3859
|
+
useEffect9(() => {
|
|
3860
|
+
const client = clientRef.current;
|
|
3861
|
+
if (!client) return;
|
|
3862
|
+
if (mcpHeadersProvider) {
|
|
3863
|
+
client.setMcpHeadersProvider(mcpHeadersProvider);
|
|
3864
|
+
}
|
|
3865
|
+
}, [mcpHeadersProvider]);
|
|
3866
|
+
const lastRegisteredToolsRef = useRef9("");
|
|
3867
|
+
useEffect9(() => {
|
|
3868
|
+
const client = clientRef.current;
|
|
3869
|
+
if (!client || !client.isConnected() || !hasTools) return;
|
|
3870
|
+
const toolKeys = Object.keys(aggregatedTools).sort().join(",");
|
|
3871
|
+
if (toolKeys === lastRegisteredToolsRef.current) {
|
|
3872
|
+
console.log("[Provider] Skipping re-registration, tools unchanged");
|
|
3873
|
+
return;
|
|
3874
|
+
}
|
|
3875
|
+
lastRegisteredToolsRef.current = toolKeys;
|
|
3876
|
+
console.log("[Provider] Registering tools:", toolKeys);
|
|
3877
|
+
try {
|
|
3878
|
+
const toolDefinitions = convertToolsToDefinitions(aggregatedTools);
|
|
3879
|
+
console.log(`[Provider] Registering ${toolDefinitions.length} tools`);
|
|
3880
|
+
client.registerTools(toolDefinitions);
|
|
3881
|
+
} catch (err) {
|
|
3882
|
+
console.error("Failed to register tools:", err);
|
|
3883
|
+
}
|
|
3884
|
+
}, [hasTools, aggregatedTools, connected]);
|
|
3885
|
+
const handleSendMessage = useCallback9(async (message, attachments) => {
|
|
3886
|
+
if (!clientRef.current) return;
|
|
3887
|
+
setStreamingText("");
|
|
3888
|
+
const activatedChatId = activatePendingChat();
|
|
3889
|
+
const activeChatId = activatedChatId || currentChatId;
|
|
3890
|
+
streamingChatIdRef.current = activeChatId;
|
|
3891
|
+
let persistedContent = message;
|
|
3892
|
+
let multimodalContent;
|
|
3893
|
+
if (attachments && attachments.length > 0) {
|
|
3894
|
+
const backend = fileUploadConfig?.backend ?? new EmbedFileUploadBackend();
|
|
3895
|
+
const persistedParts = [];
|
|
3896
|
+
if (message.trim()) {
|
|
3897
|
+
persistedParts.push({ type: "text", text: message });
|
|
3898
|
+
}
|
|
3899
|
+
for (const attachment of attachments) {
|
|
3900
|
+
persistedParts.push({
|
|
3901
|
+
type: "file",
|
|
3902
|
+
file: {
|
|
3903
|
+
name: attachment.file.name,
|
|
3904
|
+
size: attachment.file.size,
|
|
3905
|
+
mimeType: attachment.file.type
|
|
3906
|
+
}
|
|
3907
|
+
});
|
|
3908
|
+
}
|
|
3909
|
+
persistedContent = persistedParts;
|
|
3910
|
+
const contentParts = [];
|
|
3911
|
+
if (message.trim()) {
|
|
3912
|
+
contentParts.push({ type: "text", text: message });
|
|
3913
|
+
}
|
|
3914
|
+
for (const attachment of attachments) {
|
|
3915
|
+
try {
|
|
3916
|
+
const url = await backend.prepareForSend(attachment.file);
|
|
3917
|
+
if (attachment.file.type.startsWith("image/")) {
|
|
3918
|
+
contentParts.push({ type: "image", url });
|
|
3919
|
+
} else {
|
|
3920
|
+
contentParts.push({
|
|
3921
|
+
type: "file",
|
|
3922
|
+
url,
|
|
3923
|
+
mimeType: attachment.file.type,
|
|
3924
|
+
name: attachment.file.name
|
|
3925
|
+
});
|
|
3926
|
+
}
|
|
3927
|
+
} catch (error) {
|
|
3928
|
+
console.error("[Provider] Failed to prepare file for send:", error);
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
multimodalContent = contentParts;
|
|
3932
|
+
}
|
|
3933
|
+
if (activeChatId) {
|
|
3934
|
+
await saveUserMessage(activeChatId, persistedContent);
|
|
3935
|
+
}
|
|
3936
|
+
setLoading(true);
|
|
3937
|
+
await clientRef.current.sendPrompt(message, multimodalContent);
|
|
3938
|
+
}, [activatePendingChat, currentChatId, saveUserMessage, fileUploadConfig]);
|
|
3939
|
+
const value = {
|
|
3940
|
+
serverUrl,
|
|
3941
|
+
connected,
|
|
3942
|
+
client: clientRef.current,
|
|
3943
|
+
tools: {
|
|
3944
|
+
register: registerTools,
|
|
3945
|
+
unregister: unregisterTools
|
|
3946
|
+
},
|
|
3947
|
+
prompts: {
|
|
3948
|
+
update: updatePrompt,
|
|
3949
|
+
registerWaiter,
|
|
3950
|
+
unregisterWaiter
|
|
3951
|
+
},
|
|
3952
|
+
chat: {
|
|
3953
|
+
currentId: currentChatId,
|
|
3954
|
+
create: createNewChat,
|
|
3955
|
+
load: loadChat,
|
|
3956
|
+
delete: deleteChat,
|
|
3957
|
+
list: listChats,
|
|
3958
|
+
clear: clearCurrentChat
|
|
3959
|
+
},
|
|
3960
|
+
agents: {
|
|
3961
|
+
available: availableAgents,
|
|
3962
|
+
default: defaultAgent,
|
|
3963
|
+
selected: selectedAgent,
|
|
3964
|
+
set: setAgent
|
|
3965
|
+
},
|
|
3966
|
+
commands: {
|
|
3967
|
+
list: commands,
|
|
3968
|
+
refresh: refreshCommands,
|
|
3969
|
+
save: saveCommand,
|
|
3970
|
+
rename: renameCommand,
|
|
3971
|
+
delete: deleteCommand
|
|
3972
|
+
}
|
|
3973
|
+
};
|
|
3974
|
+
const effectiveStreamingText = streamingChatIdRef.current === displayedChatId ? streamingText : "";
|
|
3975
|
+
const chatUIContextValue = {
|
|
3976
|
+
connected,
|
|
3977
|
+
loading,
|
|
3978
|
+
sendMessage: handleSendMessage,
|
|
3979
|
+
messages,
|
|
3980
|
+
streamingText: effectiveStreamingText,
|
|
3981
|
+
suggestions: aggregatedSuggestions,
|
|
3982
|
+
fileUploadConfig,
|
|
3983
|
+
history: {
|
|
3984
|
+
currentId: displayedChatId,
|
|
3985
|
+
create: createNewChat,
|
|
3986
|
+
load: loadChat,
|
|
3987
|
+
delete: deleteChat,
|
|
3988
|
+
list: listChats
|
|
3989
|
+
},
|
|
3990
|
+
agents: {
|
|
3991
|
+
available: availableAgents,
|
|
3992
|
+
default: defaultAgent,
|
|
3993
|
+
selected: selectedAgent,
|
|
3994
|
+
set: setAgent
|
|
3995
|
+
},
|
|
3996
|
+
commands: {
|
|
3997
|
+
list: commands,
|
|
3998
|
+
save: saveCommand,
|
|
3999
|
+
rename: renameCommand,
|
|
4000
|
+
delete: deleteCommand
|
|
4001
|
+
},
|
|
4002
|
+
ui: {
|
|
4003
|
+
isOpen: isChatOpen,
|
|
4004
|
+
setOpen: setIsChatOpen
|
|
4005
|
+
}
|
|
4006
|
+
};
|
|
4007
|
+
const isUIDisabled = CustomButton === null || CustomChat === null;
|
|
4008
|
+
const ButtonComponent = isUIDisabled ? null : CustomButton || UseAIFloatingButton;
|
|
4009
|
+
const hasCustomChat = CustomChat !== void 0 && CustomChat !== null;
|
|
4010
|
+
const chatPanelProps = {
|
|
4011
|
+
onSendMessage: handleSendMessage,
|
|
4012
|
+
messages,
|
|
4013
|
+
loading,
|
|
4014
|
+
connected,
|
|
4015
|
+
streamingText: effectiveStreamingText,
|
|
4016
|
+
currentChatId: displayedChatId,
|
|
4017
|
+
onNewChat: createNewChat,
|
|
4018
|
+
onLoadChat: loadChat,
|
|
4019
|
+
onDeleteChat: deleteChat,
|
|
4020
|
+
onListChats: listChats,
|
|
4021
|
+
suggestions: aggregatedSuggestions,
|
|
4022
|
+
availableAgents,
|
|
4023
|
+
defaultAgent,
|
|
4024
|
+
selectedAgent,
|
|
4025
|
+
onAgentChange: setAgent,
|
|
4026
|
+
fileUploadConfig,
|
|
4027
|
+
commands,
|
|
4028
|
+
onSaveCommand: saveCommand,
|
|
4029
|
+
onRenameCommand: renameCommand,
|
|
4030
|
+
onDeleteCommand: deleteCommand
|
|
4031
|
+
};
|
|
4032
|
+
const renderDefaultChat = () => {
|
|
4033
|
+
if (isUIDisabled) return null;
|
|
4034
|
+
return /* @__PURE__ */ jsx11(UseAIFloatingChatWrapper, { isOpen: isChatOpen, onClose: () => setIsChatOpen(false), children: /* @__PURE__ */ jsx11(
|
|
4035
|
+
UseAIChatPanel,
|
|
4036
|
+
{
|
|
4037
|
+
...chatPanelProps,
|
|
4038
|
+
closeButton: /* @__PURE__ */ jsx11(CloseButton, { onClick: () => setIsChatOpen(false) })
|
|
4039
|
+
}
|
|
4040
|
+
) });
|
|
4041
|
+
};
|
|
4042
|
+
const renderCustomChat = () => {
|
|
4043
|
+
if (!CustomChat) return null;
|
|
4044
|
+
return /* @__PURE__ */ jsx11(
|
|
4045
|
+
CustomChat,
|
|
4046
|
+
{
|
|
4047
|
+
isOpen: isChatOpen,
|
|
4048
|
+
onClose: () => setIsChatOpen(false),
|
|
4049
|
+
onSendMessage: handleSendMessage,
|
|
4050
|
+
messages,
|
|
4051
|
+
loading,
|
|
4052
|
+
connected,
|
|
4053
|
+
suggestions: aggregatedSuggestions,
|
|
4054
|
+
availableAgents,
|
|
4055
|
+
defaultAgent,
|
|
4056
|
+
selectedAgent,
|
|
4057
|
+
onAgentChange: setAgent
|
|
4058
|
+
}
|
|
4059
|
+
);
|
|
4060
|
+
};
|
|
4061
|
+
const renderBuiltInChat = () => {
|
|
4062
|
+
if (!renderChat) return null;
|
|
4063
|
+
return /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
4064
|
+
ButtonComponent && /* @__PURE__ */ jsx11(
|
|
4065
|
+
ButtonComponent,
|
|
4066
|
+
{
|
|
4067
|
+
onClick: () => setIsChatOpen(true),
|
|
4068
|
+
connected
|
|
4069
|
+
}
|
|
4070
|
+
),
|
|
4071
|
+
hasCustomChat ? renderCustomChat() : renderDefaultChat()
|
|
4072
|
+
] });
|
|
4073
|
+
};
|
|
4074
|
+
return /* @__PURE__ */ jsx11(ThemeContext.Provider, { value: theme, children: /* @__PURE__ */ jsx11(StringsContext.Provider, { value: strings, children: /* @__PURE__ */ jsx11(__UseAIContext.Provider, { value, children: /* @__PURE__ */ jsxs7(__UseAIChatContext.Provider, { value: chatUIContextValue, children: [
|
|
4075
|
+
children,
|
|
4076
|
+
renderBuiltInChat()
|
|
4077
|
+
] }) }) }) });
|
|
4078
|
+
}
|
|
4079
|
+
function useAIContext() {
|
|
4080
|
+
const context = useContext4(__UseAIContext);
|
|
4081
|
+
if (!context) {
|
|
4082
|
+
if (!hasWarnedAboutMissingProvider) {
|
|
4083
|
+
console.warn(
|
|
4084
|
+
"[use-ai] useAI hook used without UseAIProvider. AI features will be disabled. Wrap your app in <UseAIProvider> to enable AI features."
|
|
4085
|
+
);
|
|
4086
|
+
hasWarnedAboutMissingProvider = true;
|
|
4087
|
+
}
|
|
4088
|
+
return noOpContextValue;
|
|
4089
|
+
}
|
|
4090
|
+
return context;
|
|
4091
|
+
}
|
|
4092
|
+
|
|
4093
|
+
// src/hooks/useStableTools.ts
|
|
4094
|
+
import { useRef as useRef10 } from "react";
|
|
4095
|
+
function useStableTools(tools) {
|
|
4096
|
+
const latestToolsRef = useRef10({});
|
|
4097
|
+
const stableToolsRef = useRef10({});
|
|
4098
|
+
const prevToolNamesRef = useRef10("");
|
|
4099
|
+
if (!tools) {
|
|
4100
|
+
latestToolsRef.current = {};
|
|
4101
|
+
return void 0;
|
|
4102
|
+
}
|
|
4103
|
+
latestToolsRef.current = tools;
|
|
4104
|
+
const currentToolNames = Object.keys(tools).sort().join(",");
|
|
4105
|
+
if (currentToolNames !== prevToolNamesRef.current) {
|
|
4106
|
+
prevToolNamesRef.current = currentToolNames;
|
|
4107
|
+
stableToolsRef.current = {};
|
|
4108
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
4109
|
+
stableToolsRef.current[name] = createStableToolWrapper(
|
|
4110
|
+
name,
|
|
4111
|
+
tool,
|
|
4112
|
+
latestToolsRef
|
|
4113
|
+
);
|
|
4114
|
+
}
|
|
4115
|
+
} else {
|
|
4116
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
4117
|
+
const stable = stableToolsRef.current[name];
|
|
4118
|
+
if (stable) {
|
|
4119
|
+
stable.description = tool.description;
|
|
4120
|
+
stable._jsonSchema = tool._jsonSchema;
|
|
4121
|
+
stable._zodSchema = tool._zodSchema;
|
|
4122
|
+
stable._options = tool._options;
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
return stableToolsRef.current;
|
|
4127
|
+
}
|
|
4128
|
+
function createStableToolWrapper(name, tool, latestToolsRef) {
|
|
4129
|
+
const stableHandler = (input) => {
|
|
4130
|
+
const currentTool = latestToolsRef.current[name];
|
|
4131
|
+
if (!currentTool) {
|
|
4132
|
+
throw new Error(`Tool "${name}" no longer exists`);
|
|
4133
|
+
}
|
|
4134
|
+
return currentTool.fn(input);
|
|
4135
|
+
};
|
|
4136
|
+
const stableExecute = async (input) => {
|
|
4137
|
+
const currentTool = latestToolsRef.current[name];
|
|
4138
|
+
if (!currentTool) {
|
|
4139
|
+
throw new Error(`Tool "${name}" no longer exists`);
|
|
4140
|
+
}
|
|
4141
|
+
return await currentTool._execute(input);
|
|
4142
|
+
};
|
|
4143
|
+
return {
|
|
4144
|
+
description: tool.description,
|
|
4145
|
+
_jsonSchema: tool._jsonSchema,
|
|
4146
|
+
_zodSchema: tool._zodSchema,
|
|
4147
|
+
fn: stableHandler,
|
|
4148
|
+
_options: tool._options,
|
|
4149
|
+
_toToolDefinition: tool._toToolDefinition.bind(tool),
|
|
4150
|
+
_execute: stableExecute
|
|
4151
|
+
};
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
// src/useAI.ts
|
|
4155
|
+
function namespaceTools(tools, namespace) {
|
|
4156
|
+
const namespacedTools = {};
|
|
4157
|
+
for (const [toolName, tool] of Object.entries(tools)) {
|
|
4158
|
+
const namespacedName = `${namespace}_${toolName}`;
|
|
4159
|
+
namespacedTools[namespacedName] = tool;
|
|
4160
|
+
}
|
|
4161
|
+
return namespacedTools;
|
|
4162
|
+
}
|
|
4163
|
+
function useAI(options = {}) {
|
|
4164
|
+
const { enabled = true } = options;
|
|
4165
|
+
const { connected, tools, client, prompts } = useAIContext();
|
|
4166
|
+
const { register: registerTools, unregister: unregisterTools } = tools;
|
|
4167
|
+
const { update: updatePrompt, registerWaiter, unregisterWaiter } = prompts;
|
|
4168
|
+
const [response, setResponse] = useState11(null);
|
|
4169
|
+
const [loading, setLoading] = useState11(false);
|
|
4170
|
+
const [error, setError] = useState11(null);
|
|
4171
|
+
const hookId = useRef11(`useAI-${Math.random().toString(36).substr(2, 9)}`);
|
|
4172
|
+
const toolsRef = useRef11({});
|
|
4173
|
+
const componentRef = useRef11(null);
|
|
4174
|
+
const promptChangeResolvers = useRef11([]);
|
|
4175
|
+
const stableTools = useStableTools(options.tools);
|
|
4176
|
+
const toolsKey = useMemo6(() => {
|
|
4177
|
+
if (!options.tools) return "";
|
|
4178
|
+
return Object.keys(options.tools).sort().join(",");
|
|
4179
|
+
}, [options.tools]);
|
|
4180
|
+
const memoizedSuggestions = useMemo6(() => options.suggestions, [options.suggestions]);
|
|
4181
|
+
useEffect10(() => {
|
|
4182
|
+
if (componentRef.current) {
|
|
4183
|
+
componentRef.current.setAttribute("data-useai-context", "true");
|
|
4184
|
+
}
|
|
4185
|
+
}, []);
|
|
4186
|
+
const waitForPromptChange = useCallback10(() => {
|
|
4187
|
+
return new Promise((resolve) => {
|
|
4188
|
+
const timeoutMs = 100;
|
|
4189
|
+
const timeoutId = setTimeout(() => {
|
|
4190
|
+
const index = promptChangeResolvers.current.indexOf(resolveAndCleanup);
|
|
4191
|
+
if (index !== -1) {
|
|
4192
|
+
promptChangeResolvers.current.splice(index, 1);
|
|
4193
|
+
}
|
|
4194
|
+
resolve();
|
|
4195
|
+
}, timeoutMs);
|
|
4196
|
+
const resolveAndCleanup = () => {
|
|
4197
|
+
clearTimeout(timeoutId);
|
|
4198
|
+
resolve();
|
|
4199
|
+
};
|
|
4200
|
+
promptChangeResolvers.current.push(resolveAndCleanup);
|
|
4201
|
+
});
|
|
4202
|
+
}, []);
|
|
4203
|
+
useEffect10(() => {
|
|
4204
|
+
if (!enabled || options.invisible) return;
|
|
4205
|
+
registerWaiter(hookId.current, waitForPromptChange);
|
|
4206
|
+
return () => {
|
|
4207
|
+
unregisterWaiter(hookId.current);
|
|
4208
|
+
};
|
|
4209
|
+
}, [enabled, options.invisible, registerWaiter, unregisterWaiter, waitForPromptChange]);
|
|
4210
|
+
useEffect10(() => {
|
|
4211
|
+
if (!enabled) return;
|
|
4212
|
+
updatePrompt(hookId.current, options.prompt, memoizedSuggestions);
|
|
4213
|
+
if (promptChangeResolvers.current.length > 0) {
|
|
4214
|
+
promptChangeResolvers.current.forEach((resolve) => resolve());
|
|
4215
|
+
promptChangeResolvers.current = [];
|
|
4216
|
+
}
|
|
4217
|
+
}, [enabled, options.prompt, memoizedSuggestions, updatePrompt]);
|
|
4218
|
+
const updatePromptRef = useRef11(updatePrompt);
|
|
4219
|
+
updatePromptRef.current = updatePrompt;
|
|
4220
|
+
useEffect10(() => {
|
|
4221
|
+
const id = hookId.current;
|
|
4222
|
+
return () => {
|
|
4223
|
+
updatePromptRef.current(id, void 0, void 0);
|
|
4224
|
+
};
|
|
4225
|
+
}, []);
|
|
4226
|
+
useEffect10(() => {
|
|
4227
|
+
if (!enabled) return;
|
|
4228
|
+
if (stableTools) {
|
|
4229
|
+
const componentId = options.id || componentRef.current?.id;
|
|
4230
|
+
const toolsToRegister = componentId ? namespaceTools(stableTools, componentId) : stableTools;
|
|
4231
|
+
registerTools(hookId.current, toolsToRegister, { invisible: options.invisible });
|
|
4232
|
+
toolsRef.current = toolsToRegister;
|
|
4233
|
+
}
|
|
4234
|
+
return () => {
|
|
4235
|
+
if (stableTools) {
|
|
4236
|
+
unregisterTools(hookId.current);
|
|
4237
|
+
}
|
|
4238
|
+
};
|
|
4239
|
+
}, [enabled, toolsKey, stableTools, options.id, options.invisible, registerTools, unregisterTools]);
|
|
4240
|
+
useEffect10(() => {
|
|
4241
|
+
if (!enabled || !client) return;
|
|
4242
|
+
const unsubscribe = client.onEvent(hookId.current, (event) => {
|
|
4243
|
+
handleAGUIEvent(event);
|
|
4244
|
+
});
|
|
4245
|
+
return () => {
|
|
4246
|
+
unsubscribe();
|
|
4247
|
+
};
|
|
4248
|
+
}, [enabled, client]);
|
|
4249
|
+
const handleAGUIEvent = useCallback10(async (event) => {
|
|
4250
|
+
switch (event.type) {
|
|
4251
|
+
case EventType.TEXT_MESSAGE_END: {
|
|
4252
|
+
const content = client?.currentMessageContent;
|
|
4253
|
+
if (content) {
|
|
4254
|
+
setResponse(content);
|
|
4255
|
+
setLoading(false);
|
|
4256
|
+
}
|
|
4257
|
+
break;
|
|
4258
|
+
}
|
|
4259
|
+
case EventType.RUN_ERROR: {
|
|
4260
|
+
const errorEvent = event;
|
|
4261
|
+
const error2 = new Error(errorEvent.message);
|
|
4262
|
+
setError(error2);
|
|
4263
|
+
setLoading(false);
|
|
4264
|
+
options.onError?.(error2);
|
|
4265
|
+
break;
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
}, [client, options.onError]);
|
|
4269
|
+
const generate = useCallback10(async (prompt) => {
|
|
4270
|
+
if (!enabled) {
|
|
4271
|
+
const error2 = new Error("AI features are disabled");
|
|
4272
|
+
setError(error2);
|
|
4273
|
+
options.onError?.(error2);
|
|
4274
|
+
return;
|
|
4275
|
+
}
|
|
4276
|
+
if (!client?.isConnected()) {
|
|
4277
|
+
const error2 = new Error("Not connected to server");
|
|
4278
|
+
setError(error2);
|
|
4279
|
+
options.onError?.(error2);
|
|
4280
|
+
return;
|
|
4281
|
+
}
|
|
4282
|
+
setLoading(true);
|
|
4283
|
+
setError(null);
|
|
4284
|
+
setResponse(null);
|
|
4285
|
+
try {
|
|
4286
|
+
client.sendPrompt(prompt);
|
|
4287
|
+
} catch (err) {
|
|
4288
|
+
const error2 = err instanceof Error ? err : new Error("Unknown error");
|
|
4289
|
+
setError(error2);
|
|
4290
|
+
setLoading(false);
|
|
4291
|
+
options.onError?.(error2);
|
|
4292
|
+
}
|
|
4293
|
+
}, [enabled, client, options.onError]);
|
|
4294
|
+
return {
|
|
4295
|
+
response,
|
|
4296
|
+
loading,
|
|
4297
|
+
error,
|
|
4298
|
+
generate,
|
|
4299
|
+
connected: enabled && connected,
|
|
4300
|
+
ref: componentRef
|
|
4301
|
+
};
|
|
4302
|
+
}
|
|
4303
|
+
|
|
4304
|
+
// src/useAIWorkflow.ts
|
|
4305
|
+
import { useState as useState12, useCallback as useCallback11, useRef as useRef12, useEffect as useEffect11 } from "react";
|
|
4306
|
+
import { EventType as EventType3 } from "@meetsmore-oss/use-ai-core";
|
|
4307
|
+
import { v4 as uuidv43 } from "uuid";
|
|
4308
|
+
function useAIWorkflow(runner, workflowId) {
|
|
4309
|
+
const { connected, client } = useAIContext();
|
|
4310
|
+
const [status, setStatus] = useState12("idle");
|
|
4311
|
+
const [text, setText] = useState12(null);
|
|
4312
|
+
const [error, setError] = useState12(null);
|
|
4313
|
+
const currentWorkflowRef = useRef12(null);
|
|
4314
|
+
const eventListenerIdRef = useRef12(`useAIWorkflow-${Math.random().toString(36).substr(2, 9)}`);
|
|
4315
|
+
const handleWorkflowEvent = useCallback11(async (event) => {
|
|
4316
|
+
const currentWorkflow = currentWorkflowRef.current;
|
|
4317
|
+
if (!currentWorkflow) return;
|
|
4318
|
+
if (event.type === EventType3.RUN_STARTED) {
|
|
4319
|
+
const runEvent = event;
|
|
4320
|
+
if (runEvent.runId !== currentWorkflow.runId) return;
|
|
4321
|
+
}
|
|
4322
|
+
switch (event.type) {
|
|
4323
|
+
case EventType3.TEXT_MESSAGE_CONTENT: {
|
|
4324
|
+
const textEvent = event;
|
|
4325
|
+
currentWorkflow.accumulatedText += textEvent.delta;
|
|
4326
|
+
setText(currentWorkflow.accumulatedText);
|
|
4327
|
+
currentWorkflow.onProgress?.({
|
|
4328
|
+
status: "running",
|
|
4329
|
+
text: currentWorkflow.accumulatedText,
|
|
4330
|
+
toolCalls: currentWorkflow.toolCalls
|
|
4331
|
+
});
|
|
4332
|
+
break;
|
|
4333
|
+
}
|
|
4334
|
+
case EventType3.TOOL_CALL_END: {
|
|
4335
|
+
if (!client) break;
|
|
4336
|
+
const toolCallEvent = event;
|
|
4337
|
+
const toolCallId = toolCallEvent.toolCallId;
|
|
4338
|
+
const toolCallData = client.getToolCallData(toolCallId);
|
|
4339
|
+
if (!toolCallData) {
|
|
4340
|
+
console.error(`[useAIWorkflow] Tool call ${toolCallId} not found`);
|
|
4341
|
+
break;
|
|
4342
|
+
}
|
|
4343
|
+
const toolName = toolCallData.name;
|
|
4344
|
+
const toolArgs = JSON.parse(toolCallData.args);
|
|
4345
|
+
console.log(`[useAIWorkflow] Executing tool: ${toolName}`, toolArgs);
|
|
4346
|
+
console.log(`[useAIWorkflow] Available tools:`, Object.keys(currentWorkflow.tools));
|
|
4347
|
+
try {
|
|
4348
|
+
const result = await executeDefinedTool(currentWorkflow.tools, toolName, toolArgs);
|
|
4349
|
+
currentWorkflow.toolCalls.push({
|
|
4350
|
+
toolName,
|
|
4351
|
+
args: toolArgs,
|
|
4352
|
+
result
|
|
4353
|
+
});
|
|
4354
|
+
currentWorkflow.onProgress?.({
|
|
4355
|
+
status: "running",
|
|
4356
|
+
text: currentWorkflow.accumulatedText,
|
|
4357
|
+
toolCalls: currentWorkflow.toolCalls
|
|
4358
|
+
});
|
|
4359
|
+
client.sendToolResponse(toolCallId, result);
|
|
4360
|
+
} catch (err) {
|
|
4361
|
+
console.error("[useAIWorkflow] Tool execution error:", err);
|
|
4362
|
+
client.sendToolResponse(toolCallId, {
|
|
4363
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
4364
|
+
});
|
|
4365
|
+
}
|
|
4366
|
+
break;
|
|
4367
|
+
}
|
|
4368
|
+
case EventType3.RUN_FINISHED: {
|
|
4369
|
+
setStatus("completed");
|
|
4370
|
+
const result = {
|
|
4371
|
+
text: currentWorkflow.accumulatedText,
|
|
4372
|
+
toolCalls: currentWorkflow.toolCalls
|
|
4373
|
+
};
|
|
4374
|
+
currentWorkflow.onProgress?.({
|
|
4375
|
+
status: "completed",
|
|
4376
|
+
text: currentWorkflow.accumulatedText,
|
|
4377
|
+
toolCalls: currentWorkflow.toolCalls
|
|
4378
|
+
});
|
|
4379
|
+
currentWorkflow.onComplete?.(result);
|
|
4380
|
+
currentWorkflowRef.current = null;
|
|
4381
|
+
break;
|
|
4382
|
+
}
|
|
4383
|
+
case EventType3.RUN_ERROR: {
|
|
4384
|
+
const errorEvent = event;
|
|
4385
|
+
const err = new Error(errorEvent.message);
|
|
4386
|
+
setError(err);
|
|
4387
|
+
setStatus("error");
|
|
4388
|
+
currentWorkflow.onProgress?.({
|
|
4389
|
+
status: "error",
|
|
4390
|
+
error: errorEvent.message,
|
|
4391
|
+
text: currentWorkflow.accumulatedText,
|
|
4392
|
+
toolCalls: currentWorkflow.toolCalls
|
|
4393
|
+
});
|
|
4394
|
+
currentWorkflow.onError?.(err);
|
|
4395
|
+
currentWorkflowRef.current = null;
|
|
4396
|
+
break;
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
}, [client]);
|
|
4400
|
+
useEffect11(() => {
|
|
4401
|
+
if (!client) return;
|
|
4402
|
+
const unsubscribe = client.onEvent(eventListenerIdRef.current, handleWorkflowEvent);
|
|
4403
|
+
return () => {
|
|
4404
|
+
unsubscribe();
|
|
4405
|
+
};
|
|
4406
|
+
}, [client, handleWorkflowEvent]);
|
|
4407
|
+
const trigger = useCallback11(async (options) => {
|
|
4408
|
+
if (!client?.isConnected()) {
|
|
4409
|
+
const err = new Error("Not connected to server");
|
|
4410
|
+
setError(err);
|
|
4411
|
+
options.onError?.(err);
|
|
4412
|
+
return;
|
|
4413
|
+
}
|
|
4414
|
+
if (currentWorkflowRef.current !== null) {
|
|
4415
|
+
const err = new Error("A workflow is already running. Wait for it to complete before triggering a new one.");
|
|
4416
|
+
setError(err);
|
|
4417
|
+
setStatus("error");
|
|
4418
|
+
options.onError?.(err);
|
|
4419
|
+
return;
|
|
4420
|
+
}
|
|
4421
|
+
setStatus("running");
|
|
4422
|
+
setError(null);
|
|
4423
|
+
setText(null);
|
|
4424
|
+
const runId = uuidv43();
|
|
4425
|
+
const threadId = uuidv43();
|
|
4426
|
+
currentWorkflowRef.current = {
|
|
4427
|
+
runId,
|
|
4428
|
+
threadId,
|
|
4429
|
+
tools: options.tools || {},
|
|
4430
|
+
onProgress: options.onProgress,
|
|
4431
|
+
onComplete: options.onComplete,
|
|
4432
|
+
onError: options.onError,
|
|
4433
|
+
accumulatedText: "",
|
|
4434
|
+
toolCalls: []
|
|
4435
|
+
};
|
|
4436
|
+
const toolDefinitions = options.tools ? convertToolsToDefinitions(options.tools) : [];
|
|
4437
|
+
const message = {
|
|
4438
|
+
type: "run_workflow",
|
|
4439
|
+
data: {
|
|
4440
|
+
runner,
|
|
4441
|
+
workflowId,
|
|
4442
|
+
inputs: options.inputs,
|
|
4443
|
+
tools: toolDefinitions,
|
|
4444
|
+
runId,
|
|
4445
|
+
threadId
|
|
4446
|
+
}
|
|
4447
|
+
};
|
|
4448
|
+
console.log("[useAIWorkflow] Sending run_workflow message:", message);
|
|
4449
|
+
client.send(message);
|
|
4450
|
+
options.onProgress?.({
|
|
4451
|
+
status: "running"
|
|
4452
|
+
});
|
|
4453
|
+
}, [client, handleWorkflowEvent, runner, workflowId]);
|
|
4454
|
+
return {
|
|
4455
|
+
trigger,
|
|
4456
|
+
status,
|
|
4457
|
+
text,
|
|
4458
|
+
error,
|
|
4459
|
+
connected
|
|
4460
|
+
};
|
|
4461
|
+
}
|
|
4462
|
+
|
|
4463
|
+
// src/index.ts
|
|
4464
|
+
import { z as z2 } from "zod";
|
|
4465
|
+
export {
|
|
4466
|
+
CloseButton,
|
|
4467
|
+
DEFAULT_MAX_FILE_SIZE,
|
|
4468
|
+
EmbedFileUploadBackend,
|
|
4469
|
+
LocalStorageChatRepository,
|
|
4470
|
+
LocalStorageCommandRepository,
|
|
4471
|
+
UseAIChat,
|
|
4472
|
+
UseAIChatPanel,
|
|
4473
|
+
UseAIClient,
|
|
4474
|
+
UseAIFloatingButton,
|
|
4475
|
+
UseAIFloatingChatWrapper,
|
|
4476
|
+
UseAIProvider,
|
|
4477
|
+
convertToolsToDefinitions,
|
|
4478
|
+
defaultStrings,
|
|
4479
|
+
defaultTheme,
|
|
4480
|
+
defineTool,
|
|
4481
|
+
executeDefinedTool,
|
|
4482
|
+
generateChatId,
|
|
4483
|
+
generateCommandId,
|
|
4484
|
+
generateMessageId,
|
|
4485
|
+
useAI,
|
|
4486
|
+
useAIContext,
|
|
4487
|
+
useAIWorkflow,
|
|
4488
|
+
useAgentSelection,
|
|
4489
|
+
useChatManagement,
|
|
4490
|
+
useCommandManagement,
|
|
4491
|
+
useDropdownState,
|
|
4492
|
+
useFileUpload,
|
|
4493
|
+
usePromptState,
|
|
4494
|
+
useSlashCommands,
|
|
4495
|
+
useStableTools,
|
|
4496
|
+
useStrings,
|
|
4497
|
+
useTheme,
|
|
4498
|
+
useToolRegistry,
|
|
4499
|
+
validateCommandName,
|
|
4500
|
+
z2 as z
|
|
4501
|
+
};
|
|
4502
|
+
//# sourceMappingURL=index.js.map
|