@burtson-labs/bandit-engine 2.0.158 → 2.0.160

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.
Files changed (49) hide show
  1. package/README.md +17 -0
  2. package/dist/{aiProviderStore-ZQ5VVVKQ.mjs → aiProviderStore-BNZGM6QU.mjs} +2 -2
  3. package/dist/chat-4BOIV4VP.mjs +16 -0
  4. package/dist/chat-provider.js +3 -3
  5. package/dist/chat-provider.js.map +1 -1
  6. package/dist/chat-provider.mjs +6 -6
  7. package/dist/{chunk-GWQRTUKF.mjs → chunk-B3HAQC6F.mjs} +2 -2
  8. package/dist/chunk-B3HAQC6F.mjs.map +1 -0
  9. package/dist/{chunk-7HC6ONWM.mjs → chunk-IABD4L6K.mjs} +43 -44
  10. package/dist/chunk-IABD4L6K.mjs.map +1 -0
  11. package/dist/{chunk-R45DM35B.mjs → chunk-LFQH6M6B.mjs} +3 -3
  12. package/dist/chunk-LFQH6M6B.mjs.map +1 -0
  13. package/dist/{chunk-IS2LWSBZ.mjs → chunk-LNGF2XAZ.mjs} +3 -3
  14. package/dist/chunk-LNGF2XAZ.mjs.map +1 -0
  15. package/dist/{chunk-GZDJNVAS.mjs → chunk-LTWMMHFT.mjs} +11 -9
  16. package/dist/chunk-LTWMMHFT.mjs.map +1 -0
  17. package/dist/{chunk-F5JS4W2K.mjs → chunk-MHF76C2F.mjs} +246 -127
  18. package/dist/chunk-MHF76C2F.mjs.map +1 -0
  19. package/dist/{chunk-B2TJZM4Z.mjs → chunk-P6SLB5RC.mjs} +3 -3
  20. package/dist/{chunk-ZW3YNAD7.mjs → chunk-PDQT564P.mjs} +5 -5
  21. package/dist/{chunk-ZW3YNAD7.mjs.map → chunk-PDQT564P.mjs.map} +1 -1
  22. package/dist/{chunk-VJVGSAKI.mjs → chunk-PXCIDXRP.mjs} +7 -7
  23. package/dist/{chunk-VJVGSAKI.mjs.map → chunk-PXCIDXRP.mjs.map} +1 -1
  24. package/dist/{chunk-2X3ZTDUW.mjs → chunk-TKSOJGHX.mjs} +5 -5
  25. package/dist/{chunk-ZOCXFQID.mjs → chunk-XTB6ZLUI.mjs} +2 -2
  26. package/dist/{chunk-ZOCXFQID.mjs.map → chunk-XTB6ZLUI.mjs.map} +1 -1
  27. package/dist/index.js +299 -164
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +11 -11
  30. package/dist/management/management.js +292 -157
  31. package/dist/management/management.js.map +1 -1
  32. package/dist/management/management.mjs +9 -9
  33. package/dist/modals/chat-modal/chat-modal.js +9 -7
  34. package/dist/modals/chat-modal/chat-modal.js.map +1 -1
  35. package/dist/modals/chat-modal/chat-modal.mjs +5 -5
  36. package/dist/{modelStore-KBUW3XAV.mjs → modelStore-NMCRL3FR.mjs} +2 -2
  37. package/package.json +6 -4
  38. package/dist/chat-HM563VOV.mjs +0 -16
  39. package/dist/chunk-7HC6ONWM.mjs.map +0 -1
  40. package/dist/chunk-F5JS4W2K.mjs.map +0 -1
  41. package/dist/chunk-GWQRTUKF.mjs.map +0 -1
  42. package/dist/chunk-GZDJNVAS.mjs.map +0 -1
  43. package/dist/chunk-IS2LWSBZ.mjs.map +0 -1
  44. package/dist/chunk-R45DM35B.mjs.map +0 -1
  45. /package/dist/{aiProviderStore-ZQ5VVVKQ.mjs.map → aiProviderStore-BNZGM6QU.mjs.map} +0 -0
  46. /package/dist/{chat-HM563VOV.mjs.map → chat-4BOIV4VP.mjs.map} +0 -0
  47. /package/dist/{chunk-B2TJZM4Z.mjs.map → chunk-P6SLB5RC.mjs.map} +0 -0
  48. /package/dist/{chunk-2X3ZTDUW.mjs.map → chunk-TKSOJGHX.mjs.map} +0 -0
  49. /package/dist/{modelStore-KBUW3XAV.mjs.map → modelStore-NMCRL3FR.mjs.map} +0 -0
@@ -1585,7 +1585,7 @@ function emitAuthTokenChanged(token) {
1585
1585
  detail: { token }
1586
1586
  }));
1587
1587
  }
1588
- var TOKEN_KEY2, AUTH_TOKEN_CHANGED_EVENT, AuthenticationService, authenticationService2;
1588
+ var TOKEN_KEY2, AUTH_TOKEN_CHANGED_EVENT, AuthenticationService, authenticationService;
1589
1589
  var init_authenticationService = __esm({
1590
1590
  "src/services/auth/authenticationService.ts"() {
1591
1591
  "use strict";
@@ -1638,7 +1638,7 @@ var init_authenticationService = __esm({
1638
1638
  }
1639
1639
  }
1640
1640
  };
1641
- authenticationService2 = new AuthenticationService();
1641
+ authenticationService = new AuthenticationService();
1642
1642
  }
1643
1643
  });
1644
1644
 
