@burtson-labs/bandit-engine 2.0.159 → 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 +2 -2
  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-TCW753JI.mjs → chunk-PXCIDXRP.mjs} +6 -6
  23. package/dist/{chunk-2X3ZTDUW.mjs → chunk-TKSOJGHX.mjs} +5 -5
  24. package/dist/{chunk-ZOCXFQID.mjs → chunk-XTB6ZLUI.mjs} +2 -2
  25. package/dist/{chunk-ZOCXFQID.mjs.map → chunk-XTB6ZLUI.mjs.map} +1 -1
  26. package/dist/index.js +298 -163
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +11 -11
  29. package/dist/management/management.js +292 -157
  30. package/dist/management/management.js.map +1 -1
  31. package/dist/management/management.mjs +9 -9
  32. package/dist/modals/chat-modal/chat-modal.js +9 -7
  33. package/dist/modals/chat-modal/chat-modal.js.map +1 -1
  34. package/dist/modals/chat-modal/chat-modal.mjs +5 -5
  35. package/dist/{modelStore-KBUW3XAV.mjs → modelStore-NMCRL3FR.mjs} +2 -2
  36. package/package.json +6 -4
  37. package/dist/chat-HM563VOV.mjs +0 -16
  38. package/dist/chunk-7HC6ONWM.mjs.map +0 -1
  39. package/dist/chunk-F5JS4W2K.mjs.map +0 -1
  40. package/dist/chunk-GWQRTUKF.mjs.map +0 -1
  41. package/dist/chunk-GZDJNVAS.mjs.map +0 -1
  42. package/dist/chunk-IS2LWSBZ.mjs.map +0 -1
  43. package/dist/chunk-R45DM35B.mjs.map +0 -1
  44. /package/dist/{aiProviderStore-ZQ5VVVKQ.mjs.map → aiProviderStore-BNZGM6QU.mjs.map} +0 -0
  45. /package/dist/{chat-HM563VOV.mjs.map → chat-4BOIV4VP.mjs.map} +0 -0
  46. /package/dist/{chunk-B2TJZM4Z.mjs.map → chunk-P6SLB5RC.mjs.map} +0 -0
  47. /package/dist/{chunk-TCW753JI.mjs.map → chunk-PXCIDXRP.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
package/dist/index.js CHANGED
@@ -232,7 +232,7 @@ function emitAuthTokenChanged(token) {
232
232
  detail: { token }
233
233
  }));
234
234
  }
235
- var TOKEN_KEY2, AUTH_TOKEN_CHANGED_EVENT, AuthenticationService, authenticationService2;
235
+ var TOKEN_KEY2, AUTH_TOKEN_CHANGED_EVENT, AuthenticationService, authenticationService;
236
236
  var init_authenticationService = __esm({
237
237
  "src/services/auth/authenticationService.ts"() {
238
238
  "use strict";
@@ -285,7 +285,7 @@ var init_authenticationService = __esm({
285
285
  }
286
286
  }
287
287
  };
288
- authenticationService2 = new AuthenticationService();
288
+ authenticationService = new AuthenticationService();
289
289
  }
290
290
  });
291
291
 
@@ -2149,7 +2149,7 @@ function buildUrl(path) {
2149
2149
  }
