@meetsmore-oss/use-ai-client 1.2.4 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  generateChatId,
3
3
  generateMessageId
4
- } from "./chunk-EGKUR4C7.js";
4
+ } from "./chunk-QTQR7MAU.js";
5
5
 
6
6
  // src/useAI.ts
7
7
  import { useState as useState11, useEffect as useEffect10, useRef as useRef11, useCallback as useCallback10, useMemo as useMemo6 } from "react";
@@ -378,8 +378,131 @@ function MarkdownContent({ content }) {
378
378
  );
379
379
  }
380
380
 
381
- // src/components/FileChip.tsx
381
+ // src/components/Spinner.tsx
382
382
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
383
+ function Spinner({
384
+ size = 16,
385
+ color = "currentColor",
386
+ trackColor,
387
+ strokeWidth = 2
388
+ }) {
389
+ const radius = 10;
390
+ const circumference = 2 * Math.PI * radius;
391
+ return /* @__PURE__ */ jsxs2(
392
+ "svg",
393
+ {
394
+ "data-testid": "spinner",
395
+ width: size,
396
+ height: size,
397
+ viewBox: "0 0 24 24",
398
+ style: {
399
+ animation: "use-ai-spin 1s linear infinite"
400
+ },
401
+ children: [
402
+ /* @__PURE__ */ jsx3("style", { children: `
403
+ @keyframes use-ai-spin {
404
+ from { transform: rotate(0deg); }
405
+ to { transform: rotate(360deg); }
406
+ }
407
+ ` }),
408
+ /* @__PURE__ */ jsx3(
409
+ "circle",
410
+ {
411
+ cx: "12",
412
+ cy: "12",
413
+ r: radius,
414
+ fill: "none",
415
+ stroke: trackColor || color,
416
+ strokeWidth,
417
+ opacity: trackColor ? 1 : 0.25
418
+ }
419
+ ),
420
+ /* @__PURE__ */ jsx3(
421
+ "circle",
422
+ {
423
+ cx: "12",
424
+ cy: "12",
425
+ r: radius,
426
+ fill: "none",
427
+ stroke: color,
428
+ strokeWidth,
429
+ strokeLinecap: "round",
430
+ strokeDasharray: circumference,
431
+ strokeDashoffset: circumference * 0.75,
432
+ style: {
433
+ transformOrigin: "center"
434
+ }
435
+ }
436
+ )
437
+ ]
438
+ }
439
+ );
440
+ }
441
+
442
+ // src/components/ProgressBar.tsx
443
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
444
+ function ProgressBar({
445
+ progress,
446
+ size = 16,
447
+ color = "currentColor",
448
+ trackColor,
449
+ strokeWidth = 2
450
+ }) {
451
+ const clampedProgress = Math.min(100, Math.max(0, progress));
452
+ const radius = 10;
453
+ const circumference = 2 * Math.PI * radius;
454
+ const strokeDashoffset = circumference * (1 - clampedProgress / 100);
455
+ return /* @__PURE__ */ jsxs3(
456
+ "svg",
457
+ {
458
+ "data-testid": "progress-bar",
459
+ role: "progressbar",
460
+ "aria-valuenow": clampedProgress,
461
+ "aria-valuemin": 0,
462
+ "aria-valuemax": 100,
463
+ width: size,
464
+ height: size,
465
+ viewBox: "0 0 24 24",
466
+ style: {
467
+ transform: "rotate(-90deg)"
468
+ },
469
+ children: [
470
+ /* @__PURE__ */ jsx4(
471
+ "circle",
472
+ {
473
+ cx: "12",
474
+ cy: "12",
475
+ r: radius,
476
+ fill: "none",
477
+ stroke: trackColor || color,
478
+ strokeWidth,
479
+ opacity: trackColor ? 1 : 0.25
480
+ }
481
+ ),
482
+ /* @__PURE__ */ jsx4(
483
+ "circle",
484
+ {
485
+ cx: "12",
486
+ cy: "12",
487
+ r: radius,
488
+ fill: "none",
489
+ stroke: color,
490
+ strokeWidth,
491
+ strokeLinecap: "round",
492
+ strokeDasharray: circumference,
493
+ strokeDashoffset,
494
+ style: {
495
+ transition: "stroke-dashoffset 0.2s ease"
496
+ }
497
+ }
498
+ )
499
+ ]
500
+ }
501
+ );
502
+ }
503
+
504
+ // src/components/FileChip.tsx
505
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
383
506
  function formatFileSize(bytes) {
384
507
  if (bytes < 1024) return `${bytes} B`;
385
508
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
@@ -394,11 +517,14 @@ function truncateFilename(name, maxLength = 20) {
394
517
  if (maxBaseLength < 5) return name.substring(0, maxLength - 3) + "...";
395
518
  return baseName.substring(0, maxBaseLength) + "..." + ext;
396
519
  }
397
- function FileChip({ attachment, onRemove, disabled }) {
520
+ function FileChip({ attachment, onRemove, disabled, processingState }) {
398
521
  const theme = useTheme();
399
522
  const { file, preview } = attachment;
400
523
  const isImage = file.type.startsWith("image/");
401
- return /* @__PURE__ */ jsxs2(
524
+ const isProcessing = processingState?.status === "processing";
525
+ const hasError = processingState?.status === "error";
526
+ const progress = processingState?.progress;
527
+ return /* @__PURE__ */ jsxs4(
402
528
  "div",
403
529
  {
404
530
  "data-testid": "file-chip",
@@ -411,10 +537,12 @@ function FileChip({ attachment, onRemove, disabled }) {
411
537
  borderRadius: "8px",
412
538
  fontSize: "13px",
413
539
  color: theme.textColor,
414
- maxWidth: "200px"
540
+ maxWidth: "200px",
541
+ position: "relative",
542
+ opacity: isProcessing ? 0.7 : 1
415
543
  },
416
544
  children: [
417
- isImage && preview ? /* @__PURE__ */ jsx3(
545
+ isImage && preview ? /* @__PURE__ */ jsx5(
418
546
  "img",
419
547
  {
420
548
  src: preview,
@@ -426,7 +554,7 @@ function FileChip({ attachment, onRemove, disabled }) {
426
554
  objectFit: "cover"
427
555
  }
428
556
  }
429
- ) : /* @__PURE__ */ jsx3(
557
+ ) : /* @__PURE__ */ jsx5(
430
558
  "div",
431
559
  {
432
560
  style: {
@@ -442,8 +570,8 @@ function FileChip({ attachment, onRemove, disabled }) {
442
570
  children: "\u{1F4CE}"
443
571
  }
444
572
  ),
445
- /* @__PURE__ */ jsxs2("div", { style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: [
446
- /* @__PURE__ */ jsx3(
573
+ /* @__PURE__ */ jsxs4("div", { style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: [
574
+ /* @__PURE__ */ jsx5(
447
575
  "div",
448
576
  {
449
577
  style: {
@@ -456,28 +584,28 @@ function FileChip({ attachment, onRemove, disabled }) {
456
584
  children: truncateFilename(file.name)
457
585
  }
458
586
  ),
459
- /* @__PURE__ */ jsx3("div", { style: { fontSize: "11px", color: theme.secondaryTextColor }, children: formatFileSize(file.size) })
587
+ /* @__PURE__ */ jsx5("div", { style: { fontSize: "11px", color: theme.secondaryTextColor }, children: formatFileSize(file.size) })
460
588
  ] }),
461
- /* @__PURE__ */ jsx3(
589
+ /* @__PURE__ */ jsx5(
462
590
  "button",
463
591
  {
464
592
  "data-testid": "file-chip-remove",
465
593
  onClick: onRemove,
466
- disabled,
594
+ disabled: disabled || isProcessing,
467
595
  style: {
468
596
  background: "transparent",
469
597
  border: "none",
470
598
  padding: "2px 4px",
471
- cursor: disabled ? "not-allowed" : "pointer",
599
+ cursor: disabled || isProcessing ? "not-allowed" : "pointer",
472
600
  color: theme.placeholderTextColor,
473
601
  fontSize: "16px",
474
602
  lineHeight: 1,
475
603
  borderRadius: "4px",
476
604
  transition: "all 0.15s",
477
- opacity: disabled ? 0.5 : 1
605
+ opacity: disabled || isProcessing ? 0.5 : 1
478
606
  },
479
607
  onMouseEnter: (e) => {
480
- if (!disabled) {
608
+ if (!disabled && !isProcessing) {
481
609
  e.currentTarget.style.background = theme.borderColor;
482
610
  e.currentTarget.style.color = theme.textColor;
483
611
  }
@@ -488,6 +616,44 @@ function FileChip({ attachment, onRemove, disabled }) {
488
616
  },
489
617
  children: "\xD7"
490
618
  }
619
+ ),
620
+ isProcessing && /* @__PURE__ */ jsx5(
621
+ "div",
622
+ {
623
+ "data-testid": "file-chip-processing",
624
+ style: {
625
+ position: "absolute",
626
+ inset: 0,
627
+ display: "flex",
628
+ alignItems: "center",
629
+ justifyContent: "center",
630
+ background: "rgba(255, 255, 255, 0.7)",
631
+ borderRadius: "inherit"
632
+ },
633
+ children: progress !== void 0 ? /* @__PURE__ */ jsx5(ProgressBar, { progress, size: 16, color: theme.secondaryTextColor }) : /* @__PURE__ */ jsx5(Spinner, { size: 16, color: theme.secondaryTextColor })
634
+ }
635
+ ),
636
+ hasError && /* @__PURE__ */ jsx5(
637
+ "div",
638
+ {
639
+ "data-testid": "file-chip-error",
640
+ style: {
641
+ position: "absolute",
642
+ bottom: "-2px",
643
+ right: "-2px",
644
+ width: "12px",
645
+ height: "12px",
646
+ borderRadius: "50%",
647
+ background: "#ef4444",
648
+ display: "flex",
649
+ alignItems: "center",
650
+ justifyContent: "center",
651
+ fontSize: "8px",
652
+ color: "white",
653
+ fontWeight: "bold"
654
+ },
655
+ children: "!"
656
+ }
491
657
  )
492
658
  ]
493
659
  }
@@ -495,7 +661,7 @@ function FileChip({ attachment, onRemove, disabled }) {
495
661
  }
496
662
  function FilePlaceholder({ name, size }) {
497
663
  const theme = useTheme();
498
- return /* @__PURE__ */ jsxs2(
664
+ return /* @__PURE__ */ jsxs4(
499
665
  "div",
500
666
  {
501
667
  "data-testid": "file-placeholder",
@@ -512,9 +678,9 @@ function FilePlaceholder({ name, size }) {
512
678
  maxWidth: "200px"
513
679
  },
514
680
  children: [
515
- /* @__PURE__ */ jsx3("span", { children: "\u{1F4CE}" }),
516
- /* @__PURE__ */ jsxs2("div", { style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: [
517
- /* @__PURE__ */ jsx3(
681
+ /* @__PURE__ */ jsx5("span", { children: "\u{1F4CE}" }),
682
+ /* @__PURE__ */ jsxs4("div", { style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: [
683
+ /* @__PURE__ */ jsx5(
518
684
  "div",
519
685
  {
520
686
  style: {
@@ -526,7 +692,7 @@ function FilePlaceholder({ name, size }) {
526
692
  children: truncateFilename(name)
527
693
  }
528
694
  ),
529
- /* @__PURE__ */ jsx3("div", { style: { fontSize: "11px" }, children: formatFileSize(size) })
695
+ /* @__PURE__ */ jsx5("div", { style: { fontSize: "11px" }, children: formatFileSize(size) })
530
696
  ] })
531
697
  ]
532
698
  }
@@ -555,7 +721,7 @@ function validateCommandName(name) {
555
721
 
556
722
  // src/components/CommandAutocomplete.tsx
557
723
  import { useEffect, useRef } from "react";
558
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
724
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
559
725
  var MAX_VISIBLE_ITEMS = 8;
560
726
  function CommandAutocomplete({
561
727
  commands,
@@ -586,7 +752,7 @@ function CommandAutocomplete({
586
752
  }
587
753
  }, [highlightedIndex]);
588
754
  if (filteredCommands.length === 0) {
589
- return /* @__PURE__ */ jsx4(
755
+ return /* @__PURE__ */ jsx6(
590
756
  "div",
591
757
  {
592
758
  "data-testid": "command-autocomplete",
@@ -602,7 +768,7 @@ function CommandAutocomplete({
602
768
  overflow: "hidden",
603
769
  zIndex: 1005
604
770
  },
605
- children: /* @__PURE__ */ jsx4(
771
+ children: /* @__PURE__ */ jsx6(
606
772
  "div",
607
773
  {
608
774
  style: {
@@ -617,7 +783,7 @@ function CommandAutocomplete({
617
783
  }
618
784
  );
619
785
  }
620
- return /* @__PURE__ */ jsx4(
786
+ return /* @__PURE__ */ jsx6(
621
787
  "div",
622
788
  {
623
789
  "data-testid": "command-autocomplete",
@@ -636,7 +802,7 @@ function CommandAutocomplete({
636
802
  overflowY: "auto",
637
803
  zIndex: 1005
638
804
  },
639
- children: filteredCommands.map((cmd, index) => /* @__PURE__ */ jsxs3(
805
+ children: filteredCommands.map((cmd, index) => /* @__PURE__ */ jsxs5(
640
806
  "div",
641
807
  {
642
808
  ref: (el) => {
@@ -656,8 +822,8 @@ function CommandAutocomplete({
656
822
  gap: "8px"
657
823
  },
658
824
  children: [
659
- /* @__PURE__ */ jsxs3("div", { style: { flex: 1, minWidth: 0 }, children: [
660
- /* @__PURE__ */ jsxs3(
825
+ /* @__PURE__ */ jsxs5("div", { style: { flex: 1, minWidth: 0 }, children: [
826
+ /* @__PURE__ */ jsxs5(
661
827
  "div",
662
828
  {
663
829
  style: {
@@ -671,7 +837,7 @@ function CommandAutocomplete({
671
837
  ]
672
838
  }
673
839
  ),
674
- /* @__PURE__ */ jsx4(
840
+ /* @__PURE__ */ jsx6(
675
841
  "div",
676
842
  {
677
843
  style: {
@@ -686,7 +852,7 @@ function CommandAutocomplete({
686
852
  }
687
853
  )
688
854
  ] }),
689
- onDelete && /* @__PURE__ */ jsx4(
855
+ onDelete && /* @__PURE__ */ jsx6(
690
856
  "button",
691
857
  {
692
858
  "data-testid": "command-delete-button",
@@ -717,7 +883,7 @@ function CommandAutocomplete({
717
883
  e.currentTarget.style.background = "transparent";
718
884
  },
719
885
  title: strings.commands.deleteCommand,
720
- 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" }) })
886
+ children: /* @__PURE__ */ jsx6("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx6("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" }) })
721
887
  }
722
888
  )
723
889
  ]
@@ -735,7 +901,7 @@ function getFilteredCommandsCount(commands, searchPrefix) {
735
901
  }
736
902
 
737
903
  // src/hooks/useSlashCommands.tsx
738
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
904
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
739
905
  var MAX_VISIBLE_ITEMS2 = 8;
740
906
  function useSlashCommands({
741
907
  commands,
@@ -870,7 +1036,7 @@ function useSlashCommands({
870
1036
  }
871
1037
  }
872
1038
  }, [savingMessageId, savingMessageText, commandNameInput, commands, onRenameCommand, onSaveCommand, cancelInlineSave, strings]);
873
- const AutocompleteComponent = showAutocomplete && commands.length > 0 ? /* @__PURE__ */ jsx5(
1039
+ const AutocompleteComponent = showAutocomplete && commands.length > 0 ? /* @__PURE__ */ jsx7(
874
1040
  CommandAutocomplete,
875
1041
  {
876
1042
  commands,
@@ -886,7 +1052,7 @@ function useSlashCommands({
886
1052
  if (savingMessageId !== messageId) {
887
1053
  return null;
888
1054
  }
889
- return /* @__PURE__ */ jsxs4(
1055
+ return /* @__PURE__ */ jsxs6(
890
1056
  "div",
891
1057
  {
892
1058
  "data-testid": "inline-save-command",
@@ -900,9 +1066,9 @@ function useSlashCommands({
900
1066
  gap: "4px"
901
1067
  },
902
1068
  children: [
903
- /* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", gap: "6px" }, children: [
904
- /* @__PURE__ */ jsx5("span", { style: { color: theme.primaryColor, fontSize: "13px", fontWeight: 500 }, children: "/" }),
905
- /* @__PURE__ */ jsx5(
1069
+ /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center", gap: "6px" }, children: [
1070
+ /* @__PURE__ */ jsx7("span", { style: { color: theme.primaryColor, fontSize: "13px", fontWeight: 500 }, children: "/" }),
1071
+ /* @__PURE__ */ jsx7(
906
1072
  "input",
907
1073
  {
908
1074
  ref: commandNameInputRef,
@@ -934,7 +1100,7 @@ function useSlashCommands({
934
1100
  }
935
1101
  }
936
1102
  ),
937
- /* @__PURE__ */ jsx5(
1103
+ /* @__PURE__ */ jsx7(
938
1104
  "button",
939
1105
  {
940
1106
  "data-testid": "save-command-confirm",
@@ -952,15 +1118,15 @@ function useSlashCommands({
952
1118
  justifyContent: "center"
953
1119
  },
954
1120
  title: strings.commands.saveCommand,
955
- children: /* @__PURE__ */ jsxs4("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
956
- /* @__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" }),
957
- /* @__PURE__ */ jsx5("polyline", { points: "17 21 17 13 7 13 7 21" }),
958
- /* @__PURE__ */ jsx5("polyline", { points: "7 3 7 8 15 8" })
1121
+ children: /* @__PURE__ */ jsxs6("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1122
+ /* @__PURE__ */ jsx7("path", { d: "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" }),
1123
+ /* @__PURE__ */ jsx7("polyline", { points: "17 21 17 13 7 13 7 21" }),
1124
+ /* @__PURE__ */ jsx7("polyline", { points: "7 3 7 8 15 8" })
959
1125
  ] })
960
1126
  }
961
1127
  )
962
1128
  ] }),
963
- commandSaveError && /* @__PURE__ */ jsx5(
1129
+ commandSaveError && /* @__PURE__ */ jsx7(
964
1130
  "div",
965
1131
  {
966
1132
  "data-testid": "command-save-error",
@@ -995,9 +1161,151 @@ import { useState as useState2, useRef as useRef3, useCallback as useCallback2,
995
1161
  // src/fileUpload/types.ts
996
1162
  var DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
997
1163
 
1164
+ // src/fileUpload/mimeTypeMatcher.ts
1165
+ function matchesMimeType(mimeType, pattern) {
1166
+ if (!pattern.includes("*")) {
1167
+ return mimeType === pattern;
1168
+ }
1169
+ const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
1170
+ const regex = new RegExp(`^${regexPattern}$`);
1171
+ return regex.test(mimeType);
1172
+ }
1173
+ function findTransformer(mimeType, transformers) {
1174
+ if (!transformers) {
1175
+ return void 0;
1176
+ }
1177
+ let bestMatch;
1178
+ let bestIsExact = false;
1179
+ let bestLength = -1;
1180
+ for (const [pattern, transformer] of Object.entries(transformers)) {
1181
+ if (!matchesMimeType(mimeType, pattern)) {
1182
+ continue;
1183
+ }
1184
+ const isExact = !pattern.includes("*");
1185
+ if (isExact && !bestIsExact) {
1186
+ bestMatch = transformer;
1187
+ bestIsExact = true;
1188
+ bestLength = pattern.length;
1189
+ continue;
1190
+ }
1191
+ if (isExact === bestIsExact && pattern.length > bestLength) {
1192
+ bestMatch = transformer;
1193
+ bestLength = pattern.length;
1194
+ }
1195
+ }
1196
+ return bestMatch;
1197
+ }
1198
+
1199
+ // src/fileUpload/EmbedFileUploadBackend.ts
1200
+ var EmbedFileUploadBackend = class {
1201
+ /**
1202
+ * Converts a File to a base64 data URL.
1203
+ *
1204
+ * @param file - The File object to convert
1205
+ * @returns Promise resolving to a base64 data URL (e.g., "data:image/png;base64,...")
1206
+ * @throws Error if file reading fails
1207
+ */
1208
+ async prepareForSend(file) {
1209
+ return new Promise((resolve, reject) => {
1210
+ const reader = new FileReader();
1211
+ reader.onload = () => {
1212
+ if (typeof reader.result === "string") {
1213
+ resolve(reader.result);
1214
+ } else {
1215
+ reject(new Error("Failed to read file as data URL"));
1216
+ }
1217
+ };
1218
+ reader.onerror = () => {
1219
+ reject(new Error(`Failed to read file: ${file.name}`));
1220
+ };
1221
+ reader.readAsDataURL(file);
1222
+ });
1223
+ }
1224
+ };
1225
+
1226
+ // src/fileUpload/processAttachments.ts
1227
+ var transformationCache = /* @__PURE__ */ new Map();
1228
+ function getFileCacheKey(file) {
1229
+ return `${file.name}:${file.size}:${file.lastModified}`;
1230
+ }
1231
+ async function getTransformedContent(file, transformer, context, onProgress) {
1232
+ const cacheKey = getFileCacheKey(file);
1233
+ const cached = transformationCache.get(cacheKey);
1234
+ if (cached !== void 0) {
1235
+ return cached;
1236
+ }
1237
+ const result = await transformer.transform(file, context, onProgress);
1238
+ transformationCache.set(cacheKey, result);
1239
+ return result;
1240
+ }
1241
+ async function processAttachments(attachments, config) {
1242
+ const { getCurrentChat, backend = new EmbedFileUploadBackend(), transformers = {}, onFileProgress } = config;
1243
+ const contentParts = [];
1244
+ const chat = await getCurrentChat();
1245
+ const context = { chat };
1246
+ for (const attachment of attachments) {
1247
+ onFileProgress?.(attachment.id, { status: "processing" });
1248
+ try {
1249
+ if (attachment.transformedContent !== void 0) {
1250
+ contentParts.push({
1251
+ type: "transformed_file",
1252
+ text: attachment.transformedContent,
1253
+ originalFile: {
1254
+ name: attachment.file.name,
1255
+ mimeType: attachment.file.type,
1256
+ size: attachment.file.size
1257
+ }
1258
+ });
1259
+ onFileProgress?.(attachment.id, { status: "done" });
1260
+ continue;
1261
+ }
1262
+ const transformer = findTransformer(attachment.file.type, transformers);
1263
+ if (transformer) {
1264
+ const transformedText = await getTransformedContent(
1265
+ attachment.file,
1266
+ transformer,
1267
+ context,
1268
+ (progress) => {
1269
+ onFileProgress?.(attachment.id, { status: "processing", progress });
1270
+ }
1271
+ );
1272
+ contentParts.push({
1273
+ type: "transformed_file",
1274
+ text: transformedText,
1275
+ originalFile: {
1276
+ name: attachment.file.name,
1277
+ mimeType: attachment.file.type,
1278
+ size: attachment.file.size
1279
+ }
1280
+ });
1281
+ } else {
1282
+ const url = await backend.prepareForSend(attachment.file);
1283
+ if (attachment.file.type.startsWith("image/")) {
1284
+ contentParts.push({ type: "image", url });
1285
+ } else {
1286
+ contentParts.push({
1287
+ type: "file",
1288
+ url,
1289
+ mimeType: attachment.file.type,
1290
+ name: attachment.file.name
1291
+ });
1292
+ }
1293
+ }
1294
+ onFileProgress?.(attachment.id, { status: "done" });
1295
+ } catch (error) {
1296
+ onFileProgress?.(attachment.id, { status: "error" });
1297
+ throw error;
1298
+ }
1299
+ }
1300
+ return contentParts;
1301
+ }
1302
+ function clearTransformationCache() {
1303
+ transformationCache.clear();
1304
+ }
1305
+
998
1306
  // src/hooks/useFileUpload.tsx
999
1307
  import { v4 as uuidv4 } from "uuid";
1000
- import { jsx as jsx6 } from "react/jsx-runtime";
1308
+ import { jsx as jsx8 } from "react/jsx-runtime";
1001
1309
  async function generateImagePreview(file) {
1002
1310
  if (!file.type.startsWith("image/")) {
1003
1311
  return void 0;
@@ -1030,18 +1338,21 @@ function isTypeAccepted(mimeType, acceptedTypes) {
1030
1338
  function useFileUpload({
1031
1339
  config,
1032
1340
  disabled = false,
1033
- resetDependency
1341
+ resetDependency,
1342
+ getCurrentChat
1034
1343
  }) {
1035
1344
  const strings = useStrings();
1036
1345
  const theme = useTheme();
1037
1346
  const [attachments, setAttachments] = useState2([]);
1038
1347
  const [isDragging, setIsDragging] = useState2(false);
1039
1348
  const [fileError, setFileError] = useState2(null);
1349
+ const [processingState, setProcessingState] = useState2(/* @__PURE__ */ new Map());
1040
1350
  const fileInputRef = useRef3(null);
1041
1351
  const dragCounterRef = useRef3(0);
1042
1352
  const enabled = config !== void 0;
1043
1353
  const maxFileSize = config?.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
1044
1354
  const acceptedTypes = config?.acceptedTypes;
1355
+ const transformers = config?.transformers;
1045
1356
  useEffect3(() => {
1046
1357
  if (fileError) {
1047
1358
  const timer = setTimeout(() => setFileError(null), 3e3);
@@ -1051,7 +1362,28 @@ function useFileUpload({
1051
1362
  useEffect3(() => {
1052
1363
  setAttachments([]);
1053
1364
  setFileError(null);
1365
+ setProcessingState(/* @__PURE__ */ new Map());
1054
1366
  }, [resetDependency]);
1367
+ const runTransformer = useCallback2(async (attachmentId, file, transformer) => {
1368
+ setProcessingState((prev) => new Map(prev).set(attachmentId, { status: "processing" }));
1369
+ try {
1370
+ const chat = await getCurrentChat();
1371
+ const context = { chat };
1372
+ const transformedContent = await getTransformedContent(file, transformer, context, (progress) => {
1373
+ setProcessingState((prev) => new Map(prev).set(attachmentId, {
1374
+ status: "processing",
1375
+ progress
1376
+ }));
1377
+ });
1378
+ setAttachments((prev) => prev.map(
1379
+ (a) => a.id === attachmentId ? { ...a, transformedContent } : a
1380
+ ));
1381
+ setProcessingState((prev) => new Map(prev).set(attachmentId, { status: "done" }));
1382
+ } catch (error) {
1383
+ console.error(`[useFileUpload] Transformation failed for ${file.name}:`, error);
1384
+ setProcessingState((prev) => new Map(prev).set(attachmentId, { status: "error" }));
1385
+ }
1386
+ }, [getCurrentChat]);
1055
1387
  const handleFiles = useCallback2(async (files) => {
1056
1388
  const fileArray = Array.from(files);
1057
1389
  for (const file of fileArray) {
@@ -1066,21 +1398,30 @@ function useFileUpload({
1066
1398
  continue;
1067
1399
  }
1068
1400
  const preview = await generateImagePreview(file);
1069
- setAttachments((prev) => [
1070
- ...prev,
1071
- {
1072
- id: uuidv4(),
1073
- file,
1074
- preview
1075
- }
1076
- ]);
1401
+ const attachmentId = uuidv4();
1402
+ const attachment = {
1403
+ id: attachmentId,
1404
+ file,
1405
+ preview
1406
+ };
1407
+ setAttachments((prev) => [...prev, attachment]);
1408
+ const transformer = findTransformer(file.type, transformers);
1409
+ if (transformer) {
1410
+ runTransformer(attachmentId, file, transformer);
1411
+ }
1077
1412
  }
1078
- }, [maxFileSize, acceptedTypes, strings]);
1413
+ }, [maxFileSize, acceptedTypes, strings, transformers, runTransformer]);
1079
1414
  const removeAttachment = useCallback2((id) => {
1080
1415
  setAttachments((prev) => prev.filter((a) => a.id !== id));
1416
+ setProcessingState((prev) => {
1417
+ const next = new Map(prev);
1418
+ next.delete(id);
1419
+ return next;
1420
+ });
1081
1421
  }, []);
1082
1422
  const clearAttachments = useCallback2(() => {
1083
1423
  setAttachments([]);
1424
+ setProcessingState(/* @__PURE__ */ new Map());
1084
1425
  }, []);
1085
1426
  const openFilePicker = useCallback2(() => {
1086
1427
  fileInputRef.current?.click();
@@ -1133,7 +1474,7 @@ function useFileUpload({
1133
1474
  }), [handleDragEnter, handleDragOver, handleDragLeave, handleDrop]);
1134
1475
  const DropZoneOverlay = useMemo(() => {
1135
1476
  if (!isDragging || !enabled) return null;
1136
- return /* @__PURE__ */ jsx6(
1477
+ return /* @__PURE__ */ jsx8(
1137
1478
  "div",
1138
1479
  {
1139
1480
  style: {
@@ -1148,7 +1489,7 @@ function useFileUpload({
1148
1489
  zIndex: 1010,
1149
1490
  pointerEvents: "none"
1150
1491
  },
1151
- children: /* @__PURE__ */ jsx6(
1492
+ children: /* @__PURE__ */ jsx8(
1152
1493
  "div",
1153
1494
  {
1154
1495
  style: {
@@ -1157,7 +1498,7 @@ function useFileUpload({
1157
1498
  borderRadius: "12px",
1158
1499
  boxShadow: theme.buttonShadow
1159
1500
  },
1160
- children: /* @__PURE__ */ jsx6("span", { style: { color: theme.primaryColor, fontWeight: 600, fontSize: "16px" }, children: strings.fileUpload.dropFilesHere })
1501
+ children: /* @__PURE__ */ jsx8("span", { style: { color: theme.primaryColor, fontWeight: 600, fontSize: "16px" }, children: strings.fileUpload.dropFilesHere })
1161
1502
  }
1162
1503
  )
1163
1504
  }
@@ -1170,6 +1511,7 @@ function useFileUpload({
1170
1511
  enabled,
1171
1512
  maxFileSize,
1172
1513
  acceptedTypes,
1514
+ processingState,
1173
1515
  fileInputRef,
1174
1516
  handleFiles,
1175
1517
  removeAttachment,
@@ -1187,7 +1529,7 @@ function useFileUpload({
1187
1529
 
1188
1530
  // src/hooks/useDropdownState.tsx
1189
1531
  import { useState as useState3, useCallback as useCallback3, useMemo as useMemo2 } from "react";
1190
- import { jsx as jsx7 } from "react/jsx-runtime";
1532
+ import { jsx as jsx9 } from "react/jsx-runtime";
1191
1533
  function useDropdownState(options = {}) {
1192
1534
  const { backdropZIndex = 1002, initialOpen = false } = options;
1193
1535
  const [isOpen, setIsOpen] = useState3(initialOpen);
@@ -1202,7 +1544,7 @@ function useDropdownState(options = {}) {
1202
1544
  }, []);
1203
1545
  const Backdrop = useMemo2(() => {
1204
1546
  if (!isOpen) return null;
1205
- return /* @__PURE__ */ jsx7(
1547
+ return /* @__PURE__ */ jsx9(
1206
1548
  "div",
1207
1549
  {
1208
1550
  onClick: close,
@@ -1227,7 +1569,7 @@ function useDropdownState(options = {}) {
1227
1569
  }
1228
1570
 
1229
1571
  // src/components/UseAIChatPanel.tsx
1230
- import { Fragment, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1572
+ import { Fragment, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1231
1573
  function getTextContent(content) {
1232
1574
  if (typeof content === "string") {
1233
1575
  return content;
@@ -1248,6 +1590,7 @@ function UseAIChatPanel({
1248
1590
  onLoadChat,
1249
1591
  onDeleteChat,
1250
1592
  onListChats,
1593
+ onGetChat,
1251
1594
  suggestions,
1252
1595
  availableAgents,
1253
1596
  defaultAgent,
@@ -1275,6 +1618,7 @@ function UseAIChatPanel({
1275
1618
  fileError,
1276
1619
  enabled: fileUploadEnabled,
1277
1620
  acceptedTypes,
1621
+ processingState: fileProcessingState,
1278
1622
  fileInputRef,
1279
1623
  removeAttachment,
1280
1624
  clearAttachments,
@@ -1283,6 +1627,7 @@ function UseAIChatPanel({
1283
1627
  getDropZoneProps,
1284
1628
  DropZoneOverlay
1285
1629
  } = useFileUpload({
1630
+ getCurrentChat: onGetChat ?? (async () => null),
1286
1631
  config: fileUploadConfig,
1287
1632
  disabled: loading,
1288
1633
  resetDependency: currentChatId
@@ -1354,7 +1699,7 @@ function UseAIChatPanel({
1354
1699
  chatHistoryDropdown.close();
1355
1700
  }
1356
1701
  };
1357
- return /* @__PURE__ */ jsxs5(
1702
+ return /* @__PURE__ */ jsxs7(
1358
1703
  "div",
1359
1704
  {
1360
1705
  onClick: () => {
@@ -1372,7 +1717,7 @@ function UseAIChatPanel({
1372
1717
  },
1373
1718
  children: [
1374
1719
  DropZoneOverlay,
1375
- /* @__PURE__ */ jsxs5(
1720
+ /* @__PURE__ */ jsxs7(
1376
1721
  "div",
1377
1722
  {
1378
1723
  style: {
@@ -1385,7 +1730,7 @@ function UseAIChatPanel({
1385
1730
  gap: "12px"
1386
1731
  },
1387
1732
  children: [
1388
- /* @__PURE__ */ jsx8("div", { style: { flex: 1, minWidth: 0, position: "relative" }, children: onListChats ? /* @__PURE__ */ jsxs5(
1733
+ /* @__PURE__ */ jsx10("div", { style: { flex: 1, minWidth: 0, position: "relative" }, children: onListChats ? /* @__PURE__ */ jsxs7(
1389
1734
  "button",
1390
1735
  {
1391
1736
  "data-testid": "chat-history-dropdown-button",
@@ -1418,7 +1763,7 @@ function UseAIChatPanel({
1418
1763
  e.currentTarget.style.background = "transparent";
1419
1764
  },
1420
1765
  children: [
1421
- /* @__PURE__ */ jsx8("span", { style: {
1766
+ /* @__PURE__ */ jsx10("span", { style: {
1422
1767
  overflow: "hidden",
1423
1768
  textOverflow: "ellipsis",
1424
1769
  whiteSpace: "nowrap",
@@ -1435,13 +1780,13 @@ function UseAIChatPanel({
1435
1780
  }
1436
1781
  return strings.header.newChat;
1437
1782
  })() }),
1438
- /* @__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" }) })
1783
+ /* @__PURE__ */ jsx10("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx10("path", { d: "M3 4.5L6 7.5L9 4.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) })
1439
1784
  ]
1440
1785
  }
1441
- ) : /* @__PURE__ */ jsx8("div", { style: { fontSize: "14px", fontWeight: "600", color: theme.textColor, padding: "6px 8px" }, children: strings.header.aiAssistant }) }),
1442
- /* @__PURE__ */ jsxs5("div", { style: { display: "flex", alignItems: "center", gap: "4px" }, children: [
1443
- availableAgents && availableAgents.length > 1 && onAgentChange && /* @__PURE__ */ jsxs5("div", { style: { position: "relative" }, children: [
1444
- /* @__PURE__ */ jsxs5(
1786
+ ) : /* @__PURE__ */ jsx10("div", { style: { fontSize: "14px", fontWeight: "600", color: theme.textColor, padding: "6px 8px" }, children: strings.header.aiAssistant }) }),
1787
+ /* @__PURE__ */ jsxs7("div", { style: { display: "flex", alignItems: "center", gap: "4px" }, children: [
1788
+ availableAgents && availableAgents.length > 1 && onAgentChange && /* @__PURE__ */ jsxs7("div", { style: { position: "relative" }, children: [
1789
+ /* @__PURE__ */ jsxs7(
1445
1790
  "button",
1446
1791
  {
1447
1792
  "data-testid": "agent-selector",
@@ -1470,7 +1815,7 @@ function UseAIChatPanel({
1470
1815
  },
1471
1816
  title: "Select AI model",
1472
1817
  children: [
1473
- /* @__PURE__ */ jsx8("span", { style: {
1818
+ /* @__PURE__ */ jsx10("span", { style: {
1474
1819
  overflow: "hidden",
1475
1820
  textOverflow: "ellipsis",
1476
1821
  whiteSpace: "nowrap",
@@ -1479,11 +1824,11 @@ function UseAIChatPanel({
1479
1824
  const agent = availableAgents.find((a) => a.id === (selectedAgent ?? defaultAgent));
1480
1825
  return agent?.name || "AI";
1481
1826
  })() }),
1482
- /* @__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" }) })
1827
+ /* @__PURE__ */ jsx10("svg", { width: "10", height: "10", viewBox: "0 0 12 12", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx10("path", { d: "M3 4.5L6 7.5L9 4.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) })
1483
1828
  ]
1484
1829
  }
1485
1830
  ),
1486
- agentDropdown.isOpen && /* @__PURE__ */ jsx8(
1831
+ agentDropdown.isOpen && /* @__PURE__ */ jsx10(
1487
1832
  "div",
1488
1833
  {
1489
1834
  style: {
@@ -1503,7 +1848,7 @@ function UseAIChatPanel({
1503
1848
  },
1504
1849
  children: availableAgents.map((agent) => {
1505
1850
  const isSelected = agent.id === (selectedAgent ?? defaultAgent);
1506
- return /* @__PURE__ */ jsxs5(
1851
+ return /* @__PURE__ */ jsxs7(
1507
1852
  "div",
1508
1853
  {
1509
1854
  "data-testid": "agent-option",
@@ -1533,13 +1878,13 @@ function UseAIChatPanel({
1533
1878
  }
1534
1879
  },
1535
1880
  children: [
1536
- /* @__PURE__ */ jsxs5("div", { style: { flex: 1, minWidth: 0 }, children: [
1537
- /* @__PURE__ */ jsx8("div", { style: {
1881
+ /* @__PURE__ */ jsxs7("div", { style: { flex: 1, minWidth: 0 }, children: [
1882
+ /* @__PURE__ */ jsx10("div", { style: {
1538
1883
  fontSize: "13px",
1539
1884
  fontWeight: isSelected ? "600" : "500",
1540
1885
  color: isSelected ? theme.primaryColor : theme.textColor
1541
1886
  }, children: agent.name }),
1542
- agent.annotation && /* @__PURE__ */ jsx8("div", { style: {
1887
+ agent.annotation && /* @__PURE__ */ jsx10("div", { style: {
1543
1888
  fontSize: "11px",
1544
1889
  color: theme.secondaryTextColor,
1545
1890
  marginTop: "2px",
@@ -1548,7 +1893,7 @@ function UseAIChatPanel({
1548
1893
  whiteSpace: "nowrap"
1549
1894
  }, children: agent.annotation })
1550
1895
  ] }),
1551
- 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" }) })
1896
+ isSelected && /* @__PURE__ */ jsx10("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx10("path", { d: "M2 7L5.5 10.5L12 4", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
1552
1897
  ]
1553
1898
  },
1554
1899
  agent.id
@@ -1557,7 +1902,7 @@ function UseAIChatPanel({
1557
1902
  }
1558
1903
  )
1559
1904
  ] }),
1560
- onNewChat && /* @__PURE__ */ jsx8(
1905
+ onNewChat && /* @__PURE__ */ jsx10(
1561
1906
  "button",
1562
1907
  {
1563
1908
  "data-testid": "new-chat-button",
@@ -1584,10 +1929,10 @@ function UseAIChatPanel({
1584
1929
  e.currentTarget.style.color = theme.secondaryTextColor;
1585
1930
  },
1586
1931
  title: strings.header.newChat,
1587
- 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" }) })
1932
+ children: /* @__PURE__ */ jsx10("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx10("path", { d: "M8 3.5V12.5M3.5 8H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) })
1588
1933
  }
1589
1934
  ),
1590
- onDeleteChat && messages.length > 0 && /* @__PURE__ */ jsx8(
1935
+ onDeleteChat && messages.length > 0 && /* @__PURE__ */ jsx10(
1591
1936
  "button",
1592
1937
  {
1593
1938
  "data-testid": "delete-chat-button",
@@ -1611,7 +1956,7 @@ function UseAIChatPanel({
1611
1956
  e.currentTarget.style.color = theme.secondaryTextColor;
1612
1957
  },
1613
1958
  title: strings.header.deleteChat,
1614
- 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" }) })
1959
+ children: /* @__PURE__ */ jsx10("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx10("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" }) })
1615
1960
  }
1616
1961
  ),
1617
1962
  closeButton
@@ -1619,7 +1964,7 @@ function UseAIChatPanel({
1619
1964
  ]
1620
1965
  }
1621
1966
  ),
1622
- chatHistoryDropdown.isOpen && onListChats && /* @__PURE__ */ jsx8(
1967
+ chatHistoryDropdown.isOpen && onListChats && /* @__PURE__ */ jsx10(
1623
1968
  "div",
1624
1969
  {
1625
1970
  style: {
@@ -1636,7 +1981,7 @@ function UseAIChatPanel({
1636
1981
  flexDirection: "column",
1637
1982
  overflow: "hidden"
1638
1983
  },
1639
- children: /* @__PURE__ */ jsx8(
1984
+ children: /* @__PURE__ */ jsx10(
1640
1985
  "div",
1641
1986
  {
1642
1987
  style: {
@@ -1644,7 +1989,7 @@ function UseAIChatPanel({
1644
1989
  overflowY: "auto",
1645
1990
  padding: "8px"
1646
1991
  },
1647
- children: chatHistory.length === 0 ? /* @__PURE__ */ jsx8(
1992
+ children: chatHistory.length === 0 ? /* @__PURE__ */ jsx10(
1648
1993
  "div",
1649
1994
  {
1650
1995
  style: {
@@ -1653,9 +1998,9 @@ function UseAIChatPanel({
1653
1998
  padding: "32px 16px",
1654
1999
  fontSize: "13px"
1655
2000
  },
1656
- children: /* @__PURE__ */ jsx8("p", { style: { margin: 0 }, children: strings.chatHistory.noChatHistory })
2001
+ children: /* @__PURE__ */ jsx10("p", { style: { margin: 0 }, children: strings.chatHistory.noChatHistory })
1657
2002
  }
1658
- ) : chatHistory.map((chat) => /* @__PURE__ */ jsxs5(
2003
+ ) : chatHistory.map((chat) => /* @__PURE__ */ jsxs7(
1659
2004
  "div",
1660
2005
  {
1661
2006
  "data-testid": "chat-history-item",
@@ -1679,10 +2024,10 @@ function UseAIChatPanel({
1679
2024
  }
1680
2025
  },
1681
2026
  children: [
1682
- /* @__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 }),
1683
- /* @__PURE__ */ jsxs5("div", { style: { fontSize: "11px", color: theme.secondaryTextColor }, children: [
2027
+ /* @__PURE__ */ jsx10("div", { style: { fontSize: "13px", fontWeight: "500", color: theme.textColor, marginBottom: "4px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: chat.title || strings.header.newChat }),
2028
+ /* @__PURE__ */ jsxs7("div", { style: { fontSize: "11px", color: theme.secondaryTextColor }, children: [
1684
2029
  new Date(chat.updatedAt).toLocaleDateString([], { month: "short", day: "numeric" }),
1685
- currentChatId === chat.id && /* @__PURE__ */ jsxs5("span", { style: {
2030
+ currentChatId === chat.id && /* @__PURE__ */ jsxs7("span", { style: {
1686
2031
  marginLeft: "8px",
1687
2032
  color: theme.primaryColor,
1688
2033
  fontWeight: "600"
@@ -1701,7 +2046,7 @@ function UseAIChatPanel({
1701
2046
  ),
1702
2047
  chatHistoryDropdown.Backdrop,
1703
2048
  agentDropdown.Backdrop,
1704
- /* @__PURE__ */ jsxs5(
2049
+ /* @__PURE__ */ jsxs7(
1705
2050
  "div",
1706
2051
  {
1707
2052
  style: {
@@ -1713,7 +2058,7 @@ function UseAIChatPanel({
1713
2058
  gap: "12px"
1714
2059
  },
1715
2060
  children: [
1716
- messages.length === 0 && /* @__PURE__ */ jsxs5(
2061
+ messages.length === 0 && /* @__PURE__ */ jsxs7(
1717
2062
  "div",
1718
2063
  {
1719
2064
  style: {
@@ -1724,12 +2069,12 @@ function UseAIChatPanel({
1724
2069
  gap: "20px"
1725
2070
  },
1726
2071
  children: [
1727
- /* @__PURE__ */ jsxs5("div", { style: { textAlign: "center", color: theme.secondaryTextColor, fontSize: "14px" }, children: [
1728
- /* @__PURE__ */ jsx8("p", { style: { margin: 0, fontSize: "32px", marginBottom: "12px" }, children: "\u{1F4AC}" }),
1729
- /* @__PURE__ */ jsx8("p", { style: { margin: 0 }, children: strings.emptyChat.startConversation }),
1730
- /* @__PURE__ */ jsx8("p", { style: { margin: "8px 0 0", fontSize: "12px" }, children: strings.emptyChat.askMeToHelp })
2072
+ /* @__PURE__ */ jsxs7("div", { style: { textAlign: "center", color: theme.secondaryTextColor, fontSize: "14px" }, children: [
2073
+ /* @__PURE__ */ jsx10("p", { style: { margin: 0, fontSize: "32px", marginBottom: "12px" }, children: "\u{1F4AC}" }),
2074
+ /* @__PURE__ */ jsx10("p", { style: { margin: 0 }, children: strings.emptyChat.startConversation }),
2075
+ /* @__PURE__ */ jsx10("p", { style: { margin: "8px 0 0", fontSize: "12px" }, children: strings.emptyChat.askMeToHelp })
1731
2076
  ] }),
1732
- displayedSuggestions.length > 0 && /* @__PURE__ */ jsx8(
2077
+ displayedSuggestions.length > 0 && /* @__PURE__ */ jsx10(
1733
2078
  "div",
1734
2079
  {
1735
2080
  style: {
@@ -1739,7 +2084,7 @@ function UseAIChatPanel({
1739
2084
  width: "100%",
1740
2085
  maxWidth: "320px"
1741
2086
  },
1742
- children: displayedSuggestions.map((suggestion, index) => /* @__PURE__ */ jsx8(
2087
+ children: displayedSuggestions.map((suggestion, index) => /* @__PURE__ */ jsx10(
1743
2088
  "button",
1744
2089
  {
1745
2090
  "data-testid": "chat-suggestion-button",
@@ -1783,7 +2128,7 @@ function UseAIChatPanel({
1783
2128
  ]
1784
2129
  }
1785
2130
  ),
1786
- messages.map((message) => /* @__PURE__ */ jsxs5(
2131
+ messages.map((message) => /* @__PURE__ */ jsxs7(
1787
2132
  "div",
1788
2133
  {
1789
2134
  "data-testid": `chat-message-${message.role}`,
@@ -1796,7 +2141,7 @@ function UseAIChatPanel({
1796
2141
  onMouseEnter: () => message.role === "user" && setHoveredMessageId(message.id),
1797
2142
  onMouseLeave: () => setHoveredMessageId(null),
1798
2143
  children: [
1799
- /* @__PURE__ */ jsxs5(
2144
+ /* @__PURE__ */ jsxs7(
1800
2145
  "div",
1801
2146
  {
1802
2147
  style: {
@@ -1804,7 +2149,7 @@ function UseAIChatPanel({
1804
2149
  maxWidth: "80%"
1805
2150
  },
1806
2151
  children: [
1807
- message.role === "user" && hoveredMessageId === message.id && onSaveCommand && !slashCommands.isSavingCommand(message.id) && /* @__PURE__ */ jsx8(
2152
+ message.role === "user" && hoveredMessageId === message.id && onSaveCommand && !slashCommands.isSavingCommand(message.id) && /* @__PURE__ */ jsx10(
1808
2153
  "button",
1809
2154
  {
1810
2155
  "data-testid": "save-command-button",
@@ -1840,14 +2185,14 @@ function UseAIChatPanel({
1840
2185
  e.currentTarget.style.transform = "scale(1)";
1841
2186
  e.currentTarget.style.boxShadow = "0 2px 6px rgba(0, 0, 0, 0.15)";
1842
2187
  },
1843
- children: /* @__PURE__ */ jsxs5("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1844
- /* @__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" }),
1845
- /* @__PURE__ */ jsx8("polyline", { points: "17 21 17 13 7 13 7 21" }),
1846
- /* @__PURE__ */ jsx8("polyline", { points: "7 3 7 8 15 8" })
2188
+ children: /* @__PURE__ */ jsxs7("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2189
+ /* @__PURE__ */ jsx10("path", { d: "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" }),
2190
+ /* @__PURE__ */ jsx10("polyline", { points: "17 21 17 13 7 13 7 21" }),
2191
+ /* @__PURE__ */ jsx10("polyline", { points: "7 3 7 8 15 8" })
1847
2192
  ] })
1848
2193
  }
1849
2194
  ),
1850
- /* @__PURE__ */ jsxs5(
2195
+ /* @__PURE__ */ jsxs7(
1851
2196
  "div",
1852
2197
  {
1853
2198
  "data-testid": "chat-message-content",
@@ -1862,7 +2207,7 @@ function UseAIChatPanel({
1862
2207
  wordWrap: "break-word"
1863
2208
  },
1864
2209
  children: [
1865
- 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(
2210
+ message.role === "user" && hasFileContent(message.content) && /* @__PURE__ */ jsx10("div", { style: { display: "flex", flexWrap: "wrap", gap: "6px", marginBottom: "8px" }, children: message.content.filter((part) => part.type === "file").map((part, idx) => /* @__PURE__ */ jsx10(
1866
2211
  FilePlaceholder,
1867
2212
  {
1868
2213
  name: part.file.name,
@@ -1870,7 +2215,7 @@ function UseAIChatPanel({
1870
2215
  },
1871
2216
  idx
1872
2217
  )) }),
1873
- message.role === "assistant" ? /* @__PURE__ */ jsx8(MarkdownContent, { content: getTextContent(message.content) }) : getTextContent(message.content)
2218
+ message.role === "assistant" ? /* @__PURE__ */ jsx10(MarkdownContent, { content: getTextContent(message.content) }) : getTextContent(message.content)
1874
2219
  ]
1875
2220
  }
1876
2221
  ),
@@ -1881,7 +2226,7 @@ function UseAIChatPanel({
1881
2226
  ]
1882
2227
  }
1883
2228
  ),
1884
- /* @__PURE__ */ jsx8(
2229
+ /* @__PURE__ */ jsx10(
1885
2230
  "div",
1886
2231
  {
1887
2232
  style: {
@@ -1900,14 +2245,14 @@ function UseAIChatPanel({
1900
2245
  },
1901
2246
  message.id
1902
2247
  )),
1903
- loading && /* @__PURE__ */ jsx8(
2248
+ loading && /* @__PURE__ */ jsx10(
1904
2249
  "div",
1905
2250
  {
1906
2251
  style: {
1907
2252
  display: "flex",
1908
2253
  alignItems: "flex-start"
1909
2254
  },
1910
- children: /* @__PURE__ */ jsx8(
2255
+ children: /* @__PURE__ */ jsx10(
1911
2256
  "div",
1912
2257
  {
1913
2258
  className: "markdown-content",
@@ -1920,19 +2265,19 @@ function UseAIChatPanel({
1920
2265
  color: theme.textColor,
1921
2266
  maxWidth: "80%"
1922
2267
  },
1923
- children: streamingText ? /* @__PURE__ */ jsx8(MarkdownContent, { content: streamingText }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
1924
- /* @__PURE__ */ jsx8("span", { style: { opacity: 0.6 }, children: strings.input.thinking }),
1925
- /* @__PURE__ */ jsx8("span", { className: "dots", style: { marginLeft: "4px" }, children: "..." })
2268
+ children: streamingText ? /* @__PURE__ */ jsx10(MarkdownContent, { content: streamingText }) : /* @__PURE__ */ jsxs7(Fragment, { children: [
2269
+ /* @__PURE__ */ jsx10("span", { style: { opacity: 0.6 }, children: strings.input.thinking }),
2270
+ /* @__PURE__ */ jsx10("span", { className: "dots", style: { marginLeft: "4px" }, children: "..." })
1926
2271
  ] })
1927
2272
  }
1928
2273
  )
1929
2274
  }
1930
2275
  ),
1931
- /* @__PURE__ */ jsx8("div", { ref: messagesEndRef })
2276
+ /* @__PURE__ */ jsx10("div", { ref: messagesEndRef })
1932
2277
  ]
1933
2278
  }
1934
2279
  ),
1935
- /* @__PURE__ */ jsxs5(
2280
+ /* @__PURE__ */ jsxs7(
1936
2281
  "div",
1937
2282
  {
1938
2283
  style: {
@@ -1940,7 +2285,7 @@ function UseAIChatPanel({
1940
2285
  borderTop: `1px solid ${theme.borderColor}`
1941
2286
  },
1942
2287
  children: [
1943
- fileError && /* @__PURE__ */ jsx8(
2288
+ fileError && /* @__PURE__ */ jsx10(
1944
2289
  "div",
1945
2290
  {
1946
2291
  "data-testid": "file-error",
@@ -1955,7 +2300,7 @@ function UseAIChatPanel({
1955
2300
  children: fileError
1956
2301
  }
1957
2302
  ),
1958
- attachments.length > 0 && /* @__PURE__ */ jsx8(
2303
+ attachments.length > 0 && /* @__PURE__ */ jsx10(
1959
2304
  "div",
1960
2305
  {
1961
2306
  "data-testid": "file-attachments",
@@ -1965,18 +2310,19 @@ function UseAIChatPanel({
1965
2310
  gap: "8px",
1966
2311
  marginBottom: "8px"
1967
2312
  },
1968
- children: attachments.map((attachment) => /* @__PURE__ */ jsx8(
2313
+ children: attachments.map((attachment) => /* @__PURE__ */ jsx10(
1969
2314
  FileChip,
1970
2315
  {
1971
2316
  attachment,
1972
2317
  onRemove: () => removeAttachment(attachment.id),
1973
- disabled: loading
2318
+ disabled: loading,
2319
+ processingState: fileProcessingState.get(attachment.id)
1974
2320
  },
1975
2321
  attachment.id
1976
2322
  ))
1977
2323
  }
1978
2324
  ),
1979
- /* @__PURE__ */ jsxs5(
2325
+ /* @__PURE__ */ jsxs7(
1980
2326
  "div",
1981
2327
  {
1982
2328
  style: {
@@ -1988,18 +2334,19 @@ function UseAIChatPanel({
1988
2334
  },
1989
2335
  children: [
1990
2336
  slashCommands.AutocompleteComponent,
1991
- /* @__PURE__ */ jsx8(
2337
+ /* @__PURE__ */ jsx10(
1992
2338
  "input",
1993
2339
  {
1994
2340
  ref: fileInputRef,
1995
2341
  type: "file",
1996
2342
  multiple: true,
2343
+ "data-testid": "file-input",
1997
2344
  style: { display: "none" },
1998
2345
  onChange: handleFileInputChange,
1999
2346
  accept: acceptedTypes?.join(",")
2000
2347
  }
2001
2348
  ),
2002
- /* @__PURE__ */ jsx8(
2349
+ /* @__PURE__ */ jsx10(
2003
2350
  "textarea",
2004
2351
  {
2005
2352
  ref: textareaRef,
@@ -2027,7 +2374,7 @@ function UseAIChatPanel({
2027
2374
  }
2028
2375
  }
2029
2376
  ),
2030
- /* @__PURE__ */ jsxs5(
2377
+ /* @__PURE__ */ jsxs7(
2031
2378
  "div",
2032
2379
  {
2033
2380
  style: {
@@ -2037,7 +2384,7 @@ function UseAIChatPanel({
2037
2384
  padding: "4px 8px"
2038
2385
  },
2039
2386
  children: [
2040
- /* @__PURE__ */ jsx8("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: fileUploadEnabled && /* @__PURE__ */ jsx8(
2387
+ /* @__PURE__ */ jsx10("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: fileUploadEnabled && /* @__PURE__ */ jsx10(
2041
2388
  "button",
2042
2389
  {
2043
2390
  "data-testid": "file-picker-button",
@@ -2069,13 +2416,13 @@ function UseAIChatPanel({
2069
2416
  e.currentTarget.style.borderColor = theme.borderColor;
2070
2417
  },
2071
2418
  title: strings.fileUpload.attachFiles,
2072
- children: /* @__PURE__ */ jsxs5("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
2073
- /* @__PURE__ */ jsx8("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
2074
- /* @__PURE__ */ jsx8("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
2419
+ children: /* @__PURE__ */ jsxs7("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
2420
+ /* @__PURE__ */ jsx10("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
2421
+ /* @__PURE__ */ jsx10("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
2075
2422
  ] })
2076
2423
  }
2077
2424
  ) }),
2078
- /* @__PURE__ */ jsx8(
2425
+ /* @__PURE__ */ jsx10(
2079
2426
  "button",
2080
2427
  {
2081
2428
  "data-testid": "chat-send-button",
@@ -2096,9 +2443,9 @@ function UseAIChatPanel({
2096
2443
  height: "32px",
2097
2444
  transition: "all 0.2s"
2098
2445
  },
2099
- children: /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
2100
- /* @__PURE__ */ jsx8("line", { x1: "12", y1: "19", x2: "12", y2: "5" }),
2101
- /* @__PURE__ */ jsx8("polyline", { points: "5 12 12 5 19 12" })
2446
+ children: /* @__PURE__ */ jsxs7("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
2447
+ /* @__PURE__ */ jsx10("line", { x1: "12", y1: "19", x2: "12", y2: "5" }),
2448
+ /* @__PURE__ */ jsx10("polyline", { points: "5 12 12 5 19 12" })
2102
2449
  ] })
2103
2450
  }
2104
2451
  )
@@ -2111,7 +2458,7 @@ function UseAIChatPanel({
2111
2458
  ]
2112
2459
  }
2113
2460
  ),
2114
- /* @__PURE__ */ jsx8("style", { children: `
2461
+ /* @__PURE__ */ jsx10("style", { children: `
2115
2462
  /* Markdown content styles */
2116
2463
  .markdown-content > :first-child {
2117
2464
  margin-top: 0 !important;
@@ -2136,7 +2483,7 @@ function UseAIChatPanel({
2136
2483
  }
2137
2484
 
2138
2485
  // src/components/UseAIFloatingChatWrapper.tsx
2139
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
2486
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
2140
2487
  function UseAIFloatingChatWrapper({
2141
2488
  isOpen,
2142
2489
  onClose,
@@ -2144,8 +2491,8 @@ function UseAIFloatingChatWrapper({
2144
2491
  }) {
2145
2492
  const theme = useTheme();
2146
2493
  if (!isOpen) return null;
2147
- return /* @__PURE__ */ jsxs6(Fragment2, { children: [
2148
- /* @__PURE__ */ jsx9(
2494
+ return /* @__PURE__ */ jsxs8(Fragment2, { children: [
2495
+ /* @__PURE__ */ jsx11(
2149
2496
  "div",
2150
2497
  {
2151
2498
  style: {
@@ -2161,7 +2508,7 @@ function UseAIFloatingChatWrapper({
2161
2508
  onClick: onClose
2162
2509
  }
2163
2510
  ),
2164
- /* @__PURE__ */ jsx9(
2511
+ /* @__PURE__ */ jsx11(
2165
2512
  "div",
2166
2513
  {
2167
2514
  style: {
@@ -2180,7 +2527,7 @@ function UseAIFloatingChatWrapper({
2180
2527
  children
2181
2528
  }
2182
2529
  ),
2183
- /* @__PURE__ */ jsx9("style", { children: `
2530
+ /* @__PURE__ */ jsx11("style", { children: `
2184
2531
  @keyframes fadeIn {
2185
2532
  from { opacity: 0; }
2186
2533
  to { opacity: 1; }
@@ -2200,7 +2547,7 @@ function UseAIFloatingChatWrapper({
2200
2547
  }
2201
2548
  function CloseButton({ onClick }) {
2202
2549
  const theme = useTheme();
2203
- return /* @__PURE__ */ jsx9(
2550
+ return /* @__PURE__ */ jsx11(
2204
2551
  "button",
2205
2552
  {
2206
2553
  "data-testid": "chat-close-button",
@@ -2235,7 +2582,7 @@ function CloseButton({ onClick }) {
2235
2582
 
2236
2583
  // src/components/UseAIChat.tsx
2237
2584
  import { createContext as createContext3, useContext as useContext3 } from "react";
2238
- import { jsx as jsx10 } from "react/jsx-runtime";
2585
+ import { jsx as jsx12 } from "react/jsx-runtime";
2239
2586
  var __UseAIChatContext = createContext3(null);
2240
2587
  function useChatUIContext() {
2241
2588
  const context = useContext3(__UseAIChatContext);
@@ -2259,6 +2606,7 @@ function UseAIChat({ floating = false }) {
2259
2606
  onLoadChat: ctx.history.load,
2260
2607
  onDeleteChat: ctx.history.delete,
2261
2608
  onListChats: ctx.history.list,
2609
+ onGetChat: ctx.history.get,
2262
2610
  suggestions: ctx.suggestions,
2263
2611
  availableAgents: ctx.agents.available,
2264
2612
  defaultAgent: ctx.agents.default,
@@ -2271,22 +2619,22 @@ function UseAIChat({ floating = false }) {
2271
2619
  onDeleteCommand: ctx.commands.delete
2272
2620
  };
2273
2621
  if (floating) {
2274
- return /* @__PURE__ */ jsx10(
2622
+ return /* @__PURE__ */ jsx12(
2275
2623
  UseAIFloatingChatWrapper,
2276
2624
  {
2277
2625
  isOpen: ctx.ui.isOpen,
2278
2626
  onClose: () => ctx.ui.setOpen(false),
2279
- children: /* @__PURE__ */ jsx10(
2627
+ children: /* @__PURE__ */ jsx12(
2280
2628
  UseAIChatPanel,
2281
2629
  {
2282
2630
  ...chatPanelProps,
2283
- closeButton: /* @__PURE__ */ jsx10(CloseButton, { onClick: () => ctx.ui.setOpen(false) })
2631
+ closeButton: /* @__PURE__ */ jsx12(CloseButton, { onClick: () => ctx.ui.setOpen(false) })
2284
2632
  }
2285
2633
  )
2286
2634
  }
2287
2635
  );
2288
2636
  }
2289
- return /* @__PURE__ */ jsx10(UseAIChatPanel, { ...chatPanelProps });
2637
+ return /* @__PURE__ */ jsx12(UseAIChatPanel, { ...chatPanelProps });
2290
2638
  }
2291
2639
 
2292
2640
  // src/client.ts
@@ -2487,12 +2835,18 @@ var UseAIClient = class {
2487
2835
  return { type: "text", text: part.text };
2488
2836
  } else if (part.type === "image") {
2489
2837
  return { type: "image", url: part.url };
2490
- } else {
2838
+ } else if (part.type === "file") {
2491
2839
  return {
2492
2840
  type: "file",
2493
2841
  url: part.url,
2494
2842
  mimeType: part.mimeType
2495
2843
  };
2844
+ } else {
2845
+ return {
2846
+ type: "transformed_file",
2847
+ text: part.text,
2848
+ originalFile: part.originalFile
2849
+ };
2496
2850
  }
2497
2851
  });
2498
2852
  }
@@ -2867,7 +3221,8 @@ var LocalStorageChatRepository = class {
2867
3221
  title: options?.title,
2868
3222
  messages: [],
2869
3223
  createdAt: now,
2870
- updatedAt: now
3224
+ updatedAt: now,
3225
+ metadata: options?.metadata
2871
3226
  };
2872
3227
  await this.enforceMaxChatsLimit();
2873
3228
  await this.saveChat(chat);
@@ -2951,6 +3306,14 @@ var LocalStorageChatRepository = class {
2951
3306
  throw new Error(`Failed to clear all chats: ${error instanceof Error ? error.message : "Unknown error"}`);
2952
3307
  }
2953
3308
  }
3309
+ async updateMetadata(id, metadata, overwrite = false) {
3310
+ const chat = await this.loadChat(id);
3311
+ if (!chat) {
3312
+ throw new Error(`Chat not found: ${id}`);
3313
+ }
3314
+ chat.metadata = overwrite ? metadata : { ...chat.metadata, ...metadata };
3315
+ await this.saveChat(chat);
3316
+ }
2954
3317
  getChatKey(id) {
2955
3318
  return `${STORAGE_KEY_PREFIX}${id}`;
2956
3319
  }
@@ -2989,36 +3352,12 @@ var LocalStorageChatRepository = class {
2989
3352
  }
2990
3353
  };
2991
3354
 
2992
- // src/fileUpload/EmbedFileUploadBackend.ts
2993
- var EmbedFileUploadBackend = class {
2994
- /**
2995
- * Converts a File to a base64 data URL.
2996
- *
2997
- * @param file - The File object to convert
2998
- * @returns Promise resolving to a base64 data URL (e.g., "data:image/png;base64,...")
2999
- * @throws Error if file reading fails
3000
- */
3001
- async prepareForSend(file) {
3002
- return new Promise((resolve, reject) => {
3003
- const reader = new FileReader();
3004
- reader.onload = () => {
3005
- if (typeof reader.result === "string") {
3006
- resolve(reader.result);
3007
- } else {
3008
- reject(new Error("Failed to read file as data URL"));
3009
- }
3010
- };
3011
- reader.onerror = () => {
3012
- reject(new Error(`Failed to read file: ${file.name}`));
3013
- };
3014
- reader.readAsDataURL(file);
3015
- });
3016
- }
3017
- };
3018
-
3019
3355
  // src/hooks/useChatManagement.ts
3020
3356
  import { useState as useState5, useCallback as useCallback4, useRef as useRef5, useEffect as useEffect5 } from "react";
3021
3357
  var CHAT_TITLE_MAX_LENGTH = 50;
3358
+ function deepEquals(a, b) {
3359
+ return JSON.stringify(a) === JSON.stringify(b);
3360
+ }
3022
3361
  function generateChatTitle(message) {
3023
3362
  return message.length > CHAT_TITLE_MAX_LENGTH ? message.substring(0, CHAT_TITLE_MAX_LENGTH) + "..." : message;
3024
3363
  }
@@ -3049,7 +3388,11 @@ function transformMessagesToClientFormat(uiMessages) {
3049
3388
  }
3050
3389
  function useChatManagement({
3051
3390
  repository,
3052
- clientRef
3391
+ clientRef,
3392
+ onSendMessage,
3393
+ setOpen,
3394
+ connected,
3395
+ loading
3053
3396
  }) {
3054
3397
  const [currentChatId, setCurrentChatId] = useState5(null);
3055
3398
  const [pendingChatId, setPendingChatId] = useState5(null);
@@ -3082,18 +3425,18 @@ function useChatManagement({
3082
3425
  const loadedMessages = await loadChatMessages(chatId);
3083
3426
  setMessages(loadedMessages);
3084
3427
  }, [loadChatMessages]);
3085
- const createNewChat = useCallback4(async () => {
3428
+ const createNewChat = useCallback4(async (options) => {
3086
3429
  console.log("[ChatManagement] createNewChat called - currentChatId:", currentChatId, "pendingChatId:", pendingChatId, "messages.length:", messages.length);
3087
3430
  if (pendingChatId && messages.length === 0) {
3088
- console.log("[ChatManagement] Pending chat is already blank, not creating new chat");
3089
- return pendingChatId;
3090
- }
3091
- if (currentChatId && !pendingChatId && messages.length === 0) {
3092
- console.log("[ChatManagement] Current chat is already blank, not creating new chat");
3093
- return currentChatId;
3431
+ const existingChat = await repository.loadChat(pendingChatId);
3432
+ const optionsMatch = existingChat && existingChat.title === options?.title && deepEquals(existingChat.metadata, options?.metadata);
3433
+ if (optionsMatch) {
3434
+ console.log("[ChatManagement] Last created chat has matching options, reusing:", pendingChatId);
3435
+ return pendingChatId;
3436
+ }
3094
3437
  }
3095
3438
  console.log("[ChatManagement] Creating new chat...");
3096
- const chatId = await repository.createChat();
3439
+ const chatId = await repository.createChat(options);
3097
3440
  setPendingChatId(chatId);
3098
3441
  setMessages([]);
3099
3442
  if (clientRef.current) {
@@ -3138,6 +3481,22 @@ function useChatManagement({
3138
3481
  }
3139
3482
  }
3140
3483
  }, [currentChatId, repository]);
3484
+ const getCurrentChat = useCallback4(async () => {
3485
+ const chatId = pendingChatId || currentChatId;
3486
+ if (!chatId) return null;
3487
+ const chat = await repository.loadChat(chatId);
3488
+ if (chat?.metadata) {
3489
+ chat.metadata = Object.freeze({ ...chat.metadata });
3490
+ }
3491
+ return chat;
3492
+ }, [pendingChatId, currentChatId, repository]);
3493
+ const updateMetadata = useCallback4(async (metadata, overwrite = false) => {
3494
+ const chatId = pendingChatId || currentChatId;
3495
+ if (!chatId) {
3496
+ throw new Error("No active chat");
3497
+ }
3498
+ await repository.updateMetadata(chatId, metadata, overwrite);
3499
+ }, [pendingChatId, currentChatId, repository]);
3141
3500
  const activatePendingChat = useCallback4(() => {
3142
3501
  if (!pendingChatId) return null;
3143
3502
  console.log("[ChatManagement] Activating pending chat:", pendingChatId);
@@ -3156,7 +3515,7 @@ function useChatManagement({
3156
3515
  console.error("[ChatManagement] Chat not found:", chatId);
3157
3516
  return false;
3158
3517
  }
3159
- const { generateMessageId: generateMessageId2 } = await import("./types-TVUXB3NB.js");
3518
+ const { generateMessageId: generateMessageId2 } = await import("./types-64CH2HXY.js");
3160
3519
  chat.messages.push({
3161
3520
  id: generateMessageId2(),
3162
3521
  role: "user",
@@ -3192,7 +3551,7 @@ function useChatManagement({
3192
3551
  console.error("[ChatManagement] Chat not found:", currentChatIdValue);
3193
3552
  return;
3194
3553
  }
3195
- const { generateMessageId: generateMessageId2 } = await import("./types-TVUXB3NB.js");
3554
+ const { generateMessageId: generateMessageId2 } = await import("./types-64CH2HXY.js");
3196
3555
  chat.messages.push({
3197
3556
  id: generateMessageId2(),
3198
3557
  role: "assistant",
@@ -3249,6 +3608,70 @@ function useChatManagement({
3249
3608
  }
3250
3609
  }, [currentChatId, pendingChatId, createNewChat, repository, loadChatMessages, clientRef]);
3251
3610
  const displayedChatId = pendingChatId || currentChatId;
3611
+ const pendingMessagesRef = useRef5([]);
3612
+ const isProcessingQueueRef = useRef5(false);
3613
+ const loadingRef = useRef5(loading);
3614
+ useEffect5(() => {
3615
+ loadingRef.current = loading;
3616
+ }, [loading]);
3617
+ const processMessageQueue = useCallback4(async () => {
3618
+ if (isProcessingQueueRef.current || pendingMessagesRef.current.length === 0 || !onSendMessage) {
3619
+ return;
3620
+ }
3621
+ isProcessingQueueRef.current = true;
3622
+ while (pendingMessagesRef.current.length > 0) {
3623
+ const { message, options } = pendingMessagesRef.current.shift();
3624
+ const { newChat = false, attachments = [], openChat = true, metadata } = options ?? {};
3625
+ if (newChat) {
3626
+ await createNewChat({ metadata });
3627
+ }
3628
+ const fileAttachments = await Promise.all(
3629
+ attachments.map(async (file) => {
3630
+ let preview;
3631
+ if (file.type.startsWith("image/")) {
3632
+ preview = await new Promise((resolve) => {
3633
+ const reader = new FileReader();
3634
+ reader.onload = () => resolve(typeof reader.result === "string" ? reader.result : void 0);
3635
+ reader.onerror = () => resolve(void 0);
3636
+ reader.readAsDataURL(file);
3637
+ });
3638
+ }
3639
+ return {
3640
+ id: crypto.randomUUID(),
3641
+ file,
3642
+ preview
3643
+ };
3644
+ })
3645
+ );
3646
+ await onSendMessage(message, fileAttachments.length > 0 ? fileAttachments : void 0);
3647
+ if (openChat && setOpen) {
3648
+ setOpen(true);
3649
+ }
3650
+ await new Promise((resolve) => {
3651
+ const checkLoading = () => {
3652
+ setTimeout(() => {
3653
+ if (!loadingRef.current) {
3654
+ resolve();
3655
+ } else {
3656
+ checkLoading();
3657
+ }
3658
+ }, 100);
3659
+ };
3660
+ checkLoading();
3661
+ });
3662
+ }
3663
+ isProcessingQueueRef.current = false;
3664
+ }, [onSendMessage, createNewChat, setOpen]);
3665
+ const sendMessage = useCallback4(async (message, options) => {
3666
+ if (!onSendMessage) {
3667
+ throw new Error("sendMessage is not available (onSendMessage callback not provided)");
3668
+ }
3669
+ if (!connected) {
3670
+ throw new Error("Not connected to UseAI server");
3671
+ }
3672
+ pendingMessagesRef.current.push({ message, options });
3673
+ await processMessageQueue();
3674
+ }, [onSendMessage, connected, processMessageQueue]);
3252
3675
  return {
3253
3676
  currentChatId,
3254
3677
  pendingChatId,
@@ -3263,6 +3686,9 @@ function useChatManagement({
3263
3686
  saveUserMessage,
3264
3687
  saveAIResponse,
3265
3688
  reloadMessages,
3689
+ sendMessage,
3690
+ getCurrentChat,
3691
+ updateMetadata,
3266
3692
  currentChatIdSnapshot,
3267
3693
  pendingChatIdSnapshot
3268
3694
  };
@@ -3644,7 +4070,7 @@ function usePromptState({
3644
4070
  }
3645
4071
 
3646
4072
  // src/providers/useAIProvider.tsx
3647
- import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
4073
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
3648
4074
  var __UseAIContext = createContext4(null);
3649
4075
  var hasWarnedAboutMissingProvider = false;
3650
4076
  var noOpContextValue = {
@@ -3674,6 +4100,11 @@ var noOpContextValue = {
3674
4100
  },
3675
4101
  list: async () => [],
3676
4102
  clear: async () => {
4103
+ },
4104
+ sendMessage: async () => {
4105
+ },
4106
+ get: async () => null,
4107
+ updateMetadata: async () => {
3677
4108
  }
3678
4109
  },
3679
4110
  agents: {
@@ -3713,7 +4144,8 @@ function UseAIProvider({
3713
4144
  renderChat = true,
3714
4145
  theme: customTheme,
3715
4146
  strings: customStrings,
3716
- visibleAgentIds
4147
+ visibleAgentIds,
4148
+ onOpenChange
3717
4149
  }) {
3718
4150
  const fileUploadConfig = fileUploadConfigProp === false ? void 0 : fileUploadConfigProp ?? DEFAULT_FILE_UPLOAD_CONFIG;
3719
4151
  const theme = { ...defaultTheme, ...customTheme };
@@ -3721,12 +4153,17 @@ function UseAIProvider({
3721
4153
  const [connected, setConnected] = useState10(false);
3722
4154
  const [isChatOpen, setIsChatOpen] = useState10(false);
3723
4155
  const [loading, setLoading] = useState10(false);
4156
+ const handleSetChatOpen = useCallback9((open) => {
4157
+ setIsChatOpen(open);
4158
+ onOpenChange?.(open);
4159
+ }, [onOpenChange]);
3724
4160
  const [streamingText, setStreamingText] = useState10("");
3725
4161
  const streamingChatIdRef = useRef9(null);
3726
4162
  const clientRef = useRef9(null);
3727
4163
  const repositoryRef = useRef9(
3728
4164
  chatRepository || new LocalStorageChatRepository()
3729
4165
  );
4166
+ const handleSendMessageRef = useRef9(null);
3730
4167
  const {
3731
4168
  registerTools,
3732
4169
  unregisterTools,
@@ -3748,9 +4185,18 @@ function UseAIProvider({
3748
4185
  clientRef,
3749
4186
  connected
3750
4187
  });
4188
+ const stableSendMessage = useCallback9(async (message, attachments) => {
4189
+ if (handleSendMessageRef.current) {
4190
+ await handleSendMessageRef.current(message, attachments);
4191
+ }
4192
+ }, []);
3751
4193
  const chatManagement = useChatManagement({
3752
4194
  repository: repositoryRef.current,
3753
- clientRef
4195
+ clientRef,
4196
+ onSendMessage: stableSendMessage,
4197
+ setOpen: handleSetChatOpen,
4198
+ connected,
4199
+ loading
3754
4200
  });
3755
4201
  const {
3756
4202
  currentChatId,
@@ -3764,7 +4210,10 @@ function UseAIProvider({
3764
4210
  clearCurrentChat,
3765
4211
  activatePendingChat,
3766
4212
  saveUserMessage,
3767
- saveAIResponse
4213
+ saveAIResponse,
4214
+ sendMessage,
4215
+ getCurrentChat,
4216
+ updateMetadata
3768
4217
  } = chatManagement;
3769
4218
  const {
3770
4219
  availableAgents,
@@ -3905,7 +4354,6 @@ function UseAIProvider({
3905
4354
  let persistedContent = message;
3906
4355
  let multimodalContent;
3907
4356
  if (attachments && attachments.length > 0) {
3908
- const backend = fileUploadConfig?.backend ?? new EmbedFileUploadBackend();
3909
4357
  const persistedParts = [];
3910
4358
  if (message.trim()) {
3911
4359
  persistedParts.push({ type: "text", text: message });
@@ -3921,35 +4369,24 @@ function UseAIProvider({
3921
4369
  });
3922
4370
  }
3923
4371
  persistedContent = persistedParts;
3924
- const contentParts = [];
4372
+ const fileContent = await processAttachments(attachments, {
4373
+ getCurrentChat,
4374
+ backend: fileUploadConfig?.backend,
4375
+ transformers: fileUploadConfig?.transformers
4376
+ });
4377
+ multimodalContent = [];
3925
4378
  if (message.trim()) {
3926
- contentParts.push({ type: "text", text: message });
3927
- }
3928
- for (const attachment of attachments) {
3929
- try {
3930
- const url = await backend.prepareForSend(attachment.file);
3931
- if (attachment.file.type.startsWith("image/")) {
3932
- contentParts.push({ type: "image", url });
3933
- } else {
3934
- contentParts.push({
3935
- type: "file",
3936
- url,
3937
- mimeType: attachment.file.type,
3938
- name: attachment.file.name
3939
- });
3940
- }
3941
- } catch (error) {
3942
- console.error("[Provider] Failed to prepare file for send:", error);
3943
- }
4379
+ multimodalContent.push({ type: "text", text: message });
3944
4380
  }
3945
- multimodalContent = contentParts;
4381
+ multimodalContent.push(...fileContent);
3946
4382
  }
3947
4383
  if (activeChatId) {
3948
4384
  await saveUserMessage(activeChatId, persistedContent);
3949
4385
  }
3950
4386
  setLoading(true);
3951
4387
  await clientRef.current.sendPrompt(message, multimodalContent);
3952
- }, [activatePendingChat, currentChatId, saveUserMessage, fileUploadConfig]);
4388
+ }, [activatePendingChat, currentChatId, saveUserMessage, fileUploadConfig, getCurrentChat]);
4389
+ handleSendMessageRef.current = handleSendMessage;
3953
4390
  const value = {
3954
4391
  serverUrl,
3955
4392
  connected,
@@ -3969,7 +4406,10 @@ function UseAIProvider({
3969
4406
  load: loadChat,
3970
4407
  delete: deleteChat,
3971
4408
  list: listChats,
3972
- clear: clearCurrentChat
4409
+ clear: clearCurrentChat,
4410
+ sendMessage,
4411
+ get: getCurrentChat,
4412
+ updateMetadata
3973
4413
  },
3974
4414
  agents: {
3975
4415
  available: availableAgents,
@@ -3999,7 +4439,8 @@ function UseAIProvider({
3999
4439
  create: createNewChat,
4000
4440
  load: loadChat,
4001
4441
  delete: deleteChat,
4002
- list: listChats
4442
+ list: listChats,
4443
+ get: getCurrentChat
4003
4444
  },
4004
4445
  agents: {
4005
4446
  available: availableAgents,
@@ -4015,7 +4456,7 @@ function UseAIProvider({
4015
4456
  },
4016
4457
  ui: {
4017
4458
  isOpen: isChatOpen,
4018
- setOpen: setIsChatOpen
4459
+ setOpen: handleSetChatOpen
4019
4460
  }
4020
4461
  };
4021
4462
  const isUIDisabled = CustomButton === null || CustomChat === null;
@@ -4045,21 +4486,21 @@ function UseAIProvider({
4045
4486
  };
4046
4487
  const renderDefaultChat = () => {
4047
4488
  if (isUIDisabled) return null;
4048
- return /* @__PURE__ */ jsx11(UseAIFloatingChatWrapper, { isOpen: isChatOpen, onClose: () => setIsChatOpen(false), children: /* @__PURE__ */ jsx11(
4489
+ return /* @__PURE__ */ jsx13(UseAIFloatingChatWrapper, { isOpen: isChatOpen, onClose: () => handleSetChatOpen(false), children: /* @__PURE__ */ jsx13(
4049
4490
  UseAIChatPanel,
4050
4491
  {
4051
4492
  ...chatPanelProps,
4052
- closeButton: /* @__PURE__ */ jsx11(CloseButton, { onClick: () => setIsChatOpen(false) })
4493
+ closeButton: /* @__PURE__ */ jsx13(CloseButton, { onClick: () => handleSetChatOpen(false) })
4053
4494
  }
4054
4495
  ) });
4055
4496
  };
4056
4497
  const renderCustomChat = () => {
4057
4498
  if (!CustomChat) return null;
4058
- return /* @__PURE__ */ jsx11(
4499
+ return /* @__PURE__ */ jsx13(
4059
4500
  CustomChat,
4060
4501
  {
4061
4502
  isOpen: isChatOpen,
4062
- onClose: () => setIsChatOpen(false),
4503
+ onClose: () => handleSetChatOpen(false),
4063
4504
  onSendMessage: handleSendMessage,
4064
4505
  messages,
4065
4506
  loading,
@@ -4074,18 +4515,18 @@ function UseAIProvider({
4074
4515
  };
4075
4516
  const renderBuiltInChat = () => {
4076
4517
  if (!renderChat) return null;
4077
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
4078
- ButtonComponent && /* @__PURE__ */ jsx11(
4518
+ return /* @__PURE__ */ jsxs9(Fragment3, { children: [
4519
+ ButtonComponent && /* @__PURE__ */ jsx13(
4079
4520
  ButtonComponent,
4080
4521
  {
4081
- onClick: () => setIsChatOpen(true),
4522
+ onClick: () => handleSetChatOpen(true),
4082
4523
  connected
4083
4524
  }
4084
4525
  ),
4085
4526
  hasCustomChat ? renderCustomChat() : renderDefaultChat()
4086
4527
  ] });
4087
4528
  };
4088
- 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: [
4529
+ return /* @__PURE__ */ jsx13(ThemeContext.Provider, { value: theme, children: /* @__PURE__ */ jsx13(StringsContext.Provider, { value: strings, children: /* @__PURE__ */ jsx13(__UseAIContext.Provider, { value, children: /* @__PURE__ */ jsxs9(__UseAIChatContext.Provider, { value: chatUIContextValue, children: [
4089
4530
  children,
4090
4531
  renderBuiltInChat()
4091
4532
  ] }) }) }) });
@@ -4488,14 +4929,18 @@ export {
4488
4929
  UseAIFloatingButton,
4489
4930
  UseAIFloatingChatWrapper,
4490
4931
  UseAIProvider,
4932
+ clearTransformationCache,
4491
4933
  convertToolsToDefinitions,
4492
4934
  defaultStrings,
4493
4935
  defaultTheme,
4494
4936
  defineTool,
4495
4937
  executeDefinedTool,
4938
+ findTransformer,
4496
4939
  generateChatId,
4497
4940
  generateCommandId,
4498
4941
  generateMessageId,
4942
+ matchesMimeType,
4943
+ processAttachments,
4499
4944
  useAI,
4500
4945
  useAIContext,
4501
4946
  useAIWorkflow,