@@ -1729,7 +1729,7 @@ var init_gateway_service = __esm({
1729
1729
  return;
1730
1730
  }
1731
1731
  try {
1732
- authenticationService2.clearToken();
1732
+ authenticationService.clearToken();
1733
1733
  } catch {
1734
1734
  }
1735
1735
  if (typeof window !== "undefined" && typeof window.dispatchEvent === "function") {
@@ -3077,7 +3077,7 @@ var init_playground_provider = __esm({
3077
3077
  PlaygroundProvider = class {
3078
3078
  config;
3079
3079
  constructor(config) {
3080
- this.config = { type: "playground" /* PLAYGROUND */, ...config };
3080
+ this.config = { ...config, type: config.type ?? "playground" /* PLAYGROUND */ };
3081
3081
  }
3082
3082
  chat(request) {
3083
3083
  const lastUserMessage = [...request.messages].reverse().find((m) => m.role === "user")?.content ?? "";
@@ -8140,7 +8140,7 @@ function buildUrl(path) {
8140
8140
  }
8141
8141
  function buildHeaders() {
8142
8142
  const headers = { "Content-Type": "application/json" };
8143
- const token = authenticationService2.getToken();
8143
+ const token = authenticationService.getToken();
8144
8144
  if (token) {
8145
8145
  headers["Authorization"] = `Bearer ${token}`;
8146
8146
  }
@@ -8288,7 +8288,7 @@ function resolveAuthIdentity(token) {
8288
8288
  if (!token) {
8289
8289
  return null;
8290
8290
  }
8291
- const claims = authenticationService2.parseJwtClaims(token);
8291
+ const claims = authenticationService.parseJwtClaims(token);
8292
8292
  if (claims?.sub) {
8293
8293
  return claims.sub;
8294
8294
  }
@@ -8821,7 +8821,7 @@ var init_conversationSyncStore = __esm({
8821
8821
  async initialize() {
8822
8822
  ensureTrackersInitialized();
8823
8823
  const gatewayUrl = usePackageSettingsStore.getState().settings?.gatewayApiUrl;
8824
- const token = authenticationService2.getToken();
8824
+ const token = authenticationService.getToken();
8825
8825
  const tokenIdentity = resolveAuthIdentity(token);
8826
8826
  const current = get();
8827
8827
  const storedIdentity = getStoredSyncIdentity();
@@ -8947,10 +8947,10 @@ var init_conversationSyncStore = __esm({
8947
8947
  });
8948
8948
  set({
8949
8949
  hasLoadedPreference: true,
8950
- initializedForToken: authenticationService2.getToken(),
8951
- initializedForIdentity: resolveAuthIdentity(authenticationService2.getToken())
8950
+ initializedForToken: authenticationService.getToken(),
8951
+ initializedForIdentity: resolveAuthIdentity(authenticationService.getToken())
8952
8952
  });
8953
- setStoredSyncIdentity(resolveAuthIdentity(authenticationService2.getToken()));
8953
+ setStoredSyncIdentity(resolveAuthIdentity(authenticationService.getToken()));
8954
8954
  if (enabled) {
8955
8955
  set({ hasCompletedInitialUpload: false });
8956
8956
  }
@@ -8990,10 +8990,10 @@ var init_conversationSyncStore = __esm({
8990
8990
  });
8991
8991
  set({
8992
8992
  hasLoadedPreference: true,
8993
- initializedForToken: authenticationService2.getToken(),
8994
- initializedForIdentity: resolveAuthIdentity(authenticationService2.getToken())
8993
+ initializedForToken: authenticationService.getToken(),
8994
+ initializedForIdentity: resolveAuthIdentity(authenticationService.getToken())
8995
8995
  });
8996
- setStoredSyncIdentity(resolveAuthIdentity(authenticationService2.getToken()));
8996
+ setStoredSyncIdentity(resolveAuthIdentity(authenticationService.getToken()));
8997
8997
  if (preference.syncEnabled && preference.isAdvancedVectorFeaturesEnabled) {
8998
8998
  await get().runSync({ force: true });
8999
8999
  }
@@ -9033,7 +9033,7 @@ var init_conversationSyncStore = __esm({
9033
9033
  debugLogger.error("conversationSyncStore: runSync error - missing gateway URL");
9034
9034
  return;
9035
9035
  }
9036
- const token = authenticationService2.getToken();
9036
+ const token = authenticationService.getToken();
9037
9037
  if (!token) {
9038
9038
  set({ status: "error", lastError: "Authentication required to sync conversations." });
9039
9039
  debugLogger.error("conversationSyncStore: runSync error - missing auth token");
@@ -9966,7 +9966,7 @@ var init_useKnowledgeStore = __esm({
9966
9966
  init_indexedDBService();
9967
9967
  import_uuid5 = require("uuid");
9968
9968
  import_mammoth = __toESM(require("mammoth"));
9969
- pdfjsLib = __toESM(require("pdfjs-dist/legacy/build/pdf"));
9969
+ pdfjsLib = __toESM(require("pdfjs-dist/legacy/build/pdf.mjs"));
9970
9970
  init_embeddingService();
9971
9971
  init_debugLogger();
9972
9972
  init_vectorDatabaseService();
@@ -11177,7 +11177,7 @@ var init_knowledge_file_modal = __esm({
11177
11177
  import_material7 = require("@mui/material");
11178
11178
  init_useVectorStore();
11179
11179
  init_packageSettingsStore();
11180
- pdfjsLib2 = __toESM(require("pdfjs-dist/legacy/build/pdf"));
11180
+ pdfjsLib2 = __toESM(require("pdfjs-dist/legacy/build/pdf.mjs"));
11181
11181
  init_lowlight();
11182
11182
  init_markdownRendering();
11183
11183
  init_debugLogger();
@@ -11968,7 +11968,7 @@ var init_voiceService = __esm({
11968
11968
  debugLogger.error("Gateway API URL not configured");
11969
11969
  throw new Error("Gateway API URL not configured");
11970
11970
  }
11971
- const token = authenticationService2.getToken();
11971
+ const token = authenticationService.getToken();
11972
11972
  if (!token) {
11973
11973
  debugLogger.warn("No JWT token available - skipping voice models fetch");
11974
11974
  const emptyModels = {
@@ -11978,7 +11978,7 @@ var init_voiceService = __esm({
11978
11978
  };
11979
11979
  return emptyModels;
11980
11980
  }
11981
- if (authenticationService2.isTokenExpired(token)) {
11981
+ if (authenticationService.isTokenExpired(token)) {
11982
11982
  debugLogger.warn("JWT token is expired - skipping voice models fetch");
11983
11983
  const emptyModels = {
11984
11984
  models: [],
@@ -12067,7 +12067,7 @@ var init_tts_client = __esm({
12067
12067
  init_packageSettingsStore();
12068
12068
  init_debugLogger();
12069
12069
  getOrAppendAuthHeader = (existing = {}) => {
12070
- const token = authenticationService2.getToken();
12070
+ const token = authenticationService.getToken();
12071
12071
  if (token) {
12072
12072
  existing["Authorization"] = `Bearer ${token}`;
12073
12073
  return existing;
@@ -12758,8 +12758,8 @@ var init_ai_response_action_bar = __esm({
12758
12758
  const { preferences } = usePreferencesStore();
12759
12759
  const { settings: packageSettings } = usePackageSettingsStore();
12760
12760
  (0, import_react14.useEffect)(() => {
12761
- const isAuthenticated = authenticationService2.isAuthenticated();
12762
- const token = authenticationService2.getToken();
12761
+ const isAuthenticated = authenticationService.isAuthenticated();
12762
+ const token = authenticationService.getToken();
12763
12763
  if (!initialized || availableVoices.length === 0) {
12764
12764
  if (token && isAuthenticated) {
12765
12765
  debugLogger.debug("AI Action Bar: Voices not initialized or unavailable, attempting to load from API");
@@ -13603,11 +13603,12 @@ ${sourcesMarkdownList.join("\n")}`;
13603
13603
  tr({ children, node: _node, ...tableRowProps }) {
13604
13604
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_material9.TableRow, { ...tableRowProps, children });
13605
13605
  },
13606
- th({ children, node: _node, ...tableCellProps }) {
13606
+ th({ children, node: _node, align, ...tableCellProps }) {
13607
13607
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
13608
13608
  import_material9.TableCell,
13609
13609
  {
13610
13610
  ...tableCellProps,
13611
+ align: align === "char" ? void 0 : align,
13611
13612
  sx: {
13612
13613
  fontWeight: "bold",
13613
13614
  color: theme.palette.text.primary,
@@ -13620,11 +13621,12 @@ ${sourcesMarkdownList.join("\n")}`;
13620
13621
  }
13621
13622
  );
13622
13623
  },
13623
- td({ children, node: _node, ...tableCellProps }) {
13624
+ td({ children, node: _node, align, ...tableCellProps }) {
13624
13625
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
13625
13626
  import_material9.TableCell,
13626
13627
  {
13627
13628
  ...tableCellProps,
13629
+ align: align === "char" ? void 0 : align,
13628
13630
  sx: {
13629
13631
  color: theme.palette.text.primary,
13630
13632
  fontSize: "0.875rem",
@@ -14701,7 +14703,7 @@ var init_memory_modal = __esm({
14701
14703
  color: (0, import_styles3.alpha)(theme.palette.text.secondary, 0.7),
14702
14704
  maxWidth: 280
14703
14705
  }, children: selectedTab === "user" ? shouldUseVectorForMemories ? "Start by adding something you'd like me to remember. Your memories will be kept in a private workspace for quick recall." : "Start by adding something you'd like me to remember about you or your preferences." : shouldUseVectorForMemories ? "Auto memories are created automatically based on our conversations and kept in your private workspace for better answers." : "Auto memories are created automatically based on our conversations." })
14704
- ] }) : filteredMemories.map((memory) => {
14706
+ ] }) : filteredMemories.map((memory, memoryIndex) => {
14705
14707
  const isSelected = selectedIds.includes(memory.id);
14706
14708
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react17.default.Fragment, { children: [
14707
14709
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_material10.List, { sx: { listStyle: "none", pl: 0, m: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
@@ -14901,7 +14903,7 @@ var init_memory_modal = __esm({
14901
14903
  ]
14902
14904
  }
14903
14905
  ) }),
14904
- filteredMemories.indexOf(memory) < filteredMemories.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_material10.Divider, { sx: { bgcolor: (0, import_styles3.alpha)(appBar.border, 0.5), my: 0.5 } })
14906
+ memoryIndex < filteredMemories.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_material10.Divider, { sx: { bgcolor: (0, import_styles3.alpha)(appBar.border, 0.5), my: 0.5 } })
14905
14907
  ] }, memory.id);
14906
14908
  })
14907
14909
  }
@@ -15613,7 +15615,7 @@ var init_FeedbackModal = __esm({
15613
15615
  }
15614
15616
  const gatewayService = new GatewayService(
15615
15617
  gatewayUrl,
15616
- () => authenticationService2.getToken(),
15618
+ () => authenticationService.getToken(),
15617
15619
  feedbackEmail || settings?.feedbackEmail
15618
15620
  );
15619
15621
  const feedbackRequest = {
@@ -19242,7 +19244,7 @@ ${listMarkdown}`;
19242
19244
  children
19243
19245
  }
19244
19246
  );
19245
- const CodeRenderer = ({ inline, className, children, ...props }) => {
19247
+ const CodeRenderer = ({ node: _node, className, children, ...props }) => {
19246
19248
  const match = /language-([\w-]+)/.exec(className || "");
19247
19249
  const requestedLanguage = match?.[1]?.toLowerCase() ?? "";
19248
19250
  const codeText = String(children).replace(/\n$/, "");
@@ -19576,7 +19578,7 @@ var init_mcpServersService = __esm({
19576
19578
  };
19577
19579
  authHeaders2 = () => {
19578
19580
  const headers = { "Content-Type": "application/json" };
19579
- const token = authenticationService2.getToken();
19581
+ const token = authenticationService.getToken();
19580
19582
  if (token) headers["Authorization"] = `Bearer ${token}`;
19581
19583
  return headers;
19582
19584
  };
@@ -20773,7 +20775,7 @@ var init_engineStore = __esm({
20773
20775
  }
20774
20776
  try {
20775
20777
  const headers = { "Content-Type": "application/json" };
20776
- const token = authenticationService2.getToken();
20778
+ const token = authenticationService.getToken();
20777
20779
  if (token) headers["Authorization"] = `Bearer ${token}`;
20778
20780
  const res = await fetch(`${base}/models`, { headers });
20779
20781
  const data = await res.json();
@@ -21092,7 +21094,7 @@ var init_stt_client = __esm({
21092
21094
  filename,
21093
21095
  url: `${gatewayUrl}/stt/transcribe`
21094
21096
  });
21095
- const token = authenticationService2.getToken();
21097
+ const token = authenticationService.getToken();
21096
21098
  const response = await fetch(`${gatewayUrl}/stt/transcribe`, {
21097
21099
  method: "POST",
21098
21100
  headers: {
@@ -21212,6 +21214,109 @@ var init_transcriber = __esm({
21212
21214
  }
21213
21215
  });
21214
21216
 
21217
+ // src/chat/composer-submission.ts
21218
+ var isPromiseLike, awaitAcceptance, buildSubmission, isImeComposing;
21219
+ var init_composer_submission = __esm({
21220
+ "src/chat/composer-submission.ts"() {
21221
+ "use strict";
21222
+ isPromiseLike = (value) => !!value && typeof value.then === "function";
21223
+ awaitAcceptance = async (ack) => {
21224
+ if (ack === false) return false;
21225
+ if (!isPromiseLike(ack)) return true;
21226
+ try {
21227
+ return await ack !== false;
21228
+ } catch {
21229
+ return false;
21230
+ }
21231
+ };
21232
+ buildSubmission = (text, images, documents) => {
21233
+ const typed = text.trim();
21234
+ const base = typed || (images.length === 1 ? "[\u{1F4F7} You attached an image]" : images.length > 1 ? `[\u{1F4F7} You attached ${images.length} images]` : "");
21235
+ const fileSummary = documents.length > 0 ? `[\u{1F4CE} Attached ${documents.length} file${documents.length > 1 ? "s" : ""}: ${documents.map((d) => d.fileName).join(", ")}]` : "";
21236
+ const displayQuestion = [base || (fileSummary ? "[\u{1F4E4} You submitted content without text]" : ""), fileSummary].filter(Boolean).join("\n\n");
21237
+ const prompt = [text, ...documents.map((d) => d.block)].filter(Boolean).join("\n\n");
21238
+ return { prompt, images: [...images], displayQuestion };
21239
+ };
21240
+ isImeComposing = (event) => event.nativeEvent?.isComposing === true || event.keyCode === 229;
21241
+ }
21242
+ });
21243
+
21244
+ // src/chat/document-import.ts
21245
+ var CODE_EXTENSIONS, TEXT_EXTENSIONS, isCodeFileName, isPlainTextFile, CONTROL_CHARS, INVISIBLE_FORMATTING, stripInvisible, neutralizeRoleMarkers, prepareDocumentText, formatDocumentBlock, readText, readPdfText, readDocument;
21246
+ var init_document_import = __esm({
21247
+ "src/chat/document-import.ts"() {
21248
+ "use strict";
21249
+ CODE_EXTENSIONS = [".js", ".ts", ".tsx", ".json", ".py", ".java", ".c", ".cpp", ".cs"];
21250
+ TEXT_EXTENSIONS = [".md", ".txt", ...CODE_EXTENSIONS];
21251
+ isCodeFileName = (name) => {
21252
+ const lower = name.toLowerCase();
21253
+ return CODE_EXTENSIONS.some((ext) => lower.endsWith(ext));
21254
+ };
21255
+ isPlainTextFile = (name, mimeType = "") => mimeType.startsWith("text/") || TEXT_EXTENSIONS.some((ext) => name.toLowerCase().endsWith(ext));
21256
+ CONTROL_CHARS = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g;
21257
+ INVISIBLE_FORMATTING = /[\u202A-\u202E\u2066-\u2069\u200B\u2060\uFEFF]/g;
21258
+ stripInvisible = (text) => text.normalize("NFC").replace(CONTROL_CHARS, "").replace(INVISIBLE_FORMATTING, "");
21259
+ neutralizeRoleMarkers = (text) => text.replace(/<\s*\/?\s*script\b[^>]*>/gi, "").replace(/(^|\n)([ \t]*)\/?(system|assistant|user)\s*:/gi, "$1$2[removed-role]");
21260
+ prepareDocumentText = (text, fileName) => {
21261
+ const visible = stripInvisible(text);
21262
+ return (isCodeFileName(fileName) ? visible : neutralizeRoleMarkers(visible)).trim();
21263
+ };
21264
+ formatDocumentBlock = (fileName, text) => `\u{1F4C4} ${fileName}
21265
+ ${prepareDocumentText(text, fileName)}`;
21266
+ readText = (file) => typeof file.text === "function" ? file.text() : new Promise((resolve, reject) => {
21267
+ const reader = new FileReader();
21268
+ reader.onload = () => resolve(String(reader.result ?? ""));
21269
+ reader.onerror = () => reject(reader.error ?? new Error("It couldn't be read."));
21270
+ reader.readAsText(file);
21271
+ });
21272
+ readPdfText = async (file, maxPages = 10) => {
21273
+ const pdfjsLib3 = await import("pdfjs-dist/legacy/build/pdf.mjs");
21274
+ pdfjsLib3.GlobalWorkerOptions.workerSrc = "https://cdn.burtson.ai/scripts/pdf.worker.js";
21275
+ const pdf = await pdfjsLib3.getDocument({ data: await file.arrayBuffer() }).promise;
21276
+ const pages = await Promise.all(
21277
+ Array.from({ length: Math.min(pdf.numPages, maxPages) }, async (_, i) => {
21278
+ try {
21279
+ const page = await pdf.getPage(i + 1);
21280
+ const content = await page.getTextContent();
21281
+ return content.items.map((item) => typeof item?.str === "string" ? item.str : "").join(" ");
21282
+ } catch {
21283
+ return "";
21284
+ }
21285
+ })
21286
+ );
21287
+ return pages.join("\n\n");
21288
+ };
21289
+ readDocument = async (file) => {
21290
+ const name = file.name.toLowerCase();
21291
+ try {
21292
+ let text;
21293
+ if (isPlainTextFile(name, file.type)) {
21294
+ text = await readText(file);
21295
+ } else if (name.endsWith(".docx")) {
21296
+ const mammoth2 = await import("mammoth");
21297
+ text = (await mammoth2.extractRawText({ arrayBuffer: await file.arrayBuffer() })).value;
21298
+ } else if (name.endsWith(".pdf")) {
21299
+ text = await readPdfText(file);
21300
+ } else {
21301
+ return { ok: false, fileName: file.name, reason: "This file type can't be read yet." };
21302
+ }
21303
+ const block = formatDocumentBlock(file.name, text);
21304
+ if (block === `\u{1F4C4} ${file.name}
21305
+ `) {
21306
+ return { ok: false, fileName: file.name, reason: "No readable text was found in it." };
21307
+ }
21308
+ return { ok: true, fileName: file.name, block };
21309
+ } catch (error) {
21310
+ return {
21311
+ ok: false,
21312
+ fileName: file.name,
21313
+ reason: error instanceof Error && error.message ? error.message : "It couldn't be read."
21314
+ };
21315
+ }
21316
+ };
21317
+ }
21318
+ });
21319
+
21215
21320
  // src/store/voiceModeStore.ts
21216
21321
  var import_zustand16, useVoiceModeStore;
21217
21322
  var init_voiceModeStore = __esm({
@@ -21266,6 +21371,8 @@ var init_chat_input = __esm({
21266
21371
  init_packageSettingsStore();
21267
21372
  init_transcriber();
21268
21373
  init_memory_modal();
21374
+ init_composer_submission();
21375
+ init_document_import();
21269
21376
  import_styles24 = require("@mui/material/styles");
21270
21377
  init_debugLogger();
21271
21378
  init_brandingService();
@@ -21434,102 +21541,60 @@ var init_chat_input = __esm({
21434
21541
  const removeImage = (index) => {
21435
21542
  setPastedImages((prev) => prev.filter((_, i) => i !== index));
21436
21543
  };
21544
+ const latestDraftRef = (0, import_react53.useRef)({ text: inputValue, images: pastedImages, files: fileInputs });
21545
+ latestDraftRef.current = { text: inputValue, images: pastedImages, files: fileInputs };
21546
+ const submittingRef = (0, import_react53.useRef)(false);
21547
+ const [sendError, setSendError] = (0, import_react53.useState)(null);
21437
21548
  const handleSubmit = async () => {
21438
- if (inputValue.trim() === "" && pastedImages.length === 0 && fileInputs.length === 0)
21439
- return;
21440
- const tempImages = [...pastedImages];
21441
- const escapePromptInjection = (text) => {
21442
- return text.replace(/<\s*\/?script.*?>/gi, "").replace(/```.*?```/gs, "").replace(/(?<=\n|^)\/?(system|assistant|user):/gi, "[removed-role]");
21443
- };
21444
- const getFileText = async (file) => {
21445
- const isCodeFile2 = (name2) => name2.endsWith(".js") || name2.endsWith(".ts") || name2.endsWith(".tsx") || name2.endsWith(".json") || name2.endsWith(".py") || name2.endsWith(".java") || name2.endsWith(".c") || name2.endsWith(".cpp") || name2.endsWith(".cs");
21446
- const sanitize = (text, name2) => isCodeFile2(name2) ? text.trim() : text.replace(/[^\x20-\x7E\r\n\t]/g, "").trim();
21447
- const name = file.name.toLowerCase();
21448
- try {
21449
- if (file.type.startsWith("text/") || name.endsWith(".md") || name.endsWith(".txt") || name.endsWith(".js") || name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".json") || name.endsWith(".py") || name.endsWith(".java") || name.endsWith(".c") || name.endsWith(".cpp") || name.endsWith(".cs")) {
21450
- const text = await file.text();
21451
- if (name.endsWith(".txt") || name.endsWith(".md") || name.endsWith(".docx") || name.endsWith(".pdf")) {
21452
- return `\u{1F4C4} ${file.name}
21453
- ${sanitize(escapePromptInjection(text), file.name)}`;
21454
- } else {
21455
- return `\u{1F4C4} ${file.name}
21456
- ${sanitize(text, file.name)}`;
21457
- }
21458
- }
21459
- if (name.endsWith(".docx")) {
21460
- const mammoth2 = await import("mammoth");
21461
- const arrayBuffer = await file.arrayBuffer();
21462
- const { value } = await mammoth2.extractRawText({ arrayBuffer });
21463
- return `\u{1F4C4} ${file.name}
21464
- ${sanitize(escapePromptInjection(value), file.name)}`;
21465
- }
21466
- if (name.endsWith(".pdf")) {
21467
- const pdfjsLib3 = await import("pdfjs-dist/legacy/build/pdf");
21468
- pdfjsLib3.GlobalWorkerOptions.workerSrc = "https://cdn.burtson.ai/scripts/pdf.worker.js";
21469
- const buffer = await file.arrayBuffer();
21470
- const loadingTask = pdfjsLib3.getDocument({ data: buffer });
21471
- try {
21472
- const pdf = await loadingTask.promise;
21473
- debugLogger.info("PDF loaded", { numPages: pdf.numPages });
21474
- const maxPages = Math.min(pdf.numPages, 10);
21475
- const pages = await Promise.all(
21476
- Array.from({ length: maxPages }, async (_, i) => {
21477
- try {
21478
- const page = await pdf.getPage(i + 1);
21479
- const content = await page.getTextContent();
21480
- return content.items.map((item) => {
21481
- if (item && typeof item === "object" && "str" in item && typeof item.str === "string") {
21482
- return item.str;
21483
- }
21484
- return "";
21485
- }).join(" ");
21486
- } catch (err) {
21487
- debugLogger.error(`Failed to read page ${i + 1}`, { error: err });
21488
- return "";
21489
- }
21490
- })
21491
- );
21492
- const joined = pages.join("\n\n");
21493
- debugLogger.info("Extracted PDF content", { contentPreview: joined.slice(0, 500) });
21494
- return `\u{1F4C4} ${file.name}
21495
- ${sanitize(
21496
- escapePromptInjection(joined),
21497
- file.name
21498
- )}`;
21499
- } catch (error) {
21500
- debugLogger.error("PDF load failed", {
21501
- error: error instanceof Error ? error.message : String(error),
21502
- fileName: file.name
21503
- });
21504
- return "";
21505
- }
21549
+ if (submittingRef.current) return;
21550
+ if (inputValue.trim() === "" && pastedImages.length === 0 && fileInputs.length === 0) return;
21551
+ submittingRef.current = true;
21552
+ const draft = { text: inputValue, images: [...pastedImages], files: [...fileInputs] };
21553
+ try {
21554
+ const reads = await Promise.all(draft.files.map(readDocument));
21555
+ const failed = reads.filter((r) => !r.ok);
21556
+ if (failed.length > 0) {
21557
+ setSendError(
21558
+ failed.map((f) => `Couldn't attach ${f.fileName}: ${f.reason}`).join(" ")
21559
+ );
21560
+ return;
21561
+ }
21562
+ const documents = reads.filter((r) => r.ok);
21563
+ const submission = buildSubmission(draft.text, draft.images, documents);
21564
+ debugLogger.debug("Composer submission", {
21565
+ documents: documents.map((d) => d.fileName),
21566
+ promptLength: submission.prompt.length
21567
+ });
21568
+ setSendError(null);
21569
+ setInputValue("");
21570
+ setPastedImages([]);
21571
+ setFileInputs([]);
21572
+ if (fileInputRef.current) fileInputRef.current.value = "";
21573
+ inputRef.current?.blur();
21574
+ const accepted = await awaitAcceptance(
21575
+ onSend(submission.prompt, submission.images, submission.displayQuestion)
21576
+ );
21577
+ if (!accepted) {
21578
+ const now = latestDraftRef.current;
21579
+ const untouched = now.text.trim() === "" && now.images.length === 0 && now.files.length === 0;
21580
+ if (untouched) {
21581
+ setInputValue(draft.text);
21582
+ setPastedImages(draft.images);
21583
+ setFileInputs(draft.files);
21584
+ setSendError("Your message wasn't sent. It's back in the box so you can try again.");
21585
+ } else {
21586
+ setSendError("Your last message wasn't sent.");
21506
21587
  }
21507
- return "";
21508
- } catch {
21509
- return "";
21510
21588
  }
21511
- };
21512
- const fileTexts = await Promise.all(fileInputs.map(getFileText));
21513
- debugLogger.debug("All extracted file texts", { fileTexts });
21514
- const fileContent = fileTexts.filter(Boolean).join("\n\n");
21515
- const tempQuestionBase = inputValue.trim() || (tempImages.length === 1 ? "[\u{1F4F7} You attached an image]" : tempImages.length > 1 ? `[\u{1F4F7} You attached ${tempImages.length} images]` : "");
21516
- const fileDisplaySummary = fileInputs.length > 0 ? `[\u{1F4CE} Attached ${fileInputs.length} file${fileInputs.length > 1 ? "s" : ""}: ${fileInputs.map((f) => f.name).join(", ")}]` : "";
21517
- const displayQuestion = [
21518
- tempQuestionBase || (fileDisplaySummary ? "[\u{1F4E4} You submitted content without text]" : ""),
21519
- fileDisplaySummary
21520
- ].filter(Boolean).join("\n\n");
21521
- const fullPrompt = [inputValue, fileContent].filter(Boolean).join("\n\n");
21522
- setInputValue("");
21523
- setPastedImages([]);
21524
- setFileInputs([]);
21525
- onSend(fullPrompt, tempImages, displayQuestion);
21526
- inputRef.current?.blur();
21527
- fileInputRef.current.value = "";
21589
+ } finally {
21590
+ submittingRef.current = false;
21591
+ }
21528
21592
  };
21529
21593
  const handleKeyPress = (e) => {
21594
+ if (isImeComposing(e)) return;
21530
21595
  if (e.key === "Enter" && !e.shiftKey) {
21531
21596
  e.preventDefault();
21532
- handleSubmit();
21597
+ void handleSubmit();
21533
21598
  }
21534
21599
  };
21535
21600
  const handleTranscriptionCompleted = (transcribed) => {
@@ -21647,6 +21712,15 @@ ${sanitize(
21647
21712
  gap: isMobile ? compactMobile ? 0.75 : 1 : 0.5
21648
21713
  },
21649
21714
  children: [
21715
+ sendError && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
21716
+ import_material40.Typography,
21717
+ {
21718
+ role: "alert",
21719
+ variant: "caption",
21720
+ sx: { color: theme.palette.error.main, px: 0.5, pt: isMobile ? 0 : 0.5 },
21721
+ children: sendError
21722
+ }
21723
+ ),
21650
21724
  /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
21651
21725
  import_material40.Box,
21652
21726
  {
@@ -21692,7 +21766,10 @@ ${sanitize(
21692
21766
  import_material40.IconButton,
21693
21767
  {
21694
21768
  size: "small",
21695
- onClick: () => setFileInputs((prev) => prev.filter((_, i) => i !== idx)),
21769
+ onClick: () => {
21770
+ setSendError(null);
21771
+ setFileInputs((prev) => prev.filter((_, i) => i !== idx));
21772
+ },
21696
21773
  sx: { ml: 0.5, color: theme.palette.mode === "dark" ? "#aaa" : "#444" },
21697
21774
  children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(CloseIcon, { sx: { fontSize: 14 } })
21698
21775
  }
@@ -22700,7 +22777,7 @@ function syncTelemetry() {
22700
22777
  const settings = usePackageSettingsStore.getState().settings;
22701
22778
  const cfg = resolveTelemetryConfig({
22702
22779
  telemetry: settings?.telemetry,
22703
- banditApiKey: authenticationService2.getToken() ?? void 0
22780
+ banditApiKey: authenticationService.getToken() ?? void 0
22704
22781
  });
22705
22782
  active = cfg ? new TelemetryExporter(cfg) : null;
22706
22783
  } catch {
@@ -23351,7 +23428,7 @@ var init_mcpService = __esm({
23351
23428
  }
23352
23429
  if (tool.mcpServerId) {
23353
23430
  const base2 = settings.gatewayApiUrl.replace(/\/$/, "");
23354
- const token2 = authenticationService2.getToken();
23431
+ const token2 = authenticationService.getToken();
23355
23432
  const controller2 = new AbortController();
23356
23433
  const timeoutId2 = setTimeout(() => controller2.abort(), 3e4);
23357
23434
  try {
@@ -23398,7 +23475,7 @@ var init_mcpService = __esm({
23398
23475
  "Content-Type": "application/json"
23399
23476
  }
23400
23477
  };
23401
- const token = authenticationService2.getToken();
23478
+ const token = authenticationService.getToken();
23402
23479
  if (token) {
23403
23480
  requestOptions.headers = {
23404
23481
  ...requestOptions.headers,
@@ -23498,6 +23575,7 @@ var init_useAIProvider = __esm({
23498
23575
  "src/chat/hooks/useAIProvider.tsx"() {
23499
23576
  "use strict";
23500
23577
  init_debugLogger();
23578
+ init_authenticationService();
23501
23579
  import_react58 = require("react");
23502
23580
  init_knowledgeStore();
23503
23581
  init_aiProviderStore();
@@ -23799,8 +23877,11 @@ var init_useAIProvider = __esm({
23799
23877
  history,
23800
23878
  addHistory,
23801
23879
  inputRef,
23802
- onError
23880
+ onError,
23881
+ onTurnOutcome
23803
23882
  }) => {
23883
+ const onTurnOutcomeRef = (0, import_react58.useRef)(onTurnOutcome);
23884
+ onTurnOutcomeRef.current = onTurnOutcome;
23804
23885
  const currentSubRef = (0, import_react58.useRef)(null);
23805
23886
  const lastPartialRef = (0, import_react58.useRef)({
23806
23887
  text: "",
@@ -23846,10 +23927,14 @@ var init_useAIProvider = __esm({
23846
23927
  };
23847
23928
  const runAIProvider = (0, import_react58.useCallback)(
23848
23929
  async (systemPrompt, question, images) => {
23930
+ let turnHadOutput = false;
23931
+ const reportOutcome = (outcome) => onTurnOutcomeRef.current?.(outcome);
23849
23932
  if (!provider) {
23850
23933
  debugLogger.error("No AI provider configured");
23851
23934
  overrideComponentStatus("Error");
23852
- if (onError) onError(new Error("No AI provider configured"));
23935
+ const error = new Error("No AI provider configured");
23936
+ if (onError) onError(error);
23937
+ reportOutcome({ kind: "failed", hadOutput: false, error });
23853
23938
  return;
23854
23939
  }
23855
23940
  const userPreferences = preferences;
@@ -24003,7 +24088,7 @@ Use this information to personalize your responses whenever relevant.` : "";
24003
24088
  s3Url: resolvedId,
24004
24089
  isUserContent: result.isUserContent,
24005
24090
  isTeamContent: result.isTeamContent,
24006
- contentSource: result.contentSource,
24091
+ contentSource: result.contentSource === "user" || result.contentSource === "team" ? result.contentSource : void 0,
24007
24092
  originalFileName: result.originalFileName || result.filename || result.name,
24008
24093
  // Preserve original data for downstream checks (e.g. vector modal detection)
24009
24094
  _vectorResult: result
@@ -24358,6 +24443,10 @@ ${protocol}`;
24358
24443
  const sub = stream.subscribe({
24359
24444
  next: (data) => {
24360
24445
  if (!data?.message?.content && !data?.message?.tool_calls) return;
24446
+ if (!turnHadOutput) {
24447
+ turnHadOutput = true;
24448
+ reportOutcome({ kind: "output" });
24449
+ }
24361
24450
  if (Array.isArray(data.message.tool_calls) && data.message.tool_calls.length > 0) {
24362
24451
  nativeToolCalls.push(...data.message.tool_calls);
24363
24452
  sawToolBlock = true;
@@ -24392,13 +24481,14 @@ ${protocol}`;
24392
24481
  },
24393
24482
  error: (err) => {
24394
24483
  debugLogger.error("Stream error:", err);
24484
+ reportOutcome({ kind: "failed", hadOutput: turnHadOutput, error: err });
24395
24485
  stopWorking();
24396
24486
  overrideComponentStatus("Idle");
24397
24487
  setIsSubmitting(false);
24398
24488
  setIsStreaming(false);
24399
24489
  if (/lifetime validation|token.*(expired|invalid)|unauthorized|\b401\b/i.test(err.message || "")) {
24400
- const { replaceLastAnswer: replaceLastAnswer2 } = useConversationStore.getState();
24401
- replaceLastAnswer2(
24490
+ const { replaceLastAnswer: replaceLastAnswer3 } = useConversationStore.getState();
24491
+ replaceLastAnswer3(
24402
24492
  "_Your session ended \u2014 sign back in to continue._",
24403
24493
  lastPartialRef.current.images,
24404
24494
  false,
@@ -24425,8 +24515,8 @@ ${protocol}`;
24425
24515
  scope: rateLimit.scope,
24426
24516
  frontier: rateLimit.frontier
24427
24517
  });
24428
- const { replaceLastAnswer: replaceLastAnswer2 } = useConversationStore.getState();
24429
- replaceLastAnswer2(
24518
+ const { replaceLastAnswer: replaceLastAnswer3 } = useConversationStore.getState();
24519
+ replaceLastAnswer3(
24430
24520
  rateLimit.scope === "weekly" ? "_You've reached your weekly limit. Upgrade to keep chatting._" : "_You've hit your session limit. It resets soon \u2014 or upgrade for higher limits._",
24431
24521
  lastPartialRef.current.images,
24432
24522
  false,
@@ -24447,10 +24537,16 @@ ${protocol}`;
24447
24537
  if (partial && !partial.trim().endsWith("\u2026") && !partial.trim().endsWith("...")) {
24448
24538
  partial = partial.trimEnd() + " \u2026";
24449
24539
  }
24540
+ const { replaceLastAnswer: replaceLastAnswer2 } = useConversationStore.getState();
24450
24541
  if (partial) {
24451
- const { replaceLastAnswer: replaceLastAnswer2 } = useConversationStore.getState();
24452
24542
  replaceLastAnswer2(partial, lastPartialRef.current.images, false, lastPartialRef.current.usedDocs, true);
24453
24543
  setResponse(partial);
24544
+ } else {
24545
+ const { conversations: convs, currentId: convId } = useConversationStore.getState();
24546
+ const last = convs.find((c) => c.id === convId)?.history.at(-1);
24547
+ if (last?.answer === "...") {
24548
+ replaceLastAnswer2("_No response came back. Try sending again._", lastPartialRef.current.images, false, lastPartialRef.current.usedDocs, true);
24549
+ }
24454
24550
  }
24455
24551
  setStreamBuffer("");
24456
24552
  setIsThinking?.(false);
@@ -24462,6 +24558,7 @@ ${protocol}`;
24462
24558
  }
24463
24559
  },
24464
24560
  complete: async () => {
24561
+ reportOutcome({ kind: "completed" });
24465
24562
  try {
24466
24563
  setIsThinking?.(false);
24467
24564
  telemetryEvent("tool_loop:llm_response", { responseLength: fullMessage.length });
@@ -24728,7 +24825,7 @@ _This link is temporary and expires in about ${mins} minutes._`;
24728
24825
  }
24729
24826
  } else {
24730
24827
  const data = result.data ?? {};
24731
- const informative = [data.message, data.details, data.error, result.error].map((value) => typeof value === "string" ? value.trim() : void 0).filter((value) => Boolean(value) && value.toLowerCase() !== "n/a");
24828
+ const informative = [data.message, data.details, data.error, result.error].map((value) => typeof value === "string" ? value.trim() : "").filter((value) => value.length > 0 && value.toLowerCase() !== "n/a");
24732
24829
  resultText = informative.length ? informative.join(" \u2014 ") : `I couldn't complete that request: ${result.error || "Unknown error"}.`;
24733
24830
  }
24734
24831
  enhancedMessage = enhancedMessage.replace(placeholderToken, resultText);
@@ -25075,6 +25172,7 @@ ${inlineImageBlocks.join("\n\n")}` : "");
25075
25172
  } catch {
25076
25173
  }
25077
25174
  currentSubRef.current = null;
25175
+ onTurnOutcomeRef.current?.({ kind: "cancelled" });
25078
25176
  let partial = lastPartialRef.current.text;
25079
25177
  if (partial && !partial.trim().endsWith("\u2026") && !partial.trim().endsWith("...")) {
25080
25178
  partial = partial.trimEnd() + " \u2026";
@@ -25293,7 +25391,7 @@ var init_useNetworkStatus = __esm({
25293
25391
  if (connection) {
25294
25392
  const updateConnectionInfo = () => {
25295
25393
  const slowConnections = ["slow-2g", "2g"];
25296
- const isSlowConnection = slowConnections.includes(connection.effectiveType);
25394
+ const isSlowConnection = slowConnections.includes(connection.effectiveType ?? "");
25297
25395
  setNetworkStatus((prev) => ({
25298
25396
  ...prev,
25299
25397
  isSlowConnection,
@@ -27743,7 +27841,7 @@ var init_conversation_drawer = __esm({
27743
27841
  title: "Sign out",
27744
27842
  onClick: (event) => {
27745
27843
  event.stopPropagation();
27746
- authenticationService2.clearToken();
27844
+ authenticationService.clearToken();
27747
27845
  window.location.href = "/login";
27748
27846
  },
27749
27847
  sx: {
@@ -28870,7 +28968,7 @@ var init_enhanced_mobile_conversations_modal = __esm({
28870
28968
  title: "Sign out",
28871
28969
  onClick: (event) => {
28872
28970
  event.stopPropagation();
28873
- authenticationService2.clearToken();
28971
+ authenticationService.clearToken();
28874
28972
  window.location.href = "/login";
28875
28973
  },
28876
28974
  sx: {
@@ -29690,7 +29788,8 @@ var init_query_suggestion_picker = __esm({
29690
29788
  }
29691
29789
  },
29692
29790
  onClick: () => {
29693
- onSend(prompt, []);
29791
+ void Promise.resolve(onSend(prompt, [])).catch(() => {
29792
+ });
29694
29793
  setHasSentPrompt(true);
29695
29794
  },
29696
29795
  children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
@@ -29751,7 +29850,7 @@ var init_UnderReview = __esm({
29751
29850
  const theme = (0, import_material52.useTheme)();
29752
29851
  const navigate = (0, import_react_router_dom5.useNavigate)();
29753
29852
  const handleResetSession = () => {
29754
- authenticationService2.clearToken();
29853
+ authenticationService.clearToken();
29755
29854
  navigate("/login");
29756
29855
  };
29757
29856
  return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
@@ -30432,8 +30531,8 @@ var init_chat2 = __esm({
30432
30531
  const notificationService2 = useNotificationService();
30433
30532
  const [selectedTheme, setSelectedTheme] = (0, import_react79.useState)(null);
30434
30533
  const [themeLoading, setThemeLoading] = (0, import_react79.useState)(true);
30435
- const token = authenticationService2.getToken();
30436
- const claims = token ? authenticationService2.parseJwtClaims(token) : null;
30534
+ const token = authenticationService.getToken();
30535
+ const claims = token ? authenticationService.parseJwtClaims(token) : null;
30437
30536
  const baseTheme = themeMap_default[selectedTheme ?? "bandit-dark"] || banditDarkTheme;
30438
30537
  const banditTheme = (0, import_react79.useMemo)(() => {
30439
30538
  return (0, import_styles34.createTheme)(baseTheme, {
@@ -30506,7 +30605,7 @@ var init_chat2 = __esm({
30506
30605
  } = useTTS();
30507
30606
  (0, import_react79.useEffect)(() => {
30508
30607
  const timer = setTimeout(() => {
30509
- const isAuthenticated = authenticationService2.isAuthenticated();
30608
+ const isAuthenticated = authenticationService.isAuthenticated();
30510
30609
  if (!initialized || availableVoices.length === 0) {
30511
30610
  if (token && isAuthenticated) {
30512
30611
  debugLogger.debug("Chat: Voices not initialized or unavailable, attempting to load from API");
@@ -30519,7 +30618,7 @@ var init_chat2 = __esm({
30519
30618
  return () => clearTimeout(timer);
30520
30619
  }, [initialized, availableVoices.length, loadVoicesFromAPI, token]);
30521
30620
  (0, import_react79.useEffect)(() => {
30522
- const isAuthenticated = authenticationService2.isAuthenticated();
30621
+ const isAuthenticated = authenticationService.isAuthenticated();
30523
30622
  if (packageSettings?.gatewayApiUrl && availableVoices.length === 0 && !initialized) {
30524
30623
  if (token && isAuthenticated) {
30525
30624
  debugLogger.debug("Chat: Gateway URL available and no voices loaded, attempting to load from API");
@@ -31134,7 +31233,16 @@ var init_chat2 = __esm({
31134
31233
  if (rafId) cancelAnimationFrame(rafId);
31135
31234
  };
31136
31235
  }, [_hasHydrated, chatContainerEl, getScrollState, history.length]);
31236
+ const sendAckRef = (0, import_react79.useRef)(null);
31237
+ const settleSendAck = (0, import_react79.useCallback)((outcome) => {
31238
+ const ack = sendAckRef.current;
31239
+ if (!ack) return;
31240
+ sendAckRef.current = null;
31241
+ if (outcome.kind === "failed" && !outcome.hadOutput) ack.reject(outcome.error);
31242
+ else ack.resolve();
31243
+ }, []);
31137
31244
  const aiProvider = useAIProvider({
31245
+ onTurnOutcome: settleSendAck,
31138
31246
  overrideComponentStatus: setComponentStatus,
31139
31247
  setIsSubmitting,
31140
31248
  setIsStreaming,
@@ -31185,6 +31293,26 @@ var init_chat2 = __esm({
31185
31293
  }, [ttsStop, isStreaming, handleStop]);
31186
31294
  const handleSend = (0, import_react79.useCallback)(
31187
31295
  (question, images, displayQuestion) => {
31296
+ sendAckRef.current?.resolve();
31297
+ const acknowledged = new Promise((resolve, reject) => {
31298
+ sendAckRef.current = { resolve, reject };
31299
+ });
31300
+ const runProvider = (systemPrompt2, providerImages2) => {
31301
+ aiProvider(systemPrompt2, question, providerImages2).catch((error) => {
31302
+ debugLogger.error("AI provider failed before streaming", {
31303
+ error: error instanceof Error ? error.message : String(error)
31304
+ });
31305
+ setIsStreaming(false);
31306
+ setResponseStarted(false);
31307
+ setIsSubmitting(false);
31308
+ setPendingMessage(null);
31309
+ const { conversations: convs, currentId: convId, replaceLastAnswer } = useConversationStore.getState();
31310
+ if (convs.find((c) => c.id === convId)?.history.at(-1)?.answer === "...") {
31311
+ replaceLastAnswer("_No response came back. Try sending again._", void 0, false, void 0, true);
31312
+ }
31313
+ settleSendAck({ kind: "failed", hadOutput: false, error });
31314
+ });
31315
+ };
31188
31316
  const requestStartTime = trackRequestStart();
31189
31317
  const questionForDisplay = displayQuestion || question;
31190
31318
  const pendingImages = images.length > 0 ? [...images] : void 0;
@@ -31223,10 +31351,15 @@ var init_chat2 = __esm({
31223
31351
  rawQuestion: question
31224
31352
  });
31225
31353
  const providerImages2 = pendingImages ? [...pendingImages] : [];
31226
- aiProvider(systemPrompt, question, providerImages2);
31354
+ runProvider(systemPrompt, providerImages2);
31227
31355
  }, 0);
31356
+ }).catch((error) => {
31357
+ debugLogger.error("Starting a new conversation failed", {
31358
+ error: error instanceof Error ? error.message : String(error)
31359
+ });
31360
+ settleSendAck({ kind: "failed", hadOutput: false, error });
31228
31361
  });
31229
- return;
31362
+ return acknowledged;
31230
31363
  }
31231
31364
  if (existing.name === "New Conversation") {
31232
31365
  generateName(question).then((newName) => {
@@ -31274,10 +31407,12 @@ var init_chat2 = __esm({
31274
31407
  }
31275
31408
  setResponse("");
31276
31409
  const providerImages = pendingImages ? [...pendingImages] : [];
31277
- aiProvider(systemPrompt, question, providerImages);
31410
+ runProvider(systemPrompt, providerImages);
31411
+ return acknowledged;
31278
31412
  },
31279
31413
  [
31280
31414
  aiProvider,
31415
+ settleSendAck,
31281
31416
  chatContainerRef,
31282
31417
  generateName,
31283
31418
  getScrollState,
@@ -31301,7 +31436,8 @@ var init_chat2 = __esm({
31301
31436
  }
31302
31437
  setInputValue("");
31303
31438
  setPastedImages([]);
31304
- handleSend(cleaned, [], cleaned);
31439
+ handleSend(cleaned, [], cleaned).catch(() => {
31440
+ });
31305
31441
  },
31306
31442
  [handleSend, setInputValue, setPastedImages]
31307
31443
  );
@@ -31879,12 +32015,12 @@ var init_chat2 = __esm({
31879
32015
  ossConfigured,
31880
32016
  isOSSMode: isOSSMode(),
31881
32017
  tier: featureFlag.getEvaluation()?.tier,
31882
- hasToken: authenticationService2.isAuthenticated(),
32018
+ hasToken: authenticationService.isAuthenticated(),
31883
32019
  playgroundBypassAuth,
31884
32020
  isPlaygroundRoute,
31885
32021
  bypassAuth
31886
32022
  });
31887
- if (!allowUnauthenticated && !bypassAuth && !authenticationService2.isAuthenticated()) {
32023
+ if (!allowUnauthenticated && !bypassAuth && !authenticationService.isAuthenticated()) {
31888
32024
  debugLogger.debug("User is not authenticated, redirecting to login");
31889
32025
  return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_router_dom6.Navigate, { to: "/login", replace: true });
31890
32026
  }
@@ -36704,7 +36840,7 @@ var useGatewayClient = () => {
36704
36840
  }
36705
36841
  return new GatewayService(
36706
36842
  gatewayUrl,
36707
- () => authenticationService2.getToken(),
36843
+ () => authenticationService.getToken(),
36708
36844
  feedbackEmail
36709
36845
  );
36710
36846
  }, [gatewayUrl, feedbackEmail]);
@@ -40553,7 +40689,7 @@ var buildHeaders2 = () => {
40553
40689
  const headers = {
40554
40690
  "Content-Type": "application/json"
40555
40691
  };
40556
- const token = authenticationService2.getToken();
40692
+ const token = authenticationService.getToken();
40557
40693
  if (token) {
40558
40694
  headers.Authorization = `Bearer ${token}`;
40559
40695
  }
@@ -43152,7 +43288,7 @@ var AIProviderInitService = class _AIProviderInitService {
43152
43288
  type: "ollama" /* OLLAMA */,
43153
43289
  baseUrl: settings.ollamaUrl,
43154
43290
  tokenFactory: () => {
43155
- let token = authenticationService2.getToken();
43291
+ let token = authenticationService.getToken();
43156
43292
  if (!token) {
43157
43293
  token = localStorage.getItem("authToken");
43158
43294
  }
@@ -43193,7 +43329,7 @@ var AIProviderInitService = class _AIProviderInitService {
43193
43329
  type: "ollama" /* OLLAMA */,
43194
43330
  baseUrl: settings.ollamaUrl || "http://localhost:11434",
43195
43331
  tokenFactory: () => {
43196
- let token = authenticationService2.getToken();
43332
+ let token = authenticationService.getToken();
43197
43333
  if (!token) {
43198
43334
  token = localStorage.getItem("authToken");
43199
43335
  }
@@ -43224,7 +43360,7 @@ var AIProviderInitService = class _AIProviderInitService {
43224
43360
  type: "ollama" /* OLLAMA */,
43225
43361
  baseUrl: "http://localhost:11434",
43226
43362
  tokenFactory: () => {
43227
- let token = authenticationService2.getToken();
43363
+ let token = authenticationService.getToken();
43228
43364
  if (!token) {
43229
43365
  token = localStorage.getItem("authToken");
43230
43366
  }
@@ -43314,7 +43450,7 @@ var AIProviderInitService = class _AIProviderInitService {
43314
43450
  return config;
43315
43451
  }
43316
43452
  config.tokenFactory = () => {
43317
- let token = authenticationService2.getToken();
43453
+ let token = authenticationService.getToken();
43318
43454
  if (!token && typeof localStorage !== "undefined") {
43319
43455
  try {
43320
43456
  token = localStorage.getItem("authToken");
@@ -43573,7 +43709,6 @@ var ProviderTab = () => {
43573
43709
  usePackageSettingsStore.setState({ settings: updatedSettings });
43574
43710
  }
43575
43711
  setProviderConfig(applyDefaultModel(normalizedConfig));
43576
- setIsProviderConfigOpen(false);
43577
43712
  showMessage("Provider configuration saved and switched successfully!", "success");
43578
43713
  debugLogger.info("Provider configuration saved and switched", { type: normalizedConfig.type });
43579
43714
  } catch (error) {
@@ -43961,7 +44096,7 @@ function buildUrl3(path) {
43961
44096
  }
43962
44097
  function authHeaders() {
43963
44098
  const headers = { "Content-Type": "application/json" };
43964
- const token = authenticationService2.getToken();
44099
+ const token = authenticationService.getToken();
43965
44100
  if (token) headers["Authorization"] = `Bearer ${token}`;
43966
44101
  return headers;
43967
44102
  }
@@ -45306,7 +45441,7 @@ var Management = () => {
45306
45441
  let isActive = true;
45307
45442
  const loadCapabilities = async () => {
45308
45443
  try {
45309
- const token = authToken ?? authenticationService2.getToken();
45444
+ const token = authToken ?? authenticationService.getToken();
45310
45445
  const headers = {
45311
45446
  "Content-Type": "application/json"
45312
45447
  };
@@ -45442,7 +45577,7 @@ var Management = () => {
45442
45577
  "admin",
45443
45578
  "platform_admin"
45444
45579
  ]);
45445
- const userRoles = (authenticationService2.parseJwtClaims(authenticationService2.getToken() ?? "")?.roles ?? []).map((r) => r.toLowerCase());
45580
+ const userRoles = (authenticationService.parseJwtClaims(authenticationService.getToken() ?? "")?.roles ?? []).map((r) => r.toLowerCase());
45446
45581
  const isWorkspaceAdmin = userRoles.some(
45447
45582
  (r) => platformAdminRoles.has(r) || r.endsWith("-admin")
45448
45583
  );