@eshal-bot/chat-widget 0.1.71 → 0.1.72

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.
@@ -8010,16 +8010,21 @@
8010
8010
  * Sends a streaming chat request using fetch
8011
8011
  * @param {object} request - Chat request object
8012
8012
  * @param {string} apiBaseUrl - Base URL for the API
8013
+ * @param {Function|undefined} getAuthToken - async fn returning a Bearer token (in-app JWT path), or undefined (Mode B proxy)
8013
8014
  */
8014
- const sendStreamingRequest = async (request, apiBaseUrl, organizationId) => {
8015
+ const sendStreamingRequest = async (request, apiBaseUrl, getAuthToken) => {
8015
8016
  const headers = {
8016
8017
  'Content-Type': 'application/json',
8017
8018
  Accept: 'text/event-stream'
8018
8019
  };
8019
8020
 
8020
- // Add organization ID header if provided
8021
- if (organizationId) {
8022
- headers['x-eshal-org'] = organizationId;
8021
+ // In-app JWT path: call getAuthToken() and set Authorization header
8022
+ // Mode B proxy: no auth header from widget; proxy injects x-eshal-sig + x-eshal-org server-side
8023
+ if (typeof getAuthToken === 'function') {
8024
+ const token = await getAuthToken();
8025
+ if (token) {
8026
+ headers['Authorization'] = "Bearer ".concat(token);
8027
+ }
8023
8028
  }
8024
8029
  const response = await fetch("".concat(apiBaseUrl.replace(/\/$/, ''), "/api/v1/support"), {
8025
8030
  method: 'POST',
@@ -8343,20 +8348,33 @@
8343
8348
  // Handle text_chunk events
8344
8349
  if (parsed.type === 'text_chunk') {
8345
8350
  var _parsed$choices;
8351
+ // ALWAYS create the skeleton on the first text_chunk, even when it
8352
+ // carries no content (a role-only "skeleton" chunk — delta.role
8353
+ // without delta.content — is the backend's "an assistant turn is
8354
+ // starting" signal used by the input/source-clarification short-
8355
+ // circuits in chat_utils.py). Without this, a clarification turn
8356
+ // that emits [text_chunk role-only] -> [complete content+
8357
+ // finalContent] never created the assistant bubble (this branch
8358
+ // skipped on empty content; the COMPLETE branch's updateFinalMessage
8359
+ // requires lastMessage.role === 'assistant' to render — see
8360
+ // updateFinalMessage line ~189) → the follow-up question was
8361
+ // silently dropped, even though the wire carried everything
8362
+ // correctly. Mirrors handleStreamEvent (line ~257) and packages/
8363
+ // frontend ChatService.ts (line ~213) which both create the
8364
+ // skeleton unconditionally on the first text_chunk.
8365
+ if (!hasReceivedFirstChunk.current) {
8366
+ hasReceivedFirstChunk.current = true;
8367
+ setMessages(prev => {
8368
+ const lastMessage = prev[prev.length - 1];
8369
+ if (!lastMessage || lastMessage.role !== 'assistant' || !lastMessage.isProcessing) {
8370
+ const skeletonMessage = createSkeletonMessage();
8371
+ return [...(prev || []), skeletonMessage];
8372
+ }
8373
+ return prev;
8374
+ });
8375
+ }
8346
8376
  const content = ((_parsed$choices = parsed.choices) === null || _parsed$choices === void 0 || (_parsed$choices = _parsed$choices[0]) === null || _parsed$choices === void 0 || (_parsed$choices = _parsed$choices.delta) === null || _parsed$choices === void 0 ? void 0 : _parsed$choices.content) || parsed.content;
8347
8377
  if (content) {
8348
- // Create skeleton on first chunk if not already created
8349
- if (!hasReceivedFirstChunk.current) {
8350
- hasReceivedFirstChunk.current = true;
8351
- setMessages(prev => {
8352
- const lastMessage = prev[prev.length - 1];
8353
- if (!lastMessage || lastMessage.role !== 'assistant' || !lastMessage.isProcessing) {
8354
- const skeletonMessage = createSkeletonMessage();
8355
- return [...(prev || []), skeletonMessage];
8356
- }
8357
- return prev;
8358
- });
8359
- }
8360
8378
  accumulatedText.current += content;
8361
8379
  updateMessage(accumulatedText.current);
8362
8380
  }
@@ -8759,31 +8777,27 @@
8759
8777
  };
8760
8778
 
8761
8779
  /**
8762
- * Constructs the voice WebSocket base URL — ALWAYS derived from the API
8763
- * base URL the widget is embedded with, as `wss://{host}/voice/ws`.
8780
+ * Constructs the voice WebSocket base URL.
8764
8781
  *
8765
- * `/voice/ws/{org}/{session}` is terminated by the frontend custom server
8766
- * (packages/frontend/server.js), which proxies it in-cluster to
8767
- * knowledge-api on `/ws`. The widget always talks to the same origin it
8768
- * loaded its config from, so the WS endpoint is fully determined by
8769
- * `apiBaseUrl`. There is intentionally NO override parameter: voice can
8770
- * never be pointed at the (no-longer-public) knowledge-api LoadBalancer by
8771
- * a stale embed option or backend config value.
8782
+ * If wsBaseUrl is provided explicitly (via ChatWidget.init({ wsBaseUrl })),
8783
+ * use it directly. Required for Mode B external embeds where apiBaseUrl is
8784
+ * the customer server, not the eshal frontend.
8785
+ *
8786
+ * For in-app use (apiBaseUrl IS the eshal frontend), derives from apiBaseUrl.
8772
8787
  *
8773
8788
  * @param {string} apiBaseUrl - Frontend base URL, e.g. https://demo.eshal.ai
8774
- * @returns {string} Voice WS base, e.g. wss://demo.eshal.ai/voice/ws
8789
+ * @param {string} [wsBaseUrl] - Explicit override, e.g. wss://dev.eshal.ai/voice/ws
8790
+ * @returns {string} Voice WS base URL
8775
8791
  */
8776
- const getWebSocketUrl = apiBaseUrl => {
8792
+ const getWebSocketUrl = (apiBaseUrl, wsBaseUrl) => {
8793
+ if (wsBaseUrl) return wsBaseUrl.replace(/\/$/, '');
8777
8794
  if (!apiBaseUrl) {
8778
- // No base URL yet (config still loading) — safe public default.
8779
8795
  return 'wss://dev.eshal.ai/voice/ws';
8780
8796
  }
8781
8797
  try {
8782
8798
  const url = new URL(apiBaseUrl);
8783
- // http→ws, https→wss; point at the frontend voice-proxy path.
8784
8799
  url.protocol = url.protocol.replace(/^http/, 'ws');
8785
8800
  url.pathname = '/voice/ws';
8786
- // Clean URL without a trailing slash.
8787
8801
  return url.toString().replace(/\/$/, '');
8788
8802
  } catch (error) {
8789
8803
  console.error("Invalid API base URL provided for WebSocket:", error);
@@ -9127,8 +9141,10 @@
9127
9141
  welcomeMessage,
9128
9142
  quickQuestions = [],
9129
9143
  apiBaseUrl,
9144
+ wsBaseUrl,
9130
9145
  apiKey,
9131
9146
  organizationId,
9147
+ getAuthToken,
9132
9148
  autoOpen,
9133
9149
  openDelay,
9134
9150
  darkMode,
@@ -9296,11 +9312,9 @@
9296
9312
  const visitorProfileRef = reactExports.useRef(null);
9297
9313
  const websocketUrl = reactExports.useMemo(() => {
9298
9314
  if (!organizationId) return null;
9299
- // Voice WS base is always derived from apiBaseUrl (the frontend origin
9300
- // the widget is embedded with) → wss://{host}/voice/ws. No override.
9301
- const resolvedWsBaseUrl = getWebSocketUrl(apiBaseUrl);
9315
+ const resolvedWsBaseUrl = getWebSocketUrl(apiBaseUrl, wsBaseUrl);
9302
9316
  return "".concat(resolvedWsBaseUrl, "/").concat(organizationId, "/").concat(bidiSessionId);
9303
- }, [apiBaseUrl, organizationId, bidiSessionId]);
9317
+ }, [apiBaseUrl, wsBaseUrl, organizationId, bidiSessionId]);
9304
9318
  const getNextMessageId = reactExports.useCallback(() => {
9305
9319
  const id = "msg-".concat(messageIdRef.current);
9306
9320
  messageIdRef.current += 1;
@@ -9765,7 +9779,7 @@
9765
9779
  if (apiType === 'userName' || apiType === 'email' || apiType === 'phone') {
9766
9780
  // For onboarding fields, just consume the stream without processing.
9767
9781
  // Single attempt — no retry, no user-visible partial state to clean up.
9768
- const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId);
9782
+ const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, getAuthToken);
9769
9783
  const reader = stream.getReader();
9770
9784
  const decoder = new TextDecoder();
9771
9785
  while (true) {
@@ -9784,7 +9798,7 @@
9784
9798
  let lastErr;
9785
9799
  for (let attempt = 0; attempt <= STREAM_RETRY_DELAYS_MS.length; attempt += 1) {
9786
9800
  try {
9787
- const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId);
9801
+ const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, getAuthToken);
9788
9802
  await processStream(stream, setMessages);
9789
9803
  updateActivity(organizationId);
9790
9804
  return {
@@ -9812,7 +9826,7 @@
9812
9826
 
9813
9827
  // Non-chat, non-onboarding apiType (shouldn't currently happen):
9814
9828
  // fire and forget, single attempt.
9815
- await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId);
9829
+ await sendStreamingRequest(chatRequest, apiBaseUrl, getAuthToken);
9816
9830
  updateActivity(organizationId);
9817
9831
  return {
9818
9832
  success: true
@@ -9848,7 +9862,7 @@
9848
9862
  // UX-M09: release the concurrency guard so the next send can proceed.
9849
9863
  isSendingRef.current = false;
9850
9864
  }
9851
- }, [apiBaseUrl, apiKey, getNextMessageId, organizationId, messages, bidiSessionId, routingChoice, widgetFilter, provideCitations]);
9865
+ }, [apiBaseUrl, apiKey, getNextMessageId, getAuthToken, messages, bidiSessionId, routingChoice, widgetFilter, provideCitations]);
9852
9866
  const handleOnboardingFormSubmit = reactExports.useCallback(async answers => {
9853
9867
  const questions = getOnboardingQuestions(onboardingQuestions);
9854
9868
  if (questions.length === 0) return;
@@ -91749,6 +91763,31 @@
91749
91763
  return String(n);
91750
91764
  }
91751
91765
 
91766
+ // ── Currency-aware value formatting ──────────────────────────────────────
91767
+ // A2UI chart data is normalized to `{ label, value }` by A2uiBlock's
91768
+ // numericData(), which drops the original Y-column key. A2uiBlock now threads
91769
+ // that key through as `props.yKey` so these renderers can decide whether the
91770
+ // axis / tooltip values are money. Heuristic mirrors the frontend's
91771
+ // chartShared.tsx `isCurrencyKey` — keep the keyword list in lockstep.
91772
+ // `(^|[_\W])` (not `\b`) so "average_salary" / "total_expenses" still match —
91773
+ // JS `\b` doesn't fire next to `_`.
91774
+ const CURRENCY_KEY_RE$1 = /(^|[_\W])(price|salary|cost|amount|revenue|expense|total|value|budget|spend|income|sale|balance)/i;
91775
+ function isCurrencyKey$1(key) {
91776
+ return typeof key === 'string' && CURRENCY_KEY_RE$1.test(key);
91777
+ }
91778
+
91779
+ // Compact currency for axis ticks → "$120.0k". Full currency for tooltips →
91780
+ // "$87,600" (comma-grouped, up to 2 decimals, no forced trailing zeros so a
91781
+ // whole-dollar salary reads "$81,200" not "$81,200.00").
91782
+ function formatCurrencyCompact(v) {
91783
+ return "$".concat(formatCompactNumber(v));
91784
+ }
91785
+ function formatCurrencyFull(v) {
91786
+ return "$".concat(Number(v).toLocaleString('en-US', {
91787
+ maximumFractionDigits: 2
91788
+ }));
91789
+ }
91790
+
91752
91791
  /**
91753
91792
  * Recharts bar chart — mirrors agent-sdk's eshalRenderers.BarChart.
91754
91793
  *
@@ -91761,6 +91800,11 @@
91761
91800
  isDark = false
91762
91801
  } = _ref;
91763
91802
  const data = Array.isArray(props.data) ? props.data : [];
91803
+ // Whether the Y values are money — drives the `$` on axis ticks + tooltip.
91804
+ const currency = isCurrencyKey$1(props.yKey);
91805
+ // Human series name for the tooltip ("Average Salary"), so it no longer
91806
+ // reads the generic dataKey "value".
91807
+ const seriesName = props.valueLabel || undefined;
91764
91808
  const borderColor = isDark ? '#374151' : '#e2e8f0';
91765
91809
  const bgColor = isDark ? '#1f2937' : '#ffffff';
91766
91810
  const labelColor = isDark ? '#9ca3af' : '#64748b';
@@ -91835,6 +91879,9 @@
91835
91879
  fontSize: 11,
91836
91880
  fill: labelColor
91837
91881
  },
91882
+ angle: 0,
91883
+ textAnchor: "middle",
91884
+ interval: 0,
91838
91885
  label: props.xAxisLabel ? {
91839
91886
  value: props.xAxisLabel,
91840
91887
  position: 'insideBottom',
@@ -91855,9 +91902,9 @@
91855
91902
  fontSize: 11,
91856
91903
  fill: labelColor
91857
91904
  } : undefined,
91858
- tickFormatter: v => props.unit ? "".concat(formatCompactNumber(v), " ").concat(props.unit) : formatCompactNumber(v)
91905
+ tickFormatter: v => currency ? formatCurrencyCompact(v) : props.unit ? "".concat(formatCompactNumber(v), " ").concat(props.unit) : formatCompactNumber(v)
91859
91906
  }), /*#__PURE__*/jsxRuntimeExports.jsx(Tooltip, {
91860
- formatter: v => props.unit ? "".concat(Number(v).toLocaleString(), " ").concat(props.unit) : Number(v).toLocaleString(),
91907
+ formatter: v => currency ? formatCurrencyFull(v) : props.unit ? "".concat(Number(v).toLocaleString(), " ").concat(props.unit) : Number(v).toLocaleString(),
91861
91908
  contentStyle: {
91862
91909
  background: tooltipBg,
91863
91910
  border: "1px solid ".concat(borderColor),
@@ -91867,6 +91914,7 @@
91867
91914
  }
91868
91915
  }), /*#__PURE__*/jsxRuntimeExports.jsx(Bar, {
91869
91916
  dataKey: "value",
91917
+ name: seriesName,
91870
91918
  fill: "#3b82f6",
91871
91919
  radius: [2, 2, 0, 0]
91872
91920
  })]
@@ -91950,6 +91998,7 @@
91950
91998
  donut = false
91951
91999
  } = _ref3;
91952
92000
  const data = Array.isArray(props.data) ? props.data : [];
92001
+ const currency = isCurrencyKey$1(props.yKey);
91953
92002
  const borderColor = isDark ? '#374151' : '#e2e8f0';
91954
92003
  const bgColor = isDark ? '#1f2937' : '#ffffff';
91955
92004
  const titleColor = isDark ? '#f3f4f6' : '#0f172a';
@@ -92000,7 +92049,7 @@
92000
92049
  fill: PIE_COLORS[i % PIE_COLORS.length]
92001
92050
  }, i))
