@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.
- package/dist/chat-widget.esm.js +3 -3
- package/dist/chat-widget.js +296 -167
- package/dist/chat-widget.min.js +3 -3
- package/dist/chat-widget.umd.js +3 -3
- package/package.json +1 -1
- package/dist/chat-widget.esm.js.map +0 -1
- package/dist/chat-widget.js.map +0 -1
- package/dist/chat-widget.umd.js.map +0 -1
package/dist/chat-widget.js
CHANGED
|
@@ -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,
|
|
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
|
-
//
|
|
8021
|
-
|
|
8022
|
-
|
|
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
|
|
8763
|
-
* base URL the widget is embedded with, as `wss://{host}/voice/ws`.
|
|
8780
|
+
* Constructs the voice WebSocket base URL.
|
|
8764
8781
|
*
|
|
8765
|
-
*
|
|
8766
|
-
*
|
|
8767
|
-
*
|
|
8768
|
-
*
|
|
8769
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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`)
|
|
92674
|
-
// below so a chart of monthly
|
|
92675
|
-
//
|
|
92676
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 (
|
|
99848
|
-
|
|
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 (
|
|
99883
|
-
|
|
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,
|
|
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
|
-
|
|
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 {
|
|
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,
|
|
100454
|
+
function useFilterCatalog(apiBaseUrl, getAuthToken) {
|
|
100455
|
+
const hasAuth = !!apiBaseUrl;
|
|
100360
100456
|
const [state, setState] = reactExports.useState({
|
|
100361
100457
|
categories: [],
|
|
100362
|
-
loading:
|
|
100458
|
+
loading: hasAuth,
|
|
100363
100459
|
error: null
|
|
100364
100460
|
});
|
|
100365
100461
|
reactExports.useEffect(() => {
|
|
100366
|
-
if (!apiBaseUrl
|
|
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
|
|
100375
|
-
const cached = _PROCESS_CACHE.get(
|
|
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
|
|
100390
|
-
|
|
100391
|
-
|
|
100392
|
-
|
|
100393
|
-
|
|
100394
|
-
|
|
100395
|
-
|
|
100396
|
-
|
|
100397
|
-
|
|
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
|
-
|
|
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,
|
|
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 {
|
|
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,
|
|
100508
|
-
if (!apiBaseUrl
|
|
100509
|
-
throw new Error("apiBaseUrl
|
|
100615
|
+
const fetchAgentConfig = async (apiBaseUrl, getAuthToken) => {
|
|
100616
|
+
if (!apiBaseUrl) {
|
|
100617
|
+
throw new Error("apiBaseUrl is required");
|
|
100510
100618
|
}
|
|
100511
100619
|
try {
|
|
100512
|
-
const
|
|
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
|
-
|
|
100535
|
-
|
|
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 (!
|
|
100587
|
-
setConfigError("
|
|
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,
|
|
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
|
-
}, [
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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 (!
|
|
100929
|
+
if (!conversationId || !apiBaseUrl) return;
|
|
100809
100930
|
const base = apiBaseUrl.replace(/\/$/, "");
|
|
100810
|
-
|
|
100811
|
-
|
|
100812
|
-
|
|
100813
|
-
|
|
100814
|
-
|
|
100815
|
-
|
|
100816
|
-
|
|
100817
|
-
|
|
100818
|
-
|
|
100819
|
-
|
|
100820
|
-
|
|
100821
|
-
|
|
100822
|
-
|
|
100823
|
-
|
|
100824
|
-
|
|
100825
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
//
|
|
101095
|
-
//
|
|
101096
|
-
const
|
|
101097
|
-
|
|
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
|