2150
2150
  function buildHeaders() {
2151
2151
  const headers = { "Content-Type": "application/json" };
2152
- const token = authenticationService2.getToken();
2152
+ const token = authenticationService.getToken();
2153
2153
  if (token) {
2154
2154
  headers["Authorization"] = `Bearer ${token}`;
2155
2155
  }
@@ -2297,7 +2297,7 @@ function resolveAuthIdentity(token) {
2297
2297
  if (!token) {
2298
2298
  return null;
2299
2299
  }
2300
- const claims = authenticationService2.parseJwtClaims(token);
2300
+ const claims = authenticationService.parseJwtClaims(token);
2301
2301
  if (claims?.sub) {
2302
2302
  return claims.sub;
2303
2303
  }
@@ -2830,7 +2830,7 @@ var init_conversationSyncStore = __esm({
2830
2830
  async initialize() {
2831
2831
  ensureTrackersInitialized();
2832
2832
  const gatewayUrl = usePackageSettingsStore.getState().settings?.gatewayApiUrl;
2833
- const token = authenticationService2.getToken();
2833
+ const token = authenticationService.getToken();
2834
2834
  const tokenIdentity = resolveAuthIdentity(token);
2835
2835
  const current = get();
2836
2836
  const storedIdentity = getStoredSyncIdentity();
@@ -2956,10 +2956,10 @@ var init_conversationSyncStore = __esm({
2956
2956
  });
2957
2957
  set({
2958
2958
  hasLoadedPreference: true,
2959
- initializedForToken: authenticationService2.getToken(),
2960
- initializedForIdentity: resolveAuthIdentity(authenticationService2.getToken())
2959
+ initializedForToken: authenticationService.getToken(),
2960
+ initializedForIdentity: resolveAuthIdentity(authenticationService.getToken())
2961
2961
  });
2962
- setStoredSyncIdentity(resolveAuthIdentity(authenticationService2.getToken()));
2962
+ setStoredSyncIdentity(resolveAuthIdentity(authenticationService.getToken()));
2963
2963
  if (enabled) {
2964
2964
  set({ hasCompletedInitialUpload: false });
2965
2965
  }
@@ -2999,10 +2999,10 @@ var init_conversationSyncStore = __esm({
2999
2999
  });
3000
3000
  set({
3001
3001
  hasLoadedPreference: true,
3002
- initializedForToken: authenticationService2.getToken(),
3003
- initializedForIdentity: resolveAuthIdentity(authenticationService2.getToken())
3002
+ initializedForToken: authenticationService.getToken(),
3003
+ initializedForIdentity: resolveAuthIdentity(authenticationService.getToken())
3004
3004
  });
3005
- setStoredSyncIdentity(resolveAuthIdentity(authenticationService2.getToken()));
3005
+ setStoredSyncIdentity(resolveAuthIdentity(authenticationService.getToken()));
3006
3006
  if (preference.syncEnabled && preference.isAdvancedVectorFeaturesEnabled) {
3007
3007
  await get().runSync({ force: true });
3008
3008
  }
@@ -3042,7 +3042,7 @@ var init_conversationSyncStore = __esm({
3042
3042
  debugLogger.error("conversationSyncStore: runSync error - missing gateway URL");
3043
3043
  return;
3044
3044
  }
3045
- const token = authenticationService2.getToken();
3045
+ const token = authenticationService.getToken();
3046
3046
  if (!token) {
3047
3047
  set({ status: "error", lastError: "Authentication required to sync conversations." });
3048
3048
  debugLogger.error("conversationSyncStore: runSync error - missing auth token");
@@ -3342,7 +3342,7 @@ var init_mcpServersService = __esm({
3342
3342
  };
3343
3343
  authHeaders = () => {
3344
3344
  const headers = { "Content-Type": "application/json" };
3345
- const token = authenticationService2.getToken();
3345
+ const token = authenticationService.getToken();
3346
3346
  if (token) headers["Authorization"] = `Bearer ${token}`;
3347
3347
  return headers;
3348
3348
  };
@@ -4784,7 +4784,7 @@ var init_gateway_service = __esm({
4784
4784
  return;
4785
4785
  }
4786
4786
  try {
4787
- authenticationService2.clearToken();
4787
+ authenticationService.clearToken();
4788
4788
  } catch {
4789
4789
  }
4790
4790
  if (typeof window !== "undefined" && typeof window.dispatchEvent === "function") {
@@ -6132,7 +6132,7 @@ var init_playground_provider = __esm({
6132
6132
  PlaygroundProvider = class {
6133
6133
  config;
6134
6134
  constructor(config) {
6135
- this.config = { type: "playground" /* PLAYGROUND */, ...config };
6135
+ this.config = { ...config, type: config.type ?? "playground" /* PLAYGROUND */ };
6136
6136
  }
6137
6137
  chat(request) {
6138
6138
  const lastUserMessage = [...request.messages].reverse().find((m) => m.role === "user")?.content ?? "";
@@ -10570,7 +10570,7 @@ var init_useKnowledgeStore = __esm({
10570
10570
  init_indexedDBService();
10571
10571
  import_uuid5 = require("uuid");
10572
10572
  import_mammoth = __toESM(require("mammoth"));
10573
- pdfjsLib = __toESM(require("pdfjs-dist/legacy/build/pdf"));
10573
+ pdfjsLib = __toESM(require("pdfjs-dist/legacy/build/pdf.mjs"));
10574
10574
  init_embeddingService();
10575
10575
  init_debugLogger();
10576
10576
  init_vectorDatabaseService();
@@ -11690,7 +11690,7 @@ ${listMarkdown}`;
11690
11690
  children
11691
11691
  }
11692
11692
  );
11693
- const CodeRenderer = ({ inline, className, children, ...props }) => {
11693
+ const CodeRenderer = ({ node: _node, className, children, ...props }) => {
11694
11694
  const match = /language-([\w-]+)/.exec(className || "");
11695
11695
  const requestedLanguage = match?.[1]?.toLowerCase() ?? "";
11696
11696
  const codeText = String(children).replace(/\n$/, "");
@@ -12862,7 +12862,7 @@ var init_knowledge_file_modal = __esm({
12862
12862
  import_material9 = require("@mui/material");
12863
12863
  init_useVectorStore();
12864
12864
  init_packageSettingsStore();
12865
- pdfjsLib2 = __toESM(require("pdfjs-dist/legacy/build/pdf"));
12865
+ pdfjsLib2 = __toESM(require("pdfjs-dist/legacy/build/pdf.mjs"));
12866
12866
  init_lowlight();
12867
12867
  init_markdownRendering();
12868
12868
  init_debugLogger();
@@ -13613,7 +13613,7 @@ var init_voiceService = __esm({
13613
13613
  debugLogger.error("Gateway API URL not configured");
13614
13614
  throw new Error("Gateway API URL not configured");
13615
13615
  }
13616
- const token = authenticationService2.getToken();
13616
+ const token = authenticationService.getToken();
13617
13617
  if (!token) {
13618
13618
  debugLogger.warn("No JWT token available - skipping voice models fetch");
13619
13619
  const emptyModels = {
@@ -13623,7 +13623,7 @@ var init_voiceService = __esm({
13623
13623
  };
13624
13624
  return emptyModels;
13625
13625
  }
13626
- if (authenticationService2.isTokenExpired(token)) {
13626
+ if (authenticationService.isTokenExpired(token)) {
13627
13627
  debugLogger.warn("JWT token is expired - skipping voice models fetch");
13628
13628
  const emptyModels = {
13629
13629
  models: [],
@@ -13712,7 +13712,7 @@ var init_tts_client = __esm({
13712
13712
  init_packageSettingsStore();
13713
13713
  init_debugLogger();
13714
13714
  getOrAppendAuthHeader = (existing = {}) => {
13715
- const token = authenticationService2.getToken();
13715
+ const token = authenticationService.getToken();
13716
13716
  if (token) {
13717
13717
  existing["Authorization"] = `Bearer ${token}`;
13718
13718
  return existing;
@@ -14409,8 +14409,8 @@ var init_ai_response_action_bar = __esm({
14409
14409
  const { preferences } = usePreferencesStore();
14410
14410
  const { settings: packageSettings } = usePackageSettingsStore();
14411
14411
  (0, import_react18.useEffect)(() => {
14412
- const isAuthenticated = authenticationService2.isAuthenticated();
14413
- const token = authenticationService2.getToken();
14412
+ const isAuthenticated = authenticationService.isAuthenticated();
14413
+ const token = authenticationService.getToken();
14414
14414
  if (!initialized || availableVoices.length === 0) {
14415
14415
  if (token && isAuthenticated) {
14416
14416
  debugLogger.debug("AI Action Bar: Voices not initialized or unavailable, attempting to load from API");
@@ -15254,11 +15254,12 @@ ${sourcesMarkdownList.join("\n")}`;
15254
15254
  tr({ children, node: _node, ...tableRowProps }) {
15255
15255
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_material11.TableRow, { ...tableRowProps, children });
15256
15256
  },
15257
- th({ children, node: _node, ...tableCellProps }) {
15257
+ th({ children, node: _node, align, ...tableCellProps }) {
15258
15258
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
15259
15259
  import_material11.TableCell,
15260
15260
  {
15261
15261
  ...tableCellProps,
15262
+ align: align === "char" ? void 0 : align,
15262
15263
  sx: {
15263
15264
  fontWeight: "bold",
15264
15265
  color: theme.palette.text.primary,
@@ -15271,11 +15272,12 @@ ${sourcesMarkdownList.join("\n")}`;
15271
15272
  }
15272
15273
  );
15273
15274
  },
15274
- td({ children, node: _node, ...tableCellProps }) {
15275
+ td({ children, node: _node, align, ...tableCellProps }) {
15275
15276
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
15276
15277
  import_material11.TableCell,
15277
15278
  {
15278
15279
  ...tableCellProps,
15280
+ align: align === "char" ? void 0 : align,
15279
15281
  sx: {
15280
15282
  color: theme.palette.text.primary,
15281
15283
  fontSize: "0.875rem",
@@ -16073,7 +16075,7 @@ var init_engineStore = __esm({
16073
16075
  }
16074
16076
  try {
16075
16077
  const headers = { "Content-Type": "application/json" };
16076
- const token = authenticationService2.getToken();
16078
+ const token = authenticationService.getToken();
16077
16079
  if (token) headers["Authorization"] = `Bearer ${token}`;
16078
16080
  const res = await fetch(`${base}/models`, { headers });
16079
16081
  const data = await res.json();
@@ -16392,7 +16394,7 @@ var init_stt_client = __esm({
16392
16394
  filename,
16393
16395
  url: `${gatewayUrl}/stt/transcribe`
16394
16396
  });
16395
- const token = authenticationService2.getToken();
16397
+ const token = authenticationService.getToken();
16396
16398
  const response = await fetch(`${gatewayUrl}/stt/transcribe`, {
16397
16399
  method: "POST",
16398
16400
  headers: {
@@ -17336,7 +17338,7 @@ var init_memory_modal = __esm({
17336
17338
  color: (0, import_styles8.alpha)(theme.palette.text.secondary, 0.7),
17337
17339
  maxWidth: 280
17338
17340
  }, 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." })
17339
- ] }) : filteredMemories.map((memory) => {
17341
+ ] }) : filteredMemories.map((memory, memoryIndex) => {
17340
17342
  const isSelected = selectedIds.includes(memory.id);
17341
17343
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react25.default.Fragment, { children: [
17342
17344
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_material16.List, { sx: { listStyle: "none", pl: 0, m: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
@@ -17536,7 +17538,7 @@ var init_memory_modal = __esm({
17536
17538
  ]
17537
17539
  }
17538
17540
  ) }),
17539
- filteredMemories.indexOf(memory) < filteredMemories.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_material16.Divider, { sx: { bgcolor: (0, import_styles8.alpha)(appBar.border, 0.5), my: 0.5 } })
17541
+ memoryIndex < filteredMemories.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_material16.Divider, { sx: { bgcolor: (0, import_styles8.alpha)(appBar.border, 0.5), my: 0.5 } })
17540
17542
  ] }, memory.id);
17541
17543
  })
17542
17544
  }
@@ -18000,6 +18002,109 @@ var init_memory_modal = __esm({
18000
18002
  }
18001
18003
  });
18002
18004
 
18005
+ // src/chat/composer-submission.ts
18006
+ var isPromiseLike, awaitAcceptance, buildSubmission, isImeComposing;
18007
+ var init_composer_submission = __esm({
18008
+ "src/chat/composer-submission.ts"() {
18009
+ "use strict";
18010
+ isPromiseLike = (value) => !!value && typeof value.then === "function";
18011
+ awaitAcceptance = async (ack) => {
18012
+ if (ack === false) return false;
18013
+ if (!isPromiseLike(ack)) return true;
18014
+ try {
18015
+ return await ack !== false;
18016
+ } catch {
18017
+ return false;
18018
+ }
18019
+ };
18020
+ buildSubmission = (text, images, documents) => {
18021
+ const typed = text.trim();
18022
+ const base = typed || (images.length === 1 ? "[\u{1F4F7} You attached an image]" : images.length > 1 ? `[\u{1F4F7} You attached ${images.length} images]` : "");
18023
+ const fileSummary = documents.length > 0 ? `[\u{1F4CE} Attached ${documents.length} file${documents.length > 1 ? "s" : ""}: ${documents.map((d) => d.fileName).join(", ")}]` : "";
18024
+ const displayQuestion = [base || (fileSummary ? "[\u{1F4E4} You submitted content without text]" : ""), fileSummary].filter(Boolean).join("\n\n");
18025
+ const prompt = [text, ...documents.map((d) => d.block)].filter(Boolean).join("\n\n");
18026
+ return { prompt, images: [...images], displayQuestion };
18027
+ };
18028
+ isImeComposing = (event) => event.nativeEvent?.isComposing === true || event.keyCode === 229;
18029
+ }
18030
+ });
18031
+
18032
+ // src/chat/document-import.ts
18033
+ var CODE_EXTENSIONS, TEXT_EXTENSIONS, isCodeFileName, isPlainTextFile, CONTROL_CHARS, INVISIBLE_FORMATTING, stripInvisible, neutralizeRoleMarkers, prepareDocumentText, formatDocumentBlock, readText, readPdfText, readDocument;
18034
+ var init_document_import = __esm({
18035
+ "src/chat/document-import.ts"() {
18036
+ "use strict";
18037
+ CODE_EXTENSIONS = [".js", ".ts", ".tsx", ".json", ".py", ".java", ".c", ".cpp", ".cs"];
18038
+ TEXT_EXTENSIONS = [".md", ".txt", ...CODE_EXTENSIONS];
18039
+ isCodeFileName = (name) => {
18040
+ const lower = name.toLowerCase();
18041
+ return CODE_EXTENSIONS.some((ext) => lower.endsWith(ext));
18042
+ };
18043
+ isPlainTextFile = (name, mimeType = "") => mimeType.startsWith("text/") || TEXT_EXTENSIONS.some((ext) => name.toLowerCase().endsWith(ext));
18044
+ CONTROL_CHARS = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g;
18045
+ INVISIBLE_FORMATTING = /[\u202A-\u202E\u2066-\u2069\u200B\u2060\uFEFF]/g;
18046
+ stripInvisible = (text) => text.normalize("NFC").replace(CONTROL_CHARS, "").replace(INVISIBLE_FORMATTING, "");
18047
+ neutralizeRoleMarkers = (text) => text.replace(/<\s*\/?\s*script\b[^>]*>/gi, "").replace(/(^|\n)([ \t]*)\/?(system|assistant|user)\s*:/gi, "$1$2[removed-role]");
18048
+ prepareDocumentText = (text, fileName) => {
18049
+ const visible = stripInvisible(text);
18050
+ return (isCodeFileName(fileName) ? visible : neutralizeRoleMarkers(visible)).trim();
18051
+ };
18052
+ formatDocumentBlock = (fileName, text) => `\u{1F4C4} ${fileName}
18053
+ ${prepareDocumentText(text, fileName)}`;
18054
+ readText = (file) => typeof file.text === "function" ? file.text() : new Promise((resolve, reject) => {
18055
+ const reader = new FileReader();
18056
+ reader.onload = () => resolve(String(reader.result ?? ""));
18057
+ reader.onerror = () => reject(reader.error ?? new Error("It couldn't be read."));
18058
+ reader.readAsText(file);
18059
+ });
18060
+ readPdfText = async (file, maxPages = 10) => {
18061
+ const pdfjsLib3 = await import("pdfjs-dist/legacy/build/pdf.mjs");
18062
+ pdfjsLib3.GlobalWorkerOptions.workerSrc = "https://cdn.burtson.ai/scripts/pdf.worker.js";
18063
+ const pdf = await pdfjsLib3.getDocument({ data: await file.arrayBuffer() }).promise;
18064
+ const pages = await Promise.all(
18065
+ Array.from({ length: Math.min(pdf.numPages, maxPages) }, async (_, i) => {
18066
+ try {
18067
+ const page = await pdf.getPage(i + 1);
18068
+ const content = await page.getTextContent();
18069
+ return content.items.map((item) => typeof item?.str === "string" ? item.str : "").join(" ");
18070
+ } catch {
18071
+ return "";
18072
+ }
18073
+ })
18074
+ );
18075
+ return pages.join("\n\n");
18076
+ };
18077
+ readDocument = async (file) => {
18078
+ const name = file.name.toLowerCase();
18079
+ try {
18080
+ let text;
18081
+ if (isPlainTextFile(name, file.type)) {
18082
+ text = await readText(file);
18083
+ } else if (name.endsWith(".docx")) {
18084
+ const mammoth2 = await import("mammoth");
18085
+ text = (await mammoth2.extractRawText({ arrayBuffer: await file.arrayBuffer() })).value;
18086
+ } else if (name.endsWith(".pdf")) {
18087
+ text = await readPdfText(file);
18088
+ } else {
18089
+ return { ok: false, fileName: file.name, reason: "This file type can't be read yet." };
18090
+ }
18091
+ const block = formatDocumentBlock(file.name, text);
18092
+ if (block === `\u{1F4C4} ${file.name}
18093
+ `) {
18094
+ return { ok: false, fileName: file.name, reason: "No readable text was found in it." };
18095
+ }
18096
+ return { ok: true, fileName: file.name, block };
18097
+ } catch (error) {
18098
+ return {
18099
+ ok: false,
18100
+ fileName: file.name,
18101
+ reason: error instanceof Error && error.message ? error.message : "It couldn't be read."
18102
+ };
18103
+ }
18104
+ };
18105
+ }
18106
+ });
18107
+
18003
18108
  // src/store/voiceModeStore.ts
18004
18109
  var import_zustand16, useVoiceModeStore;
18005
18110
  var init_voiceModeStore = __esm({
@@ -18054,6 +18159,8 @@ var init_chat_input = __esm({
18054
18159
  init_packageSettingsStore();
18055
18160
  init_transcriber();
18056
18161
  init_memory_modal();
18162
+ init_composer_submission();
18163
+ init_document_import();
18057
18164
  import_styles9 = require("@mui/material/styles");
18058
18165
  init_debugLogger();
18059
18166
  init_brandingService();
@@ -18222,102 +18329,60 @@ var init_chat_input = __esm({
18222
18329
  const removeImage = (index) => {
18223
18330
  setPastedImages((prev) => prev.filter((_, i) => i !== index));
18224
18331
  };
18332
+ const latestDraftRef = (0, import_react26.useRef)({ text: inputValue, images: pastedImages, files: fileInputs });
18333
+ latestDraftRef.current = { text: inputValue, images: pastedImages, files: fileInputs };
18334
+ const submittingRef = (0, import_react26.useRef)(false);
18335
+ const [sendError, setSendError] = (0, import_react26.useState)(null);
18225
18336
  const handleSubmit = async () => {
18226
- if (inputValue.trim() === "" && pastedImages.length === 0 && fileInputs.length === 0)
18227
- return;
18228
- const tempImages = [...pastedImages];
18229
- const escapePromptInjection = (text) => {
18230
- return text.replace(/<\s*\/?script.*?>/gi, "").replace(/```.*?```/gs, "").replace(/(?<=\n|^)\/?(system|assistant|user):/gi, "[removed-role]");
18231
- };
18232
- const getFileText = async (file) => {
18233
- 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");
18234
- const sanitize = (text, name2) => isCodeFile2(name2) ? text.trim() : text.replace(/[^\x20-\x7E\r\n\t]/g, "").trim();
18235
- const name = file.name.toLowerCase();
18236
- try {
18237
- 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")) {
18238
- const text = await file.text();
18239
- if (name.endsWith(".txt") || name.endsWith(".md") || name.endsWith(".docx") || name.endsWith(".pdf")) {
18240
- return `\u{1F4C4} ${file.name}
18241
- ${sanitize(escapePromptInjection(text), file.name)}`;
18242
- } else {
18243
- return `\u{1F4C4} ${file.name}
18244
- ${sanitize(text, file.name)}`;
18245
- }
18246
- }
18247
- if (name.endsWith(".docx")) {
18248
- const mammoth2 = await import("mammoth");
18249
- const arrayBuffer = await file.arrayBuffer();
18250
- const { value } = await mammoth2.extractRawText({ arrayBuffer });
18251
- return `\u{1F4C4} ${file.name}
18252
- ${sanitize(escapePromptInjection(value), file.name)}`;
18253
- }
18254
- if (name.endsWith(".pdf")) {
18255
- const pdfjsLib3 = await import("pdfjs-dist/legacy/build/pdf");
18256
- pdfjsLib3.GlobalWorkerOptions.workerSrc = "https://cdn.burtson.ai/scripts/pdf.worker.js";
18257
- const buffer = await file.arrayBuffer();
18258
- const loadingTask = pdfjsLib3.getDocument({ data: buffer });
18259
- try {
18260
- const pdf = await loadingTask.promise;
18261
- debugLogger.info("PDF loaded", { numPages: pdf.numPages });
18262
- const maxPages = Math.min(pdf.numPages, 10);
18263
- const pages = await Promise.all(
18264
- Array.from({ length: maxPages }, async (_, i) => {
18265
- try {
18266
- const page = await pdf.getPage(i + 1);
18267
- const content = await page.getTextContent();
18268
- return content.items.map((item) => {
18269
- if (item && typeof item === "object" && "str" in item && typeof item.str === "string") {
18270
- return item.str;
18271
- }
18272
- return "";
18273
- }).join(" ");
18274
- } catch (err) {
18275
- debugLogger.error(`Failed to read page ${i + 1}`, { error: err });
18276
- return "";
18277
- }
18278
- })
18279
- );
18280
- const joined = pages.join("\n\n");
18281
- debugLogger.info("Extracted PDF content", { contentPreview: joined.slice(0, 500) });
18282
- return `\u{1F4C4} ${file.name}
18283
- ${sanitize(
18284
- escapePromptInjection(joined),
18285
- file.name
18286
- )}`;
18287
- } catch (error) {
18288
- debugLogger.error("PDF load failed", {
18289
- error: error instanceof Error ? error.message : String(error),
18290
- fileName: file.name
18291
- });
18292
- return "";
18293
- }
18337
+ if (submittingRef.current) return;
18338
+ if (inputValue.trim() === "" && pastedImages.length === 0 && fileInputs.length === 0) return;
18339
+ submittingRef.current = true;
18340
+ const draft = { text: inputValue, images: [...pastedImages], files: [...fileInputs] };
18341
+ try {
18342
+ const reads = await Promise.all(draft.files.map(readDocument));
18343
+ const failed = reads.filter((r) => !r.ok);
18344
+ if (failed.length > 0) {
18345
+ setSendError(
18346
+ failed.map((f) => `Couldn't attach ${f.fileName}: ${f.reason}`).join(" ")
18347
+ );
18348
+ return;
18349
+ }
18350
+ const documents = reads.filter((r) => r.ok);
18351
+ const submission = buildSubmission(draft.text, draft.images, documents);
18352
+ debugLogger.debug("Composer submission", {
18353
+ documents: documents.map((d) => d.fileName),
18354
+ promptLength: submission.prompt.length
18355
+ });
18356
+ setSendError(null);
18357
+ setInputValue("");
18358
+ setPastedImages([]);
18359
+ setFileInputs([]);
18360
+ if (fileInputRef.current) fileInputRef.current.value = "";
18361
+ inputRef.current?.blur();
18362
+ const accepted = await awaitAcceptance(
18363
+ onSend(submission.prompt, submission.images, submission.displayQuestion)
18364
+ );
18365
+ if (!accepted) {
18366
+ const now = latestDraftRef.current;
18367
+ const untouched = now.text.trim() === "" && now.images.length === 0 && now.files.length === 0;
18368
+ if (untouched) {
18369
+ setInputValue(draft.text);
18370
+ setPastedImages(draft.images);
18371
+ setFileInputs(draft.files);
18372
+ setSendError("Your message wasn't sent. It's back in the box so you can try again.");
18373
+ } else {
18374
+ setSendError("Your last message wasn't sent.");
18294
18375
  }
18295
- return "";
18296
- } catch {
18297
- return "";
18298
18376
  }
18299
- };
18300
- const fileTexts = await Promise.all(fileInputs.map(getFileText));
18301
- debugLogger.debug("All extracted file texts", { fileTexts });
18302
- const fileContent = fileTexts.filter(Boolean).join("\n\n");
18303
- const tempQuestionBase = inputValue.trim() || (tempImages.length === 1 ? "[\u{1F4F7} You attached an image]" : tempImages.length > 1 ? `[\u{1F4F7} You attached ${tempImages.length} images]` : "");
18304
- const fileDisplaySummary = fileInputs.length > 0 ? `[\u{1F4CE} Attached ${fileInputs.length} file${fileInputs.length > 1 ? "s" : ""}: ${fileInputs.map((f) => f.name).join(", ")}]` : "";
18305
- const displayQuestion = [
18306
- tempQuestionBase || (fileDisplaySummary ? "[\u{1F4E4} You submitted content without text]" : ""),
18307
- fileDisplaySummary
18308
- ].filter(Boolean).join("\n\n");
18309
- const fullPrompt = [inputValue, fileContent].filter(Boolean).join("\n\n");
18310
- setInputValue("");
18311
- setPastedImages([]);
18312
- setFileInputs([]);
18313
- onSend(fullPrompt, tempImages, displayQuestion);
18314
- inputRef.current?.blur();
18315
- fileInputRef.current.value = "";
18377
+ } finally {
18378
+ submittingRef.current = false;
18379
+ }
18316
18380
  };
18317
18381
  const handleKeyPress = (e) => {
18382
+ if (isImeComposing(e)) return;
18318
18383
  if (e.key === "Enter" && !e.shiftKey) {
18319
18384
  e.preventDefault();
18320
- handleSubmit();
18385
+ void handleSubmit();
18321
18386
  }
18322
18387
  };
18323
18388
  const handleTranscriptionCompleted = (transcribed) => {
@@ -18435,6 +18500,15 @@ ${sanitize(
18435
18500
  gap: isMobile ? compactMobile ? 0.75 : 1 : 0.5
18436
18501
  },
18437
18502
  children: [
18503
+ sendError && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18504
+ import_material17.Typography,
18505
+ {
18506
+ role: "alert",
18507
+ variant: "caption",
18508
+ sx: { color: theme.palette.error.main, px: 0.5, pt: isMobile ? 0 : 0.5 },
18509
+ children: sendError
18510
+ }
18511
+ ),
18438
18512
  /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18439
18513
  import_material17.Box,
18440
18514
  {
@@ -18480,7 +18554,10 @@ ${sanitize(
18480
18554
  import_material17.IconButton,
18481
18555
  {
18482
18556
  size: "small",
18483
- onClick: () => setFileInputs((prev) => prev.filter((_, i) => i !== idx)),
18557
+ onClick: () => {
18558
+ setSendError(null);
18559
+ setFileInputs((prev) => prev.filter((_, i) => i !== idx));
18560
+ },
18484
18561
  sx: { ml: 0.5, color: theme.palette.mode === "dark" ? "#aaa" : "#444" },
18485
18562
  children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(CloseIcon, { sx: { fontSize: 14 } })
18486
18563
  }
@@ -19488,7 +19565,7 @@ function syncTelemetry() {
19488
19565
  const settings = usePackageSettingsStore.getState().settings;
19489
19566
  const cfg = resolveTelemetryConfig({
19490
19567
  telemetry: settings?.telemetry,
19491
- banditApiKey: authenticationService2.getToken() ?? void 0
19568
+ banditApiKey: authenticationService.getToken() ?? void 0
19492
19569
  });
19493
19570
  active = cfg ? new TelemetryExporter(cfg) : null;
19494
19571
  } catch {
@@ -20139,7 +20216,7 @@ var init_mcpService = __esm({
20139
20216
  }
20140
20217
  if (tool.mcpServerId) {
20141
20218
  const base2 = settings.gatewayApiUrl.replace(/\/$/, "");
20142
- const token2 = authenticationService2.getToken();
20219
+ const token2 = authenticationService.getToken();
20143
20220
  const controller2 = new AbortController();
20144
20221
  const timeoutId2 = setTimeout(() => controller2.abort(), 3e4);
20145
20222
  try {
@@ -20186,7 +20263,7 @@ var init_mcpService = __esm({
20186
20263
  "Content-Type": "application/json"
20187
20264
  }
20188
20265
  };
20189
- const token = authenticationService2.getToken();
20266
+ const token = authenticationService.getToken();
20190
20267
  if (token) {
20191
20268
  requestOptions.headers = {
20192
20269
  ...requestOptions.headers,
@@ -20286,6 +20363,7 @@ var init_useAIProvider = __esm({
20286
20363
  "src/chat/hooks/useAIProvider.tsx"() {
20287
20364
  "use strict";
20288
20365
  init_debugLogger();
20366
+ init_authenticationService();
20289
20367
  import_react31 = require("react");
20290
20368
  init_knowledgeStore();
20291
20369
  init_aiProviderStore();
@@ -20587,8 +20665,11 @@ var init_useAIProvider = __esm({
20587
20665
  history,
20588
20666
  addHistory,
20589
20667
  inputRef,
20590
- onError
20668
+ onError,
20669
+ onTurnOutcome
20591
20670
  }) => {
20671
+ const onTurnOutcomeRef = (0, import_react31.useRef)(onTurnOutcome);
20672
+ onTurnOutcomeRef.current = onTurnOutcome;
20592
20673
  const currentSubRef = (0, import_react31.useRef)(null);
20593
20674
  const lastPartialRef = (0, import_react31.useRef)({
20594
20675
  text: "",
@@ -20634,10 +20715,14 @@ var init_useAIProvider = __esm({
20634
20715
  };
20635
20716
  const runAIProvider = (0, import_react31.useCallback)(
20636
20717
  async (systemPrompt, question, images) => {
20718
+ let turnHadOutput = false;
20719
+ const reportOutcome = (outcome) => onTurnOutcomeRef.current?.(outcome);
20637
20720
  if (!provider) {
20638
20721
  debugLogger.error("No AI provider configured");
20639
20722
  overrideComponentStatus("Error");
20640
- if (onError) onError(new Error("No AI provider configured"));
20723
+ const error = new Error("No AI provider configured");
20724
+ if (onError) onError(error);
20725
+ reportOutcome({ kind: "failed", hadOutput: false, error });
20641
20726
  return;
20642
20727
  }
20643
20728
  const userPreferences = preferences;
@@ -20791,7 +20876,7 @@ Use this information to personalize your responses whenever relevant.` : "";
20791
20876
  s3Url: resolvedId,
20792
20877
  isUserContent: result.isUserContent,
20793
20878
  isTeamContent: result.isTeamContent,
20794
- contentSource: result.contentSource,
20879
+ contentSource: result.contentSource === "user" || result.contentSource === "team" ? result.contentSource : void 0,
20795
20880
  originalFileName: result.originalFileName || result.filename || result.name,
20796
20881
  // Preserve original data for downstream checks (e.g. vector modal detection)
20797
20882
  _vectorResult: result
@@ -21146,6 +21231,10 @@ ${protocol}`;
21146
21231
  const sub = stream.subscribe({
21147
21232
  next: (data) => {
21148
21233
  if (!data?.message?.content && !data?.message?.tool_calls) return;
21234
+ if (!turnHadOutput) {
21235
+ turnHadOutput = true;
21236
+ reportOutcome({ kind: "output" });
21237
+ }
21149
21238
  if (Array.isArray(data.message.tool_calls) && data.message.tool_calls.length > 0) {
21150
21239
  nativeToolCalls.push(...data.message.tool_calls);
21151
21240
  sawToolBlock = true;
@@ -21180,13 +21269,14 @@ ${protocol}`;
21180
21269
  },
21181
21270
  error: (err) => {
21182
21271
  debugLogger.error("Stream error:", err);
21272
+ reportOutcome({ kind: "failed", hadOutput: turnHadOutput, error: err });
21183
21273
  stopWorking();
21184
21274
  overrideComponentStatus("Idle");
21185
21275
  setIsSubmitting(false);
21186
21276
  setIsStreaming(false);
21187
21277
  if (/lifetime validation|token.*(expired|invalid)|unauthorized|\b401\b/i.test(err.message || "")) {
21188
- const { replaceLastAnswer: replaceLastAnswer2 } = useConversationStore.getState();
21189
- replaceLastAnswer2(
21278
+ const { replaceLastAnswer: replaceLastAnswer3 } = useConversationStore.getState();
21279
+ replaceLastAnswer3(
21190
21280
  "_Your session ended \u2014 sign back in to continue._",
21191
21281
  lastPartialRef.current.images,
21192
21282
  false,
@@ -21213,8 +21303,8 @@ ${protocol}`;
21213
21303
  scope: rateLimit.scope,
21214
21304
  frontier: rateLimit.frontier
21215
21305
  });
21216
- const { replaceLastAnswer: replaceLastAnswer2 } = useConversationStore.getState();
21217
- replaceLastAnswer2(
21306
+ const { replaceLastAnswer: replaceLastAnswer3 } = useConversationStore.getState();
21307
+ replaceLastAnswer3(
21218
21308
  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._",
21219
21309
  lastPartialRef.current.images,
21220
21310
  false,
@@ -21235,10 +21325,16 @@ ${protocol}`;
21235
21325
  if (partial && !partial.trim().endsWith("\u2026") && !partial.trim().endsWith("...")) {
21236
21326
  partial = partial.trimEnd() + " \u2026";
21237
21327
  }
21328
+ const { replaceLastAnswer: replaceLastAnswer2 } = useConversationStore.getState();
21238
21329
  if (partial) {
21239
- const { replaceLastAnswer: replaceLastAnswer2 } = useConversationStore.getState();
21240
21330
  replaceLastAnswer2(partial, lastPartialRef.current.images, false, lastPartialRef.current.usedDocs, true);
21241
21331
  setResponse(partial);
21332
+ } else {
21333
+ const { conversations: convs, currentId: convId } = useConversationStore.getState();
21334
+ const last = convs.find((c) => c.id === convId)?.history.at(-1);
21335
+ if (last?.answer === "...") {
21336
+ replaceLastAnswer2("_No response came back. Try sending again._", lastPartialRef.current.images, false, lastPartialRef.current.usedDocs, true);
21337
+ }
21242
21338
  }
21243
21339
  setStreamBuffer("");
21244
21340
  setIsThinking?.(false);
@@ -21250,6 +21346,7 @@ ${protocol}`;
21250
21346
  }
21251
21347
  },
21252
21348
  complete: async () => {
21349
+ reportOutcome({ kind: "completed" });
21253
21350
  try {
21254
21351
  setIsThinking?.(false);
21255
21352
  telemetryEvent("tool_loop:llm_response", { responseLength: fullMessage.length });
@@ -21516,7 +21613,7 @@ _This link is temporary and expires in about ${mins} minutes._`;
21516
21613
  }
21517
21614
  } else {
21518
21615
  const data = result.data ?? {};
21519
- 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");
21616
+ 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");
21520
21617
  resultText = informative.length ? informative.join(" \u2014 ") : `I couldn't complete that request: ${result.error || "Unknown error"}.`;
21521
21618
  }
21522
21619
  enhancedMessage = enhancedMessage.replace(placeholderToken, resultText);
@@ -21863,6 +21960,7 @@ ${inlineImageBlocks.join("\n\n")}` : "");
21863
21960
  } catch {
21864
21961
  }
21865
21962
  currentSubRef.current = null;
21963
+ onTurnOutcomeRef.current?.({ kind: "cancelled" });
21866
21964
  let partial = lastPartialRef.current.text;
21867
21965
  if (partial && !partial.trim().endsWith("\u2026") && !partial.trim().endsWith("...")) {
21868
21966
  partial = partial.trimEnd() + " \u2026";
@@ -22081,7 +22179,7 @@ var init_useNetworkStatus = __esm({
22081
22179
  if (connection) {
22082
22180
  const updateConnectionInfo = () => {
22083
22181
  const slowConnections = ["slow-2g", "2g"];
22084
- const isSlowConnection = slowConnections.includes(connection.effectiveType);
22182
+ const isSlowConnection = slowConnections.includes(connection.effectiveType ?? "");
22085
22183
  setNetworkStatus((prev) => ({
22086
22184
  ...prev,
22087
22185
  isSlowConnection,
@@ -22361,7 +22459,7 @@ var init_FeedbackModal = __esm({
22361
22459
  }
22362
22460
  const gatewayService = new GatewayService(
22363
22461
  gatewayUrl,
22364
- () => authenticationService2.getToken(),
22462
+ () => authenticationService.getToken(),
22365
22463
  feedbackEmail || settings?.feedbackEmail
22366
22464
  );
22367
22465
  const feedbackRequest = {
@@ -25650,7 +25748,7 @@ var init_conversation_drawer = __esm({
25650
25748
  title: "Sign out",
25651
25749
  onClick: (event) => {
25652
25750
  event.stopPropagation();
25653
- authenticationService2.clearToken();
25751
+ authenticationService.clearToken();
25654
25752
  window.location.href = "/login";
25655
25753
  },
25656
25754
  sx: {
@@ -26777,7 +26875,7 @@ var init_enhanced_mobile_conversations_modal = __esm({
26777
26875
  title: "Sign out",
26778
26876
  onClick: (event) => {
26779
26877
  event.stopPropagation();
26780
- authenticationService2.clearToken();
26878
+ authenticationService.clearToken();
26781
26879
  window.location.href = "/login";
26782
26880
  },
26783
26881
  sx: {
@@ -27597,7 +27695,8 @@ var init_query_suggestion_picker = __esm({
27597
27695
  }
27598
27696
  },
27599
27697
  onClick: () => {
27600
- onSend(prompt, []);
27698
+ void Promise.resolve(onSend(prompt, [])).catch(() => {
27699
+ });
27601
27700
  setHasSentPrompt(true);
27602
27701
  },
27603
27702
  children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
@@ -30117,7 +30216,7 @@ var init_UnderReview = __esm({
30117
30216
  const theme = (0, import_material31.useTheme)();
30118
30217
  const navigate = (0, import_react_router_dom4.useNavigate)();
30119
30218
  const handleResetSession = () => {
30120
- authenticationService2.clearToken();
30219
+ authenticationService.clearToken();
30121
30220
  navigate("/login");
30122
30221
  };
30123
30222
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
@@ -30822,8 +30921,8 @@ var init_chat2 = __esm({
30822
30921
  const notificationService2 = useNotificationService();
30823
30922
  const [selectedTheme, setSelectedTheme] = (0, import_react55.useState)(null);
30824
30923
  const [themeLoading, setThemeLoading] = (0, import_react55.useState)(true);
30825
- const token = authenticationService2.getToken();
30826
- const claims = token ? authenticationService2.parseJwtClaims(token) : null;
30924
+ const token = authenticationService.getToken();
30925
+ const claims = token ? authenticationService.parseJwtClaims(token) : null;
30827
30926
  const baseTheme = themeMap_default[selectedTheme ?? "bandit-dark"] || banditDarkTheme;
30828
30927
  const banditTheme = (0, import_react55.useMemo)(() => {
30829
30928
  return (0, import_styles33.createTheme)(baseTheme, {
@@ -30896,7 +30995,7 @@ var init_chat2 = __esm({
30896
30995
  } = useTTS();
30897
30996
  (0, import_react55.useEffect)(() => {
30898
30997
  const timer = setTimeout(() => {
30899
- const isAuthenticated = authenticationService2.isAuthenticated();
30998
+ const isAuthenticated = authenticationService.isAuthenticated();
30900
30999
  if (!initialized || availableVoices.length === 0) {
30901
31000
  if (token && isAuthenticated) {
30902
31001
  debugLogger.debug("Chat: Voices not initialized or unavailable, attempting to load from API");
@@ -30909,7 +31008,7 @@ var init_chat2 = __esm({
30909
31008
  return () => clearTimeout(timer);
30910
31009
  }, [initialized, availableVoices.length, loadVoicesFromAPI, token]);
30911
31010
  (0, import_react55.useEffect)(() => {
30912
- const isAuthenticated = authenticationService2.isAuthenticated();
31011
+ const isAuthenticated = authenticationService.isAuthenticated();
30913
31012
  if (packageSettings?.gatewayApiUrl && availableVoices.length === 0 && !initialized) {
30914
31013
  if (token && isAuthenticated) {
30915
31014
  debugLogger.debug("Chat: Gateway URL available and no voices loaded, attempting to load from API");
@@ -31524,7 +31623,16 @@ var init_chat2 = __esm({
31524
31623
  if (rafId) cancelAnimationFrame(rafId);
31525
31624
  };
31526
31625
  }, [_hasHydrated, chatContainerEl, getScrollState, history.length]);
31626
+ const sendAckRef = (0, import_react55.useRef)(null);
31627
+ const settleSendAck = (0, import_react55.useCallback)((outcome) => {
31628
+ const ack = sendAckRef.current;
31629
+ if (!ack) return;
31630
+ sendAckRef.current = null;
31631
+ if (outcome.kind === "failed" && !outcome.hadOutput) ack.reject(outcome.error);
31632
+ else ack.resolve();
31633
+ }, []);
31527
31634
  const aiProvider = useAIProvider({
31635
+ onTurnOutcome: settleSendAck,
31528
31636
  overrideComponentStatus: setComponentStatus,
31529
31637
  setIsSubmitting,
31530
31638
  setIsStreaming,
@@ -31575,6 +31683,26 @@ var init_chat2 = __esm({
31575
31683
  }, [ttsStop, isStreaming, handleStop]);
31576
31684
  const handleSend = (0, import_react55.useCallback)(
31577
31685
  (question, images, displayQuestion) => {
31686
+ sendAckRef.current?.resolve();
31687
+ const acknowledged = new Promise((resolve, reject) => {
31688
+ sendAckRef.current = { resolve, reject };
31689
+ });
31690
+ const runProvider = (systemPrompt2, providerImages2) => {
31691
+ aiProvider(systemPrompt2, question, providerImages2).catch((error) => {
31692
+ debugLogger.error("AI provider failed before streaming", {
31693
+ error: error instanceof Error ? error.message : String(error)
31694
+ });
31695
+ setIsStreaming(false);
31696
+ setResponseStarted(false);
31697
+ setIsSubmitting(false);
31698
+ setPendingMessage(null);
31699
+ const { conversations: convs, currentId: convId, replaceLastAnswer } = useConversationStore.getState();
31700
+ if (convs.find((c) => c.id === convId)?.history.at(-1)?.answer === "...") {
31701
+ replaceLastAnswer("_No response came back. Try sending again._", void 0, false, void 0, true);
31702
+ }
31703
+ settleSendAck({ kind: "failed", hadOutput: false, error });
31704
+ });
31705
+ };
31578
31706
  const requestStartTime = trackRequestStart();
31579
31707
  const questionForDisplay = displayQuestion || question;
31580
31708
  const pendingImages = images.length > 0 ? [...images] : void 0;
@@ -31613,10 +31741,15 @@ var init_chat2 = __esm({
31613
31741
  rawQuestion: question
31614
31742
  });
31615
31743
  const providerImages2 = pendingImages ? [...pendingImages] : [];
31616
- aiProvider(systemPrompt, question, providerImages2);
31744
+ runProvider(systemPrompt, providerImages2);
31617
31745
  }, 0);
31746
+ }).catch((error) => {
31747
+ debugLogger.error("Starting a new conversation failed", {
31748
+ error: error instanceof Error ? error.message : String(error)
31749
+ });
31750
+ settleSendAck({ kind: "failed", hadOutput: false, error });
31618
31751
  });
31619
- return;
31752
+ return acknowledged;
31620
31753
  }
31621
31754
  if (existing.name === "New Conversation") {
31622
31755
  generateName(question).then((newName) => {
@@ -31664,10 +31797,12 @@ var init_chat2 = __esm({
31664
31797
  }
31665
31798
  setResponse("");
31666
31799
  const providerImages = pendingImages ? [...pendingImages] : [];
31667
- aiProvider(systemPrompt, question, providerImages);
31800
+ runProvider(systemPrompt, providerImages);
31801
+ return acknowledged;
31668
31802
  },
31669
31803
  [
31670
31804
  aiProvider,
31805
+ settleSendAck,
31671
31806
  chatContainerRef,
31672
31807
  generateName,
31673
31808
  getScrollState,
@@ -31691,7 +31826,8 @@ var init_chat2 = __esm({
31691
31826
  }
31692
31827
  setInputValue("");
31693
31828
  setPastedImages([]);
31694
- handleSend(cleaned, [], cleaned);
31829
+ handleSend(cleaned, [], cleaned).catch(() => {
31830
+ });
31695
31831
  },
31696
31832
  [handleSend, setInputValue, setPastedImages]
31697
31833
  );
@@ -32269,12 +32405,12 @@ var init_chat2 = __esm({
32269
32405
  ossConfigured,
32270
32406
  isOSSMode: isOSSMode(),
32271
32407
  tier: featureFlag.getEvaluation()?.tier,
32272
- hasToken: authenticationService2.isAuthenticated(),
32408
+ hasToken: authenticationService.isAuthenticated(),
32273
32409
  playgroundBypassAuth,
32274
32410
  isPlaygroundRoute,
32275
32411
  bypassAuth
32276
32412
  });
32277
- if (!allowUnauthenticated && !bypassAuth && !authenticationService2.isAuthenticated()) {
32413
+ if (!allowUnauthenticated && !bypassAuth && !authenticationService.isAuthenticated()) {
32278
32414
  debugLogger.debug("User is not authenticated, redirecting to login");
32279
32415
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_react_router_dom5.Navigate, { to: "/login", replace: true });
32280
32416
  }
@@ -32310,7 +32446,7 @@ __export(index_exports, {
32310
32446
  VectorDatabaseService: () => VectorDatabaseService,
32311
32447
  VectorMigrationService: () => VectorMigrationService,
32312
32448
  VoiceService: () => VoiceService,
32313
- authenticationService: () => authenticationService2,
32449
+ authenticationService: () => authenticationService,
32314
32450
  debugLogger: () => debugLogger,
32315
32451
  defineCustomElement: () => defineCustomElement,
32316
32452
  featureFlagService: () => featureFlagService,
@@ -32457,7 +32593,7 @@ var AIProviderInitService = class _AIProviderInitService {
32457
32593
  type: "ollama" /* OLLAMA */,
32458
32594
  baseUrl: settings.ollamaUrl,
32459
32595
  tokenFactory: () => {
32460
- let token = authenticationService2.getToken();
32596
+ let token = authenticationService.getToken();
32461
32597
  if (!token) {
32462
32598
  token = localStorage.getItem("authToken");
32463
32599
  }
@@ -32498,7 +32634,7 @@ var AIProviderInitService = class _AIProviderInitService {
32498
32634
  type: "ollama" /* OLLAMA */,
32499
32635
  baseUrl: settings.ollamaUrl || "http://localhost:11434",
32500
32636
  tokenFactory: () => {
32501
- let token = authenticationService2.getToken();
32637
+ let token = authenticationService.getToken();
32502
32638
  if (!token) {
32503
32639
  token = localStorage.getItem("authToken");
32504
32640
  }
@@ -32529,7 +32665,7 @@ var AIProviderInitService = class _AIProviderInitService {
32529
32665
  type: "ollama" /* OLLAMA */,
32530
32666
  baseUrl: "http://localhost:11434",
32531
32667
  tokenFactory: () => {
32532
- let token = authenticationService2.getToken();
32668
+ let token = authenticationService.getToken();
32533
32669
  if (!token) {
32534
32670
  token = localStorage.getItem("authToken");
32535
32671
  }
@@ -32619,7 +32755,7 @@ var AIProviderInitService = class _AIProviderInitService {
32619
32755
  return config;
32620
32756
  }
32621
32757
  config.tokenFactory = () => {
32622
- let token = authenticationService2.getToken();
32758
+ let token = authenticationService.getToken();
32623
32759
  if (!token && typeof localStorage !== "undefined") {
32624
32760
  try {
32625
32761
  token = localStorage.getItem("authToken");
@@ -32676,11 +32812,11 @@ var ChatProvider = (props) => {
32676
32812
  } catch (error) {
32677
32813
  debugLogger.error("Failed to initialize AI provider:", { error });
32678
32814
  }
32679
- const token = authenticationService2.getToken();
32680
- if (token && !authenticationService2.isTokenExpired(token)) {
32681
- authenticationService2.setToken(token);
32815
+ const token = authenticationService.getToken();
32816
+ if (token && !authenticationService.isTokenExpired(token)) {
32817
+ authenticationService.setToken(token);
32682
32818
  } else {
32683
- authenticationService2.clearToken();
32819
+ authenticationService.clearToken();
32684
32820
  }
32685
32821
  useConversationStore.getState().hydrate();
32686
32822
  useProjectStore.getState().hydrate();
@@ -37611,7 +37747,7 @@ var useGatewayClient = () => {
37611
37747
  }
37612
37748
  return new GatewayService(
37613
37749
  gatewayUrl,
37614
- () => authenticationService2.getToken(),
37750
+ () => authenticationService.getToken(),
37615
37751
  feedbackEmail
37616
37752
  );
37617
37753
  }, [gatewayUrl, feedbackEmail]);
@@ -41490,7 +41626,7 @@ var buildHeaders2 = () => {
41490
41626
  const headers = {
41491
41627
  "Content-Type": "application/json"
41492
41628
  };
41493
- const token = authenticationService2.getToken();
41629
+ const token = authenticationService.getToken();
41494
41630
  if (token) {
41495
41631
  headers.Authorization = `Bearer ${token}`;
41496
41632
  }
@@ -44233,7 +44369,6 @@ var ProviderTab = () => {
44233
44369
  usePackageSettingsStore.setState({ settings: updatedSettings });
44234
44370
  }
44235
44371
  setProviderConfig(applyDefaultModel(normalizedConfig));
44236
- setIsProviderConfigOpen(false);
44237
44372
  showMessage("Provider configuration saved and switched successfully!", "success");
44238
44373
  debugLogger.info("Provider configuration saved and switched", { type: normalizedConfig.type });
44239
44374
  } catch (error) {
@@ -44621,7 +44756,7 @@ function buildUrl3(path) {
44621
44756
  }
44622
44757
  function authHeaders2() {
44623
44758
  const headers = { "Content-Type": "application/json" };
44624
- const token = authenticationService2.getToken();
44759
+ const token = authenticationService.getToken();
44625
44760
  if (token) headers["Authorization"] = `Bearer ${token}`;
44626
44761
  return headers;
44627
44762
  }
@@ -45966,7 +46101,7 @@ var Management = () => {
45966
46101
  let isActive = true;
45967
46102
  const loadCapabilities = async () => {
45968
46103
  try {
45969
- const token = authToken ?? authenticationService2.getToken();
46104
+ const token = authToken ?? authenticationService.getToken();
45970
46105
  const headers = {
45971
46106
  "Content-Type": "application/json"
45972
46107
  };
@@ -46102,7 +46237,7 @@ var Management = () => {
46102
46237
  "admin",
46103
46238
  "platform_admin"
46104
46239
  ]);
46105
- const userRoles = (authenticationService2.parseJwtClaims(authenticationService2.getToken() ?? "")?.roles ?? []).map((r) => r.toLowerCase());
46240
+ const userRoles = (authenticationService.parseJwtClaims(authenticationService.getToken() ?? "")?.roles ?? []).map((r) => r.toLowerCase());
46106
46241
  const isWorkspaceAdmin = userRoles.some(
46107
46242
  (r) => platformAdminRoles.has(r) || r.endsWith("-admin")
46108
46243
  );
@@ -46925,7 +47060,7 @@ var useVoices = () => {
46925
47060
  const gatewayApiUrl = usePackageSettingsStore((state) => state.settings?.gatewayApiUrl);
46926
47061
  const { token } = useAuthenticationStore();
46927
47062
  (0, import_react85.useEffect)(() => {
46928
- const isAuthenticated = authenticationService2.isAuthenticated();
47063
+ const isAuthenticated = authenticationService.isAuthenticated();
46929
47064
  if (gatewayApiUrl && token && isAuthenticated && !isServiceAvailable) {
46930
47065
  debugLogger.debug("Gateway API URL and JWT token available, loading voice models...");
46931
47066
  loadVoicesFromAPI();