92002
92051
  }), /*#__PURE__*/jsxRuntimeExports.jsx(Tooltip, {
92003
- formatter: v => Number(v).toLocaleString(),
92052
+ formatter: v => currency ? formatCurrencyFull(v) : Number(v).toLocaleString(),
92004
92053
  contentStyle: {
92005
92054
  background: tooltipBg,
92006
92055
  border: "1px solid ".concat(borderColor),
@@ -92034,6 +92083,8 @@
92034
92083
  area = false
92035
92084
  } = _ref4;
92036
92085
  const data = Array.isArray(props.data) ? props.data : [];
92086
+ const currency = isCurrencyKey$1(props.yKey);
92087
+ const seriesName = props.valueLabel || undefined;
92037
92088
  const borderColor = isDark ? '#374151' : '#e2e8f0';
92038
92089
  const bgColor = isDark ? '#1f2937' : '#ffffff';
92039
92090
  const labelColor = isDark ? '#9ca3af' : '#64748b';
@@ -92109,6 +92160,9 @@
92109
92160
  fontSize: 11,
92110
92161
  fill: labelColor
92111
92162
  },
92163
+ angle: 0,
92164
+ textAnchor: "middle",
92165
+ interval: 0,
92112
92166
  label: props.xAxisLabel ? {
92113
92167
  value: props.xAxisLabel,
92114
92168
  position: 'insideBottom',
@@ -92122,9 +92176,9 @@
92122
92176
  fontSize: 11,
92123
92177
  fill: labelColor
92124
92178
  },
92125
- tickFormatter: v => props.unit ? "".concat(formatCompactNumber(v), " ").concat(props.unit) : formatCompactNumber(v)
92179
+ tickFormatter: v => currency ? formatCurrencyCompact(v) : props.unit ? "".concat(formatCompactNumber(v), " ").concat(props.unit) : formatCompactNumber(v)
92126
92180
  }), /*#__PURE__*/jsxRuntimeExports.jsx(Tooltip, {
92127
- formatter: v => props.unit ? "".concat(Number(v).toLocaleString(), " ").concat(props.unit) : Number(v).toLocaleString(),
92181
+ formatter: v => currency ? formatCurrencyFull(v) : props.unit ? "".concat(Number(v).toLocaleString(), " ").concat(props.unit) : Number(v).toLocaleString(),
92128
92182
  contentStyle: {
92129
92183
  background: tooltipBg,
92130
92184
  border: "1px solid ".concat(borderColor),
@@ -92135,6 +92189,7 @@
92135
92189
  }), area ? /*#__PURE__*/jsxRuntimeExports.jsx(Area, {
92136
92190
  type: "monotone",
92137
92191
  dataKey: "value",
92192
+ name: seriesName,
92138
92193
  stroke: "#3b82f6",
92139
92194
  fill: "#3b82f6",
92140
92195
  fillOpacity: 0.15,
@@ -92146,6 +92201,7 @@
92146
92201
  }) : /*#__PURE__*/jsxRuntimeExports.jsx(Line, {
92147
92202
  type: "monotone",
92148
92203
  dataKey: "value",
92204
+ name: seriesName,
92149
92205
  stroke: "#3b82f6",
92150
92206
  strokeWidth: 2,
92151
92207
  dot: {
@@ -92343,7 +92399,14 @@
92343
92399
  data: rows,
92344
92400
  unit: spec.unit,
92345
92401
  xAxisLabel: spec.xAxisLabel,
92346
- yAxisLabel: spec.yAxisLabel
92402
+ yAxisLabel: spec.yAxisLabel,
92403
+ // numericData() collapses the rows to { label, value }, dropping the
92404
+ // original Y-column key. Pass it through so the renderer can detect a
92405
+ // currency column (→ "$" on the Y-axis + tooltip) and label the tooltip
92406
+ // series with the human column name ("Average Salary") instead of the
92407
+ // generic dataKey "value".
92408
+ yKey: y,
92409
+ valueLabel: prettifyLabel(y)
92347
92410
  };
92348
92411
  if (t === 'pie') return /*#__PURE__*/jsxRuntimeExports.jsx(PieChartRenderer, {
92349
92412
  props: chartProps,
@@ -92670,10 +92733,12 @@
92670
92733
 
92671
92734
  // ISO date / datetime prefix — matches the typical SQL output the
92672
92735
  // agent's tools produce when GROUP BY-ing by month
92673
- // (`DATE_TRUNC('month', col)` → `YYYY-MM-DD`). Used by formatAxisTick
92674
- // below so a chart of monthly expenses shows "Jan '26" / "Feb '26"
92675
- // horizontally instead of "2026 01 01" slanted (EV-1187 follow-up).
92676
- const ISO_DATE_RE = /^(\d{4})-(\d{2})-(\d{2})(?:T|$)/;
92736
+ // (`DATE_TRUNC('month', col)` → `YYYY-MM-DD`, or `to_char(..,'YYYY-MM')`
92737
+ // → `YYYY-MM`). Used by formatAxisTick below so a chart of monthly
92738
+ // expenses shows "Jan '26" / "Feb '26" horizontally instead of
92739
+ // "2026 01 01" or "2026 01" slanted (EV-1187 follow-up + 2026-06-24
92740
+ // day-less variant fix).
92741
+ const ISO_DATE_RE = /^(\d{4})-(\d{2})(?:-\d{2})?(?:[T\s]|$)/;
92677
92742
  function formatIsoDateAsMonthTick(value) {
92678
92743
  if (typeof value !== 'string') return null;
92679
92744
  const m = value.match(ISO_DATE_RE);
@@ -92716,6 +92781,14 @@
92716
92781
  if (!SNAKE_CASE_CATEGORY_RE.test(value)) return value;
92717
92782
  return prettifyLabel(value);
92718
92783
  }
92784
+
92785
+ // Numeric string under a money column, e.g. "81,200" / "105444" / "-1,250.50".
92786
+ // Markdown tables (the common concierge path) arrive as text cells, so a
92787
+ // salary value is the STRING "81,200" — not a number — and skips the
92788
+ // USD_FORMATTER branch below. We prefix "$" while keeping the source's own
92789
+ // grouping/decimals so it reads "$81,200" (not "$81,200.00"). A leading "$"
92790
+ // already present makes the regex miss, so we never double-prefix.
92791
+ const _NUMERIC_STRING_RE = /^-?\d[\d,]*(\.\d+)?$/;
92719
92792
  function formatCell(v, columnKey) {
92720
92793
  if (v == null) return '';
92721
92794
  if (typeof v === 'number') {
@@ -92725,6 +92798,11 @@
92725
92798
  return Number.isInteger(v) ? String(v) : v.toFixed(2);
92726
92799
  }
92727
92800
  if (typeof v === 'object') return JSON.stringify(v);
92801
+ // Numeric string under a money-named column → prefix "$" (Bug: concierge
92802
+ // salary table showed "81,200" with no currency symbol).
92803
+ if (isCurrencyKey(columnKey) && _NUMERIC_STRING_RE.test(String(v).trim())) {
92804
+ return "$".concat(String(v).trim());
92805
+ }
92728
92806
  // String cells: prettify snake_case category labels (e.g.
92729
92807
  // `office_supplies` → "Office Supplies") so the table reads
92730
92808
  // consistently with the chart's X-axis ticks on the same data.
@@ -94302,7 +94380,7 @@
94302
94380
  let {
94303
94381
  sources = [],
94304
94382
  apiBaseUrl,
94305
- organizationId,
94383
+ getAuthToken,
94306
94384
  isDark,
94307
94385
  textColor,
94308
94386
  fontFamily,
@@ -94328,7 +94406,7 @@
94328
94406
  // corpora that have none of the above.
94329
94407
  const _downloadKey = s => s.url || s.source_id || s.source_name || "";
94330
94408
  const handleFileDownload = async source => {
94331
- if (!apiBaseUrl || !organizationId) return;
94409
+ if (!apiBaseUrl) return;
94332
94410
  // SharePoint proxy URLs are GET endpoints that stream the file
94333
94411
  // directly via Microsoft Graph using the stored OAuth credential.
94334
94412
  // Bypass the legacy POST /file-download lookup (which expects an
@@ -94354,11 +94432,14 @@
94354
94432
  // set, or future deployments that set it) pass through
94355
94433
  // unchanged.
94356
94434
  const spUrl = source.url.startsWith("http") ? source.url : "".concat(apiBaseUrl.replace(/\/$/, "")).concat(source.url);
94435
+ const spHeaders = {};
94436
+ if (typeof getAuthToken === 'function') {
94437
+ const token = await getAuthToken();
94438
+ if (token) spHeaders['Authorization'] = "Bearer ".concat(token);
94439
+ }
94357
94440
  res = await fetch(spUrl, {
94358
94441
  method: "GET",
94359
- headers: {
94360
- "x-eshal-org": organizationId
94361
- },
94442
+ headers: spHeaders,
94362
94443
  credentials: "include"
94363
94444
  });
94364
94445
  } else {
@@ -94374,15 +94455,18 @@
94374
94455
  // - source.source_name (the user's original filename)
94375
94456
  // Sending both lets the server skip the Milvus resolution
94376
94457
  // AND advertise the readable name in Content-Disposition.
94458
+ const dlHeaders = {
94459
+ "Content-Type": "application/json"
94460
+ };
94461
+ if (typeof getAuthToken === 'function') {
94462
+ const token = await getAuthToken();
94463
+ if (token) dlHeaders['Authorization'] = "Bearer ".concat(token);
94464
+ }
94377
94465
  res = await fetch(url, {
94378
94466
  method: "POST",
94379
- headers: {
94380
- "Content-Type": "application/json",
94381
- "x-eshal-org": organizationId
94382
- },
94467
+ headers: dlHeaders,
94383
94468
  credentials: "include",
94384
94469
  body: JSON.stringify({
94385
- org_id: organizationId,
94386
94470
  source_id: source.source_id,
94387
94471
  file_name: source.url || "",
94388
94472
  source_name: source.source_name || ""
@@ -94567,7 +94651,7 @@
94567
94651
  conciergeName,
94568
94652
  companyName,
94569
94653
  apiBaseUrl,
94570
- organizationId,
94654
+ getAuthToken,
94571
94655
  onPromptSuggestionClick,
94572
94656
  allowFeedback = true,
94573
94657
  provideCitations = true,
@@ -94739,7 +94823,7 @@
94739
94823
  }), /*#__PURE__*/jsxRuntimeExports.jsx(MessageSources, {
94740
94824
  sources: message.sources,
94741
94825
  apiBaseUrl: apiBaseUrl,
94742
- organizationId: organizationId,
94826
+ getAuthToken: getAuthToken,
94743
94827
  isDark: isDark,
94744
94828
  textColor: messageTextColor,
94745
94829
  fontFamily: messageFontFamily,
@@ -94765,7 +94849,7 @@
94765
94849
  }), /*#__PURE__*/jsxRuntimeExports.jsx(MessageSources, {
94766
94850
  sources: message.sources,
94767
94851
  apiBaseUrl: apiBaseUrl,
94768
- organizationId: organizationId,
94852
+ getAuthToken: getAuthToken,
94769
94853
  isDark: isDark,
94770
94854
  textColor: messageTextColor,
94771
94855
  fontFamily: messageFontFamily,
@@ -94945,7 +95029,7 @@
94945
95029
  conciergeName,
94946
95030
  companyName,
94947
95031
  apiBaseUrl,
94948
- organizationId,
95032
+ getAuthToken,
94949
95033
  onPromptSuggestionClick,
94950
95034
  allowFeedback,
94951
95035
  provideCitations,
@@ -94989,7 +95073,7 @@
94989
95073
  conciergeName: conciergeName,
94990
95074
  companyName: companyName,
94991
95075
  apiBaseUrl: apiBaseUrl,
94992
- organizationId: organizationId,
95076
+ getAuthToken: getAuthToken,
94993
95077
  onPromptSuggestionClick: onPromptSuggestionClick,
94994
95078
  allowFeedback: allowFeedback,
94995
95079
  provideCitations: provideCitations,
@@ -95834,7 +95918,7 @@
95834
95918
  conciergeName,
95835
95919
  companyName,
95836
95920
  apiBaseUrl,
95837
- organizationId,
95921
+ getAuthToken,
95838
95922
  onPromptSuggestionClick,
95839
95923
  conversationResetKey,
95840
95924
  allowFeedback,
@@ -96016,7 +96100,7 @@
96016
96100
  conciergeName: conciergeName,
96017
96101
  companyName: companyName,
96018
96102
  apiBaseUrl: apiBaseUrl,
96019
- organizationId: organizationId,
96103
+ getAuthToken: getAuthToken,
96020
96104
  allowFeedback: allowFeedback,
96021
96105
  provideCitations: provideCitations,
96022
96106
  onFeedback: onFeedback,
@@ -96048,7 +96132,7 @@
96048
96132
  conciergeName: conciergeName,
96049
96133
  companyName: companyName,
96050
96134
  apiBaseUrl: apiBaseUrl,
96051
- organizationId: organizationId,
96135
+ getAuthToken: getAuthToken,
96052
96136
  headerTextColor: headerTextColor
96053
96137
  }, "skeleton-waiting");
96054
96138
  }
@@ -96163,7 +96247,18 @@
96163
96247
  copyChat: "Copy Chat",
96164
96248
  startChat: "Start Chat",
96165
96249
  startingVoiceSession: "Starting voice session...",
96166
- allowMicAccess: "Allow microphone access"
96250
+ allowMicAccess: "Allow microphone access",
96251
+ // Onboarding-form validation messages. These are the English fallback for
96252
+ // every locale (translate() falls back to EN[key]); without them the form
96253
+ // rendered the raw key strings ("emailInvalid", etc.). `fieldRequiredSuffix`
96254
+ // is appended after a field label: `${label} ${fieldRequiredSuffix}`.
96255
+ emailRequired: "Please enter your email address",
96256
+ nameRequired: "Please enter your name",
96257
+ phoneRequired: "Please enter your phone number",
96258
+ fieldRequiredSuffix: "is required",
96259
+ emailInvalid: "Please enter a valid email address",
96260
+ nameInvalid: "Please enter a valid name",
96261
+ phoneInvalid: "Please enter a valid phone number"
96167
96262
  };
96168
96263
  const DICTIONARIES = {
96169
96264
  en: EN,
@@ -99552,7 +99647,7 @@
99552
99647
  showPoweredBy = true,
99553
99648
  apiBaseUrl,
99554
99649
  apiKey,
99555
- organizationId,
99650
+ getAuthToken,
99556
99651
  conversationId,
99557
99652
  conciergeId,
99558
99653
  onboardingQuestions = [],
@@ -99700,7 +99795,7 @@
99700
99795
  setCsatSubmitted(submitted);
99701
99796
  if (submitted) setShowCsat(false);
99702
99797
  } catch (_unused4) {}
99703
- }, [conversationId, organizationId]);
99798
+ }, [conversationId]);
99704
99799
 
99705
99800
  // Auto-scroll hook
99706
99801
  useAutoScroll(messagesContainerRef, [messages, isLoading], {
@@ -99838,14 +99933,15 @@
99838
99933
  const handleCsatRating = async (rating, format) => {
99839
99934
  setCsatSubmitted(true); // optimistic UI
99840
99935
  setShowCsat(false);
99841
- markCsatSubmitted(organizationId); // persist so page refresh doesn't re-show CSAT
99936
+ markCsatSubmitted(undefined); // persist so page refresh doesn't re-show CSAT
99842
99937
 
99843
99938
  try {
99844
99939
  const headers = {
99845
99940
  "Content-Type": "application/json"
99846
99941
  };
99847
- if (organizationId) {
99848
- headers["x-eshal-org"] = organizationId;
99942
+ if (typeof getAuthToken === 'function') {
99943
+ const token = await getAuthToken();
99944
+ if (token) headers['Authorization'] = "Bearer ".concat(token);
99849
99945
  }
99850
99946
  const response = await fetch("".concat(apiBaseUrl.replace(/\/$/, ""), "/api/v1/support/csat"), {
99851
99947
  method: "POST",
@@ -99855,7 +99951,6 @@
99855
99951
  conversationId,
99856
99952
  rating,
99857
99953
  ratingFormat: format,
99858
- organizationId,
99859
99954
  conciergeId
99860
99955
  })
99861
99956
  });
@@ -99879,8 +99974,9 @@
99879
99974
  const headers = {
99880
99975
  "Content-Type": "application/json"
99881
99976
  };
99882
- if (organizationId) {
99883
- headers["x-eshal-org"] = organizationId;
99977
+ if (typeof getAuthToken === 'function') {
99978
+ const token = await getAuthToken();
99979
+ if (token) headers['Authorization'] = "Bearer ".concat(token);
99884
99980
  }
99885
99981
  const ratingFormat = normalizeCsatFormat(csatFormat);
99886
99982
  const response = await fetch("".concat(apiBaseUrl.replace(/\/$/, ""), "/api/v1/support/csat"), {
@@ -99891,7 +99987,6 @@
99891
99987
  conversationId,
99892
99988
  rating: String(resolveRating),
99893
99989
  ratingFormat,
99894
- organizationId,
99895
99990
  conciergeId,
99896
99991
  feedback: resolveFeedbackText || undefined
99897
99992
  })
@@ -99925,7 +100020,7 @@
99925
100020
  } finally {
99926
100021
  setIsSubmittingResolve(false);
99927
100022
  }
99928
- }, [isCsatEnabled, isSubmittingResolve, isConversationResolved, resolveRating, resolveFeedbackText, conversationId, organizationId, conciergeId, apiBaseUrl, csatFormat, onResetConversation, onRefreshChat, onWidgetFilterChange]);
100023
+ }, [isCsatEnabled, isSubmittingResolve, isConversationResolved, resolveRating, resolveFeedbackText, conversationId, getAuthToken, conciergeId, apiBaseUrl, csatFormat, onResetConversation, onRefreshChat, onWidgetFilterChange]);
99929
100024
 
99930
100025
  // Handle X button: first click shows form, second click closes
99931
100026
  const handleCloseClick = reactExports.useCallback(() => {
@@ -100263,7 +100358,7 @@
100263
100358
  conciergeName: conciergeName,
100264
100359
  companyName: companyName,
100265
100360
  apiBaseUrl: apiBaseUrl,
100266
- organizationId: organizationId,
100361
+ getAuthToken: getAuthToken,
100267
100362
  conversationResetKey: conversationId,
100268
100363
  allowFeedback: allowFeedback,
100269
100364
  provideCitations: provideCitations,
@@ -100348,7 +100443,7 @@
100348
100443
 
100349
100444
  /**
100350
100445
  * @param {string|undefined|null} apiBaseUrl
100351
- * @param {string|undefined|null} orgId
100446
+ * @param {Function|undefined|null} getAuthToken - async fn returning a Bearer token (in-app JWT path), or undefined/null (Mode B proxy)
100352
100447
  * @returns {{
100353
100448
  * categories: Array<{id:string,label:string,count:number,tags:string[]}>,
100354
100449
  * loading: boolean,
@@ -100356,14 +100451,15 @@
100356
100451
  * hasFilters: boolean,
100357
100452
  * }}
100358
100453
  */
100359
- function useFilterCatalog(apiBaseUrl, orgId) {
100454
+ function useFilterCatalog(apiBaseUrl, getAuthToken) {
100455
+ const hasAuth = !!apiBaseUrl;
100360
100456
  const [state, setState] = reactExports.useState({
100361
100457
  categories: [],
100362
- loading: !!(apiBaseUrl && orgId),
100458
+ loading: hasAuth,
100363
100459
  error: null
100364
100460
  });
100365
100461
  reactExports.useEffect(() => {
100366
- if (!apiBaseUrl || !orgId) {
100462
+ if (!apiBaseUrl) {
100367
100463
  setState({
100368
100464
  categories: [],
100369
100465
  loading: false,
@@ -100371,8 +100467,8 @@
100371
100467
  });
100372
100468
  return;
100373
100469
  }
100374
- const key = "".concat(apiBaseUrl, "|").concat(orgId);
100375
- const cached = _PROCESS_CACHE.get(key);
100470
+ const cacheKey = getAuthToken ? "".concat(apiBaseUrl, "|__session__") : "".concat(apiBaseUrl, "|__proxy__");
100471
+ const cached = _PROCESS_CACHE.get(cacheKey);
100376
100472
  if (cached) {
100377
100473
  setState({
100378
100474
  categories: cached,
@@ -100386,43 +100482,55 @@
100386
100482
  loading: true,
100387
100483
  error: null
100388
100484
  }));
100389
- const url = "".concat(apiBaseUrl.replace(/\/$/, ""), "/api/v1/widget/filters/").concat(encodeURIComponent(orgId));
100390
- fetch(url, {
100391
- method: "GET",
100392
- headers: {
100393
- Accept: "application/json"
100394
- }
100395
- }).then(async res => {
100396
- if (!res.ok) {
100397
- throw new Error("filter catalog HTTP ".concat(res.status));
100485
+ const base = apiBaseUrl.replace(/\/$/, "");
100486
+ const url = "".concat(base, "/api/v1/widget/filters");
100487
+ (async () => {
100488
+ try {
100489
+ const headers = {
100490
+ Accept: "application/json"
100491
+ };
100492
+ // In-app JWT path: set Authorization header
100493
+ // Mode B proxy: no auth header; proxy handles org resolution
100494
+ if (typeof getAuthToken === 'function') {
100495
+ const token = await getAuthToken();
100496
+ if (token) {
100497
+ headers['Authorization'] = "Bearer ".concat(token);
100498
+ }
100499
+ }
100500
+ const res = await fetch(url, {
100501
+ method: "GET",
100502
+ headers
100503
+ });
100504
+ if (!res.ok) {
100505
+ throw new Error("filter catalog HTTP ".concat(res.status));
100506
+ }
100507
+ const body = await res.json();
100508
+ const categories = Array.isArray(body === null || body === void 0 ? void 0 : body.categories) ? body.categories : [];
100509
+ if (cancelled) return;
100510
+ _PROCESS_CACHE.set(cacheKey, categories);
100511
+ setState({
100512
+ categories,
100513
+ loading: false,
100514
+ error: null
100515
+ });
100516
+ } catch (err) {
100517
+ if (cancelled) return;
100518
+ // Fail-soft: log and present empty so the UI hides itself.
100519
+ // We do NOT bubble the error to the caller because chat
100520
+ // should keep working even if the catalog is unreachable.
100521
+ // eslint-disable-next-line no-console
100522
+ console.warn("[useFilterCatalog] catalog fetch failed:", err);
100523
+ setState({
100524
+ categories: [],
100525
+ loading: false,
100526
+ error: err
100527
+ });
100398
100528
  }
100399
- const body = await res.json();
100400
- return Array.isArray(body === null || body === void 0 ? void 0 : body.categories) ? body.categories : [];
100401
- }).then(categories => {
100402
- if (cancelled) return;
100403
- _PROCESS_CACHE.set(key, categories);
100404
- setState({
100405
- categories,
100406
- loading: false,
100407
- error: null
100408
- });
100409
- }).catch(err => {
100410
- if (cancelled) return;
100411
- // Fail-soft: log and present empty so the UI hides itself.
100412
- // We do NOT bubble the error to the caller because chat
100413
- // should keep working even if the catalog is unreachable.
100414
- // eslint-disable-next-line no-console
100415
- console.warn("[useFilterCatalog] catalog fetch failed:", err);
100416
- setState({
100417
- categories: [],
100418
- loading: false,
100419
- error: err
100420
- });
100421
- });
100529
+ })();
100422
100530
  return () => {
100423
100531
  cancelled = true;
100424
100532
  };
100425
- }, [apiBaseUrl, orgId]);
100533
+ }, [apiBaseUrl, getAuthToken]);
100426
100534
  const hasFilters = reactExports.useMemo(() => Array.isArray(state.categories) && state.categories.length > 0, [state.categories]);
100427
100535
  return _objectSpread2(_objectSpread2({}, state), {}, {
100428
100536
  hasFilters
@@ -100501,20 +100609,31 @@
100501
100609
  /**
100502
100610
  * Fetches agent configuration from the deploy-agent endpoint
100503
100611
  * @param {string} apiBaseUrl - Base URL for the API
100504
- * @param {string} orgId - Organization ID
100612
+ * @param {Function|undefined} getAuthToken - async fn returning a Bearer token (in-app JWT path), or undefined (Mode B proxy)
100505
100613
  * @returns {Promise<object>} Agent configuration object
100506
100614
  */
100507
- const fetchAgentConfig = async (apiBaseUrl, orgId) => {
100508
- if (!apiBaseUrl || !orgId) {
100509
- throw new Error("apiBaseUrl and orgId are required");
100615
+ const fetchAgentConfig = async (apiBaseUrl, getAuthToken) => {
100616
+ if (!apiBaseUrl) {
100617
+ throw new Error("apiBaseUrl is required");
100510
100618
  }
100511
100619
  try {
100512
- const url = "".concat(apiBaseUrl.replace(/\/$/, ''), "/api/v1/deploy-agent/").concat(orgId);
100620
+ const base = apiBaseUrl.replace(/\/$/, '');
100621
+ const url = "".concat(base, "/api/v1/deploy-agent");
100622
+ const headers = {
100623
+ "Content-Type": "application/json"
100624
+ };
100625
+
100626
+ // In-app JWT path: set Authorization header
100627
+ // Mode B proxy: no auth header; proxy handles org resolution
100628
+ if (typeof getAuthToken === 'function') {
100629
+ const token = await getAuthToken();
100630
+ if (token) {
100631
+ headers['Authorization'] = "Bearer ".concat(token);
100632
+ }
100633
+ }
100513
100634
  const response = await fetch(url, {
100514
100635
  method: "GET",
100515
- headers: {
100516
- "Content-Type": "application/json"
100517
- },
100636
+ headers,
100518
100637
  credentials: "include"
100519
100638
  });
100520
100639
  if (!response.ok) {
@@ -100531,8 +100650,10 @@
100531
100650
  const ChatWidget = _ref => {
100532
100651
  var _agentConfig$concierg;
100533
100652
  let {
100534
- // Required props for dynamic config
100535
- orgId,
100653
+ /**
100654
+ * @param {Function|undefined} getAuthToken - async fn returning a Keycloak Bearer token (in-app JWT path)
100655
+ */
100656
+ getAuthToken,
100536
100657
  apiBaseUrl,
100537
100658
  // Optional override props (will override fetched config if provided)
100538
100659
  darkMode,
@@ -100574,7 +100695,8 @@
100574
100695
  onboardingQuestions,
100575
100696
  collectionPrompt,
100576
100697
  allowedDomains,
100577
- onboardingEnabled
100698
+ onboardingEnabled,
100699
+ wsBaseUrl
100578
100700
  } = _ref;
100579
100701
  const [agentConfig, setAgentConfig] = reactExports.useState(null);
100580
100702
  const [isLoadingConfig, setIsLoadingConfig] = reactExports.useState(true);
@@ -100583,14 +100705,14 @@
100583
100705
  // Fetch agent configuration on mount
100584
100706
  reactExports.useEffect(() => {
100585
100707
  const loadAgentConfig = async () => {
100586
- if (!orgId || !apiBaseUrl) {
100587
- setConfigError("orgId and apiBaseUrl are required");
100708
+ if (!apiBaseUrl) {
100709
+ setConfigError("apiBaseUrl is required");
100588
100710
  setIsLoadingConfig(false);
100589
100711
  return;
100590
100712
  }
100591
100713
  try {
100592
100714
  setIsLoadingConfig(true);
100593
- const config = await fetchAgentConfig(apiBaseUrl, orgId);
100715
+ const config = await fetchAgentConfig(apiBaseUrl, getAuthToken);
100594
100716
  setAgentConfig(config);
100595
100717
  setConfigError(null);
100596
100718
  } catch (error) {
@@ -100601,11 +100723,11 @@
100601
100723
  }
100602
100724
  };
100603
100725
  loadAgentConfig();
100604
- }, [orgId, apiBaseUrl]);
100726
+ }, [apiBaseUrl, getAuthToken]);
100605
100727
 
100606
100728
  // Map API response to widget props (use props as overrides if provided)
100607
100729
  const widgetConfig = reactExports.useMemo(() => {
100608
- var _agentConfig$config, _cfg$identity, _cfg$branding, _cfg$typography, _cfg$messages, _cfg$voice, _cfg$onboarding, _cfg$disclaimer, _cfg$csat, _cfg$inactivity, _cfg$behavior, _cfg$security, _cfg$layout, _ref2, _messages$bundles$loc, _messages$bundles, _messages$bundles$en, _messages$bundles2, _ref3, _ref4, _ref5, _ref6, _branding$chatbotAvat, _branding$chatbotAvat2, _ref7, _ref8, _ref9, _ref0, _ref1, _ref10, _ref11, _ref12, _typography$headerTex, _ref13, _ref14, _ref15, _ref16, _ref17, _ref18, _inactivity$timeoutVa, _inactivity$timeoutUn, _ref19, _ref20, _ref21, _ref22, _ref23, _ref24, _ref25, _ref26, _csat$enabled, _csat$format, _csat$prompt, _csat$triggerType, _csat$idleTimeoutMins, _csat$followUpEnabled, _csat$followUpPrompt, _behavior$autoPromptE, _behavior$allowFeedba, _behavior$provideCita;
100730
+ var _agentConfig$config, _cfg$identity, _cfg$branding, _cfg$typography, _cfg$messages, _cfg$voice, _cfg$onboarding, _cfg$disclaimer, _cfg$csat, _cfg$inactivity, _cfg$behavior, _cfg$security, _cfg$layout, _ref2, _messages$bundles$loc, _messages$bundles, _messages$bundles$en, _messages$bundles2, _ref3, _ref4, _ref5, _ref6, _branding$chatbotAvat, _branding$chatbotAvat2, _ref7, _ref8, _ref9, _ref0, _ref1, _ref10, _ref11, _ref12, _typography$headerTex, _ref13, _ref14, _ref15, _ref16, _ref17, _ref18, _inactivity$timeoutVa, _inactivity$timeoutUn, _ref19, _ref20, _ref21, _ref22, _ref23, _ref24, _ref25, _ref26, _csat$enabled, _csat$format, _csat$prompt, _csat$triggerType, _csat$idleTimeoutMins, _csat$followUpEnabled, _csat$followUpPrompt, _behavior$autoPromptE, _behavior$allowFeedba, _behavior$provideCita, _agentConfig$organiza;
100609
100731
  if (!agentConfig) {
100610
100732
  return null;
100611
100733
  }
@@ -100664,7 +100786,6 @@
100664
100786
  fontFamily: (_ref1 = fontFamily !== null && fontFamily !== void 0 ? fontFamily : typography.agentMessageBubbleFontFamily) !== null && _ref1 !== void 0 ? _ref1 : "Inter",
100665
100787
  fontSize: (_ref10 = fontSize !== null && fontSize !== void 0 ? fontSize : typography.agentMessageBubbleFontSize) !== null && _ref10 !== void 0 ? _ref10 : "14px",
100666
100788
  defaultLanguage: locale,
100667
- organizationId: orgId,
100668
100789
  headerTextBold: (_ref11 = headerTextBold !== null && headerTextBold !== void 0 ? headerTextBold : typography.headerTextBold) !== null && _ref11 !== void 0 ? _ref11 : false,
100669
100790
  headerTextItalic: (_ref12 = headerTextItalic !== null && headerTextItalic !== void 0 ? headerTextItalic : typography.headerTextItalic) !== null && _ref12 !== void 0 ? _ref12 : false,
100670
100791
  headerTextColor: (_typography$headerTex = typography.headerTextColor) !== null && _typography$headerTex !== void 0 ? _typography$headerTex : "#FFFFFF",
@@ -100701,9 +100822,12 @@
100701
100822
  // every message and tell the backend to skip citation generation.
100702
100823
  // Defaults to true so agents that haven't set the field keep showing
100703
100824
  // citations (matches the testing-lab / chatbot-preview default).
100704
- provideCitations: (_behavior$provideCita = behavior.provideCitations) !== null && _behavior$provideCita !== void 0 ? _behavior$provideCita : true
100825
+ provideCitations: (_behavior$provideCita = behavior.provideCitations) !== null && _behavior$provideCita !== void 0 ? _behavior$provideCita : true,
100826
+ // organizationId from the backend response — used internally for
100827
+ // session isolation (localStorage keys) and WS paths. Not a user prop.
100828
+ organizationId: (_agentConfig$organiza = agentConfig.organizationId) !== null && _agentConfig$organiza !== void 0 ? _agentConfig$organiza : null
100705
100829
  };
100706
- }, [agentConfig, darkMode, primaryColor, position, companyName, conciergeName, companyLogo, welcomeMessage, quickQuestions, quickQuestionsEnabled, textColor, agentMessageBubbleColor, userMessageBoxColor, assistantTextColor, userTextColor, fontFamily, fontSize, defaultLanguage, orgId, headerTextBold, headerTextItalic, enableVoiceInteraction, disclaimerEnabled, disclaimerText, disclaimerPosition, disclaimerLinkLabel, disclaimerLinkUrl, showPoweredBy, onboardingQuestions, onboardingEnabled, collectionPrompt, allowedDomains, agentMessageBubbleFontFamily, agentMessageBubbleFontSize, userMessageBubbleFontFamily, userMessageBubbleFontSize
100830
+ }, [agentConfig, darkMode, primaryColor, position, companyName, conciergeName, companyLogo, welcomeMessage, quickQuestions, quickQuestionsEnabled, textColor, agentMessageBubbleColor, userMessageBoxColor, assistantTextColor, userTextColor, fontFamily, fontSize, defaultLanguage, headerTextBold, headerTextItalic, enableVoiceInteraction, disclaimerEnabled, disclaimerText, disclaimerPosition, disclaimerLinkLabel, disclaimerLinkUrl, showPoweredBy, onboardingQuestions, onboardingEnabled, collectionPrompt, allowedDomains, agentMessageBubbleFontFamily, agentMessageBubbleFontSize, userMessageBubbleFontFamily, userMessageBubbleFontSize
100707
100831
  // CSAT dependencies
100708
100832
  // (kept minimal because these come from agentConfig)
100709
100833
  ]);
@@ -100722,7 +100846,7 @@
100722
100846
  quickQuestions: [],
100723
100847
  apiBaseUrl: apiBaseUrl || "",
100724
100848
  apiKey: apiKey || "",
100725
- organizationId: orgId || "",
100849
+ getAuthToken,
100726
100850
  autoOpen: false,
100727
100851
  openDelay: 0,
100728
100852
  darkMode: false,
@@ -100730,7 +100854,7 @@
100730
100854
  onboardingQuestions: [],
100731
100855
  onboardingEnabled: false,
100732
100856
  collectionPrompt: undefined
100733
- }), [apiBaseUrl, apiKey, orgId]);
100857
+ }), [apiBaseUrl, apiKey, getAuthToken]);
100734
100858
 
100735
100859
  // ALWAYS call hooks before any early returns (Rules of Hooks)
100736
100860
  const {
@@ -100772,13 +100896,10 @@
100772
100896
  welcomeMessage: widgetConfig.welcomeMessage,
100773
100897
  quickQuestions: widgetConfig.quickQuestions,
100774
100898
  apiBaseUrl,
100775
- // Voice WS endpoint is derived entirely from apiBaseUrl inside
100776
- // useChatState (→ wss://{host}/voice/ws, the frontend voice proxy).
100777
- // Nothing is forwarded/overridable here, so a stale backend or embed
100778
- // wsBaseUrl can't point voice at the old (now non-public)
100779
- // knowledge-api LoadBalancer.
100899
+ wsBaseUrl,
100780
100900
  apiKey,
100781
100901
  organizationId: widgetConfig.organizationId,
100902
+ getAuthToken,
100782
100903
  autoOpen,
100783
100904
  openDelay,
100784
100905
  darkMode: widgetConfig.darkMode,
@@ -100805,24 +100926,33 @@
100805
100926
  // navigation (but not a page refresh — feedback is not written to
100806
100927
  // localStorage so it resets on reload as intended).
100807
100928
  updateMessageFeedback(messageContent, feedback);
100808
- if (!orgId || !conversationId || !apiBaseUrl) return;
100929
+ if (!conversationId || !apiBaseUrl) return;
100809
100930
  const base = apiBaseUrl.replace(/\/$/, "");
100810
- fetch("".concat(base, "/api/v1/support/feedback"), {
100811
- method: "POST",
100812
- headers: {
100813
- "Content-Type": "application/json",
100814
- "x-eshal-org": orgId
100815
- },
100816
- credentials: "include",
100817
- body: JSON.stringify({
100818
- messageContent,
100819
- conversationId,
100820
- organizationId: orgId,
100821
- feedback,
100822
- feedbackText
100823
- })
100824
- }).catch(err => console.error("Feedback submission error:", err));
100825
- }, [orgId, conversationId, apiBaseUrl, updateMessageFeedback]);
100931
+ (async () => {
100932
+ try {
100933
+ const feedbackHeaders = {
100934
+ "Content-Type": "application/json"
100935
+ };
100936
+ if (typeof getAuthToken === 'function') {
100937
+ const token = await getAuthToken();
100938
+ if (token) feedbackHeaders['Authorization'] = "Bearer ".concat(token);
100939
+ }
100940
+ await fetch("".concat(base, "/api/v1/support/feedback"), {
100941
+ method: "POST",
100942
+ headers: feedbackHeaders,
100943
+ credentials: "include",
100944
+ body: JSON.stringify({
100945
+ messageContent,
100946
+ conversationId,
100947
+ feedback,
100948
+ feedbackText
100949
+ })
100950
+ });
100951
+ } catch (err) {
100952
+ console.error("Feedback submission error:", err);
100953
+ }
100954
+ })();
100955
+ }, [conversationId, apiBaseUrl, getAuthToken, updateMessageFeedback]);
100826
100956
 
100827
100957
  // Refresh chat handler — reset conversation
100828
100958
  const handleRefreshChat = reactExports.useCallback(() => {
@@ -100838,7 +100968,7 @@
100838
100968
  const {
100839
100969
  categories: filterCategories,
100840
100970
  hasFilters: showFilterStep
100841
- } = useFilterCatalog(apiBaseUrl, orgId);
100971
+ } = useFilterCatalog(apiBaseUrl, getAuthToken);
100842
100972
 
100843
100973
  // Copy chat handler — copy all messages to clipboard
100844
100974
  const handleCopyChat = reactExports.useCallback(() => {
@@ -100943,7 +101073,7 @@
100943
101073
  showPoweredBy: widgetConfig.showPoweredBy,
100944
101074
  apiBaseUrl: apiBaseUrl,
100945
101075
  apiKey: apiKey,
100946
- organizationId: widgetConfig.organizationId,
101076
+ getAuthToken: getAuthToken,
100947
101077
  conversationId: conversationId,
100948
101078
  conciergeId: (_agentConfig$concierg = agentConfig === null || agentConfig === void 0 ? void 0 : agentConfig.conciergeId) !== null && _agentConfig$concierg !== void 0 ? _agentConfig$concierg : agentConfig === null || agentConfig === void 0 ? void 0 : agentConfig.id,
100949
101079
  onboardingQuestions: widgetConfig.onboardingQuestions,
@@ -101091,11 +101221,10 @@
101091
101221
  }
101092
101222
 
101093
101223
  // Render component
101094
- // If orgId and apiBaseUrl are provided, use dynamic config
101095
- // Otherwise, use the old static config approach for backward compatibility
101096
- const widgetProps = config.orgId && config.apiBaseUrl ? _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({
101097
- // Required props
101098
- orgId: config.orgId,
101224
+ // Mode B proxy: apiBaseUrl alone is enough — no key or orgId in browser
101225
+ // In-app JWT: getAuthToken fn passed via config
101226
+ const hasNewAuth = config.apiBaseUrl;
101227
+ const widgetProps = hasNewAuth ? _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({
101099
101228
  apiBaseUrl: config.apiBaseUrl
101100
101229
  }, options.wsBaseUrl !== undefined && {
101101
101230
  wsBaseUrl: config.wsBaseUrl