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