@iota-uz/sdk 0.4.17 → 0.4.20
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/bichat/index.cjs +1507 -356
- package/dist/bichat/index.cjs.map +1 -1
- package/dist/bichat/index.d.cts +56 -7
- package/dist/bichat/index.d.ts +56 -7
- package/dist/bichat/index.mjs +1513 -363
- package/dist/bichat/index.mjs.map +1 -1
- package/package.json +1 -1
- package/tailwind/compiled.css +1 -1
package/dist/bichat/index.cjs
CHANGED
|
@@ -190,6 +190,37 @@ var init_ChartCard = __esm({
|
|
|
190
190
|
DEFAULT_COLORS = ["#6366f1", "#06b6d4", "#f59e0b", "#ef4444", "#8b5cf6"];
|
|
191
191
|
}
|
|
192
192
|
});
|
|
193
|
+
exports.TableExportButton = void 0;
|
|
194
|
+
var init_TableExportButton = __esm({
|
|
195
|
+
"ui/src/bichat/components/TableExportButton.tsx"() {
|
|
196
|
+
init_useTranslation();
|
|
197
|
+
exports.TableExportButton = React.memo(function TableExportButton2({
|
|
198
|
+
onClick,
|
|
199
|
+
disabled = false,
|
|
200
|
+
label,
|
|
201
|
+
disabledTooltip
|
|
202
|
+
}) {
|
|
203
|
+
const { t } = useTranslation();
|
|
204
|
+
const resolvedLabel = label ?? t("BiChat.Export");
|
|
205
|
+
const resolvedDisabledTooltip = disabledTooltip ?? t("BiChat.Common.PleaseWait");
|
|
206
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
207
|
+
"button",
|
|
208
|
+
{
|
|
209
|
+
type: "button",
|
|
210
|
+
onClick,
|
|
211
|
+
disabled,
|
|
212
|
+
className: "cursor-pointer inline-flex items-center gap-1 px-2 py-1 text-xs font-medium text-green-700 dark:text-green-400 hover:text-green-800 dark:hover:text-green-300 disabled:text-gray-400 disabled:cursor-not-allowed transition-colors rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50",
|
|
213
|
+
"aria-label": resolvedLabel,
|
|
214
|
+
title: disabled ? resolvedDisabledTooltip : resolvedLabel,
|
|
215
|
+
children: [
|
|
216
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.FileXls, { size: 16, weight: "fill" }),
|
|
217
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: resolvedLabel })
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
});
|
|
193
224
|
|
|
194
225
|
// ui/src/bichat/utils/citationProcessor.ts
|
|
195
226
|
function processCitations(content, citations) {
|
|
@@ -302,37 +333,6 @@ var init_chartSpec = __esm({
|
|
|
302
333
|
]);
|
|
303
334
|
}
|
|
304
335
|
});
|
|
305
|
-
exports.TableExportButton = void 0;
|
|
306
|
-
var init_TableExportButton = __esm({
|
|
307
|
-
"ui/src/bichat/components/TableExportButton.tsx"() {
|
|
308
|
-
init_useTranslation();
|
|
309
|
-
exports.TableExportButton = React.memo(function TableExportButton2({
|
|
310
|
-
onClick,
|
|
311
|
-
disabled = false,
|
|
312
|
-
label,
|
|
313
|
-
disabledTooltip
|
|
314
|
-
}) {
|
|
315
|
-
const { t } = useTranslation();
|
|
316
|
-
const resolvedLabel = label ?? t("BiChat.Export");
|
|
317
|
-
const resolvedDisabledTooltip = disabledTooltip ?? t("BiChat.Common.PleaseWait");
|
|
318
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
319
|
-
"button",
|
|
320
|
-
{
|
|
321
|
-
type: "button",
|
|
322
|
-
onClick,
|
|
323
|
-
disabled,
|
|
324
|
-
className: "cursor-pointer inline-flex items-center gap-1 px-2 py-1 text-xs font-medium text-green-700 dark:text-green-400 hover:text-green-800 dark:hover:text-green-300 disabled:text-gray-400 disabled:cursor-not-allowed transition-colors rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50",
|
|
325
|
-
"aria-label": resolvedLabel,
|
|
326
|
-
title: disabled ? resolvedDisabledTooltip : resolvedLabel,
|
|
327
|
-
children: [
|
|
328
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.FileXls, { size: 16, weight: "fill" }),
|
|
329
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: resolvedLabel })
|
|
330
|
-
]
|
|
331
|
-
}
|
|
332
|
-
);
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
336
|
exports.TableWithExport = void 0;
|
|
337
337
|
var init_TableWithExport = __esm({
|
|
338
338
|
"ui/src/bichat/components/TableWithExport.tsx"() {
|
|
@@ -1518,6 +1518,10 @@ var ChatMachine = class {
|
|
|
1518
1518
|
}
|
|
1519
1519
|
/** Sets turns from fetch, preserving pending user-only turns if server hasn't caught up. */
|
|
1520
1520
|
_setTurnsFromFetch(fetchedTurns) {
|
|
1521
|
+
if (!Array.isArray(fetchedTurns)) {
|
|
1522
|
+
console.warn("[ChatMachine] Ignoring malformed turns payload from fetchSession");
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1521
1525
|
const prev = this.state.messaging.turns;
|
|
1522
1526
|
const hasPendingUserOnly = prev.length > 0 && !prev[prev.length - 1].assistantTurn;
|
|
1523
1527
|
if (hasPendingUserOnly && (!fetchedTurns || fetchedTurns.length === 0)) {
|
|
@@ -1543,6 +1547,12 @@ var ChatMachine = class {
|
|
|
1543
1547
|
streamErrorRetryable: false
|
|
1544
1548
|
});
|
|
1545
1549
|
}
|
|
1550
|
+
_notifySessionsUpdated(reason, sessionId) {
|
|
1551
|
+
if (typeof window === "undefined") return;
|
|
1552
|
+
window.dispatchEvent(new CustomEvent("bichat:sessions-updated", {
|
|
1553
|
+
detail: { reason, sessionId }
|
|
1554
|
+
}));
|
|
1555
|
+
}
|
|
1546
1556
|
_cancel() {
|
|
1547
1557
|
if (this.abortController) {
|
|
1548
1558
|
this.abortController.abort();
|
|
@@ -1810,7 +1820,11 @@ var ChatMachine = class {
|
|
|
1810
1820
|
}
|
|
1811
1821
|
}
|
|
1812
1822
|
const targetSessionId = createdSessionId || activeSessionId;
|
|
1823
|
+
if (targetSessionId && targetSessionId !== "new") {
|
|
1824
|
+
this._notifySessionsUpdated("message_sent", targetSessionId);
|
|
1825
|
+
}
|
|
1813
1826
|
if (shouldNavigateAfter && targetSessionId && targetSessionId !== "new") {
|
|
1827
|
+
this._notifySessionsUpdated("session_created", targetSessionId);
|
|
1814
1828
|
if (this.onSessionCreated) {
|
|
1815
1829
|
this.onSessionCreated(targetSessionId);
|
|
1816
1830
|
} else {
|
|
@@ -1907,6 +1921,7 @@ var ChatMachine = class {
|
|
|
1907
1921
|
const curSessionId = this.state.session.currentSessionId;
|
|
1908
1922
|
const curPendingQuestion = this.state.messaging.pendingQuestion;
|
|
1909
1923
|
if (!curSessionId || !curPendingQuestion) return;
|
|
1924
|
+
const previousTurns = this.state.messaging.turns;
|
|
1910
1925
|
this._updateMessaging({ loading: true });
|
|
1911
1926
|
this._updateSession({ error: null, errorRetryable: false });
|
|
1912
1927
|
const previousPendingQuestion = curPendingQuestion;
|
|
@@ -1924,19 +1939,28 @@ var ChatMachine = class {
|
|
|
1924
1939
|
const fetchResult = await this.dataSource.fetchSession(curSessionId);
|
|
1925
1940
|
if (this.disposed) return;
|
|
1926
1941
|
if (fetchResult) {
|
|
1927
|
-
this.
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1942
|
+
this._updateSession({ session: fetchResult.session });
|
|
1943
|
+
this._updateMessaging({ pendingQuestion: fetchResult.pendingQuestion || null });
|
|
1944
|
+
const hasMalformedRefresh = previousTurns.length > 0 && Array.isArray(fetchResult.turns) && fetchResult.turns.length === 0;
|
|
1945
|
+
if (hasMalformedRefresh) {
|
|
1946
|
+
console.warn("[ChatMachine] Preserving previous turns due to empty post-HITL refetch payload", {
|
|
1947
|
+
sessionId: curSessionId,
|
|
1948
|
+
previousTurnCount: previousTurns.length
|
|
1949
|
+
});
|
|
1950
|
+
this._updateSession({
|
|
1951
|
+
error: "Failed to fully refresh session. Showing last known messages.",
|
|
1952
|
+
errorRetryable: true
|
|
1953
|
+
});
|
|
1954
|
+
} else {
|
|
1955
|
+
this._setTurnsFromFetch(fetchResult.turns);
|
|
1956
|
+
}
|
|
1931
1957
|
} else {
|
|
1932
|
-
this.
|
|
1933
|
-
this._updateSession({ error: "Failed to load updated session", errorRetryable: false });
|
|
1958
|
+
this._updateSession({ error: "Failed to load updated session", errorRetryable: true });
|
|
1934
1959
|
}
|
|
1935
1960
|
} catch (fetchErr) {
|
|
1936
1961
|
if (this.disposed) return;
|
|
1937
|
-
this._updateMessaging({ pendingQuestion: previousPendingQuestion });
|
|
1938
1962
|
const normalized = normalizeRPCError(fetchErr, "Failed to load updated session");
|
|
1939
|
-
this._updateSession({ error: normalized.userMessage, errorRetryable:
|
|
1963
|
+
this._updateSession({ error: normalized.userMessage, errorRetryable: true });
|
|
1940
1964
|
}
|
|
1941
1965
|
}
|
|
1942
1966
|
} else {
|
|
@@ -1990,6 +2014,8 @@ var ChatMachine = class {
|
|
|
1990
2014
|
this._clearStreamError();
|
|
1991
2015
|
const convertedAttachments = attachments.map((att) => ({
|
|
1992
2016
|
clientKey: att.clientKey || crypto.randomUUID(),
|
|
2017
|
+
id: att.id,
|
|
2018
|
+
uploadId: att.uploadId,
|
|
1993
2019
|
filename: att.filename,
|
|
1994
2020
|
mimeType: att.mimeType,
|
|
1995
2021
|
sizeBytes: att.sizeBytes,
|
|
@@ -2652,6 +2678,9 @@ var MemoizedAttachmentGrid = React__default.default.memo(AttachmentGrid);
|
|
|
2652
2678
|
MemoizedAttachmentGrid.displayName = "AttachmentGrid";
|
|
2653
2679
|
var AttachmentGrid_default = MemoizedAttachmentGrid;
|
|
2654
2680
|
init_useTranslation();
|
|
2681
|
+
var MIN_SCALE = 0.25;
|
|
2682
|
+
var MAX_SCALE = 5;
|
|
2683
|
+
var ZOOM_STEP = 0.25;
|
|
2655
2684
|
function ImageModal({
|
|
2656
2685
|
isOpen,
|
|
2657
2686
|
onClose,
|
|
@@ -2664,9 +2693,23 @@ function ImageModal({
|
|
|
2664
2693
|
const [isImageLoaded, setIsImageLoaded] = React.useState(false);
|
|
2665
2694
|
const [imageError, setImageError] = React.useState(false);
|
|
2666
2695
|
const [retryKey, setRetryKey] = React.useState(0);
|
|
2696
|
+
const [scale, setScale] = React.useState(1);
|
|
2697
|
+
const [position, setPosition] = React.useState({ x: 0, y: 0 });
|
|
2698
|
+
const [isDragging, setIsDragging] = React.useState(false);
|
|
2699
|
+
const dragStartRef = React.useRef({ x: 0, y: 0 });
|
|
2700
|
+
const positionRef = React.useRef({ x: 0, y: 0 });
|
|
2701
|
+
const scaleRef = React.useRef(1);
|
|
2702
|
+
const imageAreaRef = React.useRef(null);
|
|
2667
2703
|
const hasMultipleImages = allAttachments && allAttachments.length > 1;
|
|
2668
2704
|
const canNavigatePrev = hasMultipleImages && currentIndex > 0;
|
|
2669
2705
|
const canNavigateNext = hasMultipleImages && currentIndex < (allAttachments?.length || 1) - 1;
|
|
2706
|
+
const isZoomed = scale > 1;
|
|
2707
|
+
React.useEffect(() => {
|
|
2708
|
+
scaleRef.current = scale;
|
|
2709
|
+
}, [scale]);
|
|
2710
|
+
React.useEffect(() => {
|
|
2711
|
+
positionRef.current = position;
|
|
2712
|
+
}, [position]);
|
|
2670
2713
|
React.useEffect(() => {
|
|
2671
2714
|
if (!isOpen) return;
|
|
2672
2715
|
const handleKeyDown = (e) => {
|
|
@@ -2674,6 +2717,14 @@ function ImageModal({
|
|
|
2674
2717
|
onNavigate("prev");
|
|
2675
2718
|
} else if (e.key === "ArrowRight" && onNavigate && canNavigateNext) {
|
|
2676
2719
|
onNavigate("next");
|
|
2720
|
+
} else if (e.key === "+" || e.key === "=") {
|
|
2721
|
+
setScale((s) => Math.min(s + ZOOM_STEP, MAX_SCALE));
|
|
2722
|
+
} else if (e.key === "-") {
|
|
2723
|
+
setScale((s) => Math.max(s - ZOOM_STEP, MIN_SCALE));
|
|
2724
|
+
if (scaleRef.current - ZOOM_STEP <= 1) setPosition({ x: 0, y: 0 });
|
|
2725
|
+
} else if (e.key === "0") {
|
|
2726
|
+
setScale(1);
|
|
2727
|
+
setPosition({ x: 0, y: 0 });
|
|
2677
2728
|
}
|
|
2678
2729
|
};
|
|
2679
2730
|
document.addEventListener("keydown", handleKeyDown);
|
|
@@ -2682,128 +2733,246 @@ function ImageModal({
|
|
|
2682
2733
|
React.useEffect(() => {
|
|
2683
2734
|
setIsImageLoaded(false);
|
|
2684
2735
|
setImageError(false);
|
|
2736
|
+
setScale(1);
|
|
2737
|
+
setPosition({ x: 0, y: 0 });
|
|
2685
2738
|
}, [attachment]);
|
|
2739
|
+
React.useEffect(() => {
|
|
2740
|
+
const el = imageAreaRef.current;
|
|
2741
|
+
if (!el || !isOpen) return;
|
|
2742
|
+
const handler = (e) => {
|
|
2743
|
+
const delta = e.deltaY > 0 ? -ZOOM_STEP : ZOOM_STEP;
|
|
2744
|
+
const current = scaleRef.current;
|
|
2745
|
+
const newScale = Math.min(Math.max(current + delta, MIN_SCALE), MAX_SCALE);
|
|
2746
|
+
if (newScale === current) return;
|
|
2747
|
+
e.preventDefault();
|
|
2748
|
+
setScale(newScale);
|
|
2749
|
+
if (newScale <= 1) setPosition({ x: 0, y: 0 });
|
|
2750
|
+
};
|
|
2751
|
+
el.addEventListener("wheel", handler, { passive: false });
|
|
2752
|
+
return () => el.removeEventListener("wheel", handler);
|
|
2753
|
+
}, [isOpen]);
|
|
2686
2754
|
const handleRetry = React.useCallback(() => {
|
|
2687
2755
|
setImageError(false);
|
|
2688
2756
|
setIsImageLoaded(false);
|
|
2689
2757
|
setRetryKey((k) => k + 1);
|
|
2690
2758
|
}, []);
|
|
2759
|
+
const zoomIn = React.useCallback(() => {
|
|
2760
|
+
setScale((s) => Math.min(s + ZOOM_STEP, MAX_SCALE));
|
|
2761
|
+
}, []);
|
|
2762
|
+
const zoomOut = React.useCallback(() => {
|
|
2763
|
+
setScale((s) => Math.max(s - ZOOM_STEP, MIN_SCALE));
|
|
2764
|
+
if (scaleRef.current - ZOOM_STEP <= 1) setPosition({ x: 0, y: 0 });
|
|
2765
|
+
}, []);
|
|
2766
|
+
const resetZoom = React.useCallback(() => {
|
|
2767
|
+
setScale(1);
|
|
2768
|
+
setPosition({ x: 0, y: 0 });
|
|
2769
|
+
}, []);
|
|
2770
|
+
const handleDoubleClick = React.useCallback(() => {
|
|
2771
|
+
const current = scaleRef.current;
|
|
2772
|
+
if (current !== 1) {
|
|
2773
|
+
setScale(1);
|
|
2774
|
+
setPosition({ x: 0, y: 0 });
|
|
2775
|
+
} else {
|
|
2776
|
+
setScale(2);
|
|
2777
|
+
}
|
|
2778
|
+
}, []);
|
|
2779
|
+
const handleMouseDown = React.useCallback((e) => {
|
|
2780
|
+
if (scaleRef.current <= 1) return;
|
|
2781
|
+
e.preventDefault();
|
|
2782
|
+
setIsDragging(true);
|
|
2783
|
+
dragStartRef.current = {
|
|
2784
|
+
x: e.clientX - positionRef.current.x,
|
|
2785
|
+
y: e.clientY - positionRef.current.y
|
|
2786
|
+
};
|
|
2787
|
+
}, []);
|
|
2788
|
+
const handleMouseMove = React.useCallback((e) => {
|
|
2789
|
+
if (!isDragging) return;
|
|
2790
|
+
setPosition({
|
|
2791
|
+
x: e.clientX - dragStartRef.current.x,
|
|
2792
|
+
y: e.clientY - dragStartRef.current.y
|
|
2793
|
+
});
|
|
2794
|
+
}, [isDragging]);
|
|
2795
|
+
const handleMouseUp = React.useCallback(() => {
|
|
2796
|
+
setIsDragging(false);
|
|
2797
|
+
}, []);
|
|
2798
|
+
const handleBackdropClick = React.useCallback((e) => {
|
|
2799
|
+
if (e.target === e.currentTarget && !isZoomed) {
|
|
2800
|
+
onClose();
|
|
2801
|
+
}
|
|
2802
|
+
}, [isZoomed, onClose]);
|
|
2691
2803
|
const previewUrl = attachment.preview || createDataUrl(attachment.base64Data, attachment.mimeType);
|
|
2804
|
+
const zoomPercent = Math.round(scale * 100);
|
|
2692
2805
|
return /* @__PURE__ */ jsxRuntime.jsxs(react$1.Dialog, { open: isOpen, onClose, className: "relative", style: { zIndex: 99999 }, children: [
|
|
2693
2806
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2694
2807
|
react$1.DialogBackdrop,
|
|
2695
2808
|
{
|
|
2696
|
-
className: "fixed inset-0",
|
|
2697
|
-
style: { zIndex: 99999
|
|
2809
|
+
className: "fixed inset-0 bg-black/90 backdrop-blur-sm",
|
|
2810
|
+
style: { zIndex: 99999 }
|
|
2698
2811
|
}
|
|
2699
2812
|
),
|
|
2700
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
onClick: onClose,
|
|
2715
|
-
className: "cursor-pointer flex items-center justify-center w-8 h-8 rounded-md bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-400",
|
|
2716
|
-
"aria-label": t("BiChat.Image.Close"),
|
|
2717
|
-
type: "button",
|
|
2718
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(react.X, { size: 18, weight: "bold" })
|
|
2719
|
-
}
|
|
2720
|
-
)
|
|
2721
|
-
] }),
|
|
2722
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2723
|
-
"div",
|
|
2724
|
-
{
|
|
2725
|
-
className: "relative flex-1 flex items-center justify-center min-h-0",
|
|
2726
|
-
onClick: (e) => {
|
|
2727
|
-
if (e.target === e.currentTarget) onClose();
|
|
2728
|
-
},
|
|
2729
|
-
children: [
|
|
2730
|
-
!isImageLoaded && !imageError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-3", children: [
|
|
2731
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 h-8 border-2 border-gray-300 dark:border-gray-700 border-t-gray-500 dark:border-t-gray-400 rounded-full animate-spin" }),
|
|
2732
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400 dark:text-gray-500", children: t("BiChat.Loading") })
|
|
2733
|
-
] }) }),
|
|
2734
|
-
imageError && /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "alert", className: "flex flex-col items-center justify-center text-center max-w-xs", children: [
|
|
2735
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center w-16 h-16 rounded-2xl bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 mb-5", children: /* @__PURE__ */ jsxRuntime.jsx(react.ImageBroken, { size: 28, className: "text-gray-400 dark:text-gray-500", weight: "duotone" }) }),
|
|
2736
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", children: t("BiChat.Image.FailedToLoad") }),
|
|
2737
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400 dark:text-gray-500 mb-5 truncate max-w-full", children: attachment.filename }),
|
|
2738
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2739
|
-
"button",
|
|
2740
|
-
{
|
|
2741
|
-
type: "button",
|
|
2742
|
-
onClick: handleRetry,
|
|
2743
|
-
className: "cursor-pointer inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 border border-gray-200 dark:border-gray-700 rounded-lg transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-400",
|
|
2744
|
-
"aria-label": t("BiChat.Image.Retry"),
|
|
2745
|
-
children: [
|
|
2746
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.ArrowClockwise, { size: 16, weight: "bold" }),
|
|
2747
|
-
t("BiChat.Retry.Label")
|
|
2748
|
-
]
|
|
2749
|
-
}
|
|
2750
|
-
)
|
|
2813
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2814
|
+
react$1.DialogPanel,
|
|
2815
|
+
{
|
|
2816
|
+
className: "fixed inset-0 flex flex-col",
|
|
2817
|
+
style: { zIndex: 1e5 },
|
|
2818
|
+
onMouseMove: handleMouseMove,
|
|
2819
|
+
onMouseUp: handleMouseUp,
|
|
2820
|
+
onMouseLeave: handleMouseUp,
|
|
2821
|
+
children: [
|
|
2822
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center px-5 py-3 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
|
|
2823
|
+
hasMultipleImages && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-white/50 tabular-nums whitespace-nowrap font-medium", children: [
|
|
2824
|
+
currentIndex + 1,
|
|
2825
|
+
" / ",
|
|
2826
|
+
allAttachments?.length
|
|
2751
2827
|
] }),
|
|
2752
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
className:
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2828
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-white/90 truncate font-medium", children: attachment.filename }),
|
|
2829
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-white/40 whitespace-nowrap", children: formatFileSize(attachment.sizeBytes) })
|
|
2830
|
+
] }) }),
|
|
2831
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2832
|
+
"div",
|
|
2833
|
+
{
|
|
2834
|
+
ref: imageAreaRef,
|
|
2835
|
+
className: "relative flex-1 flex items-center justify-center min-h-0 px-4 pb-4",
|
|
2836
|
+
onClick: handleBackdropClick,
|
|
2837
|
+
style: { cursor: isZoomed ? isDragging ? "grabbing" : "grab" : "default" },
|
|
2838
|
+
children: [
|
|
2839
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2840
|
+
"button",
|
|
2841
|
+
{
|
|
2842
|
+
onClick: onClose,
|
|
2843
|
+
className: "absolute top-3 right-5 z-30 cursor-pointer flex items-center justify-center w-10 h-10 rounded-full bg-black/50 hover:bg-black/70 backdrop-blur-md text-white/80 hover:text-white border border-white/10 transition-all duration-200 shadow-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/30",
|
|
2844
|
+
"aria-label": t("BiChat.Image.Close"),
|
|
2845
|
+
type: "button",
|
|
2846
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.X, { size: 20, weight: "bold" })
|
|
2847
|
+
}
|
|
2848
|
+
),
|
|
2849
|
+
!isImageLoaded && !imageError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-3", children: [
|
|
2850
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 h-8 border-2 border-white/20 border-t-white/60 rounded-full animate-spin" }),
|
|
2851
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-white/40", children: t("BiChat.Loading") })
|
|
2852
|
+
] }) }),
|
|
2853
|
+
imageError && /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "alert", className: "flex flex-col items-center justify-center text-center max-w-xs", children: [
|
|
2854
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center w-16 h-16 rounded-2xl bg-white/5 border border-white/10 mb-5", children: /* @__PURE__ */ jsxRuntime.jsx(react.ImageBroken, { size: 28, className: "text-white/30", weight: "duotone" }) }),
|
|
2855
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-white/70 mb-1", children: t("BiChat.Image.FailedToLoad") }),
|
|
2856
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-white/30 mb-5 truncate max-w-full", children: attachment.filename }),
|
|
2857
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2858
|
+
"button",
|
|
2859
|
+
{
|
|
2860
|
+
type: "button",
|
|
2861
|
+
onClick: handleRetry,
|
|
2862
|
+
className: "cursor-pointer inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white/80 bg-white/10 hover:bg-white/15 border border-white/10 rounded-lg transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/30",
|
|
2863
|
+
"aria-label": t("BiChat.Image.Retry"),
|
|
2864
|
+
children: [
|
|
2865
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.ArrowClockwise, { size: 16, weight: "bold" }),
|
|
2866
|
+
t("BiChat.Retry.Label")
|
|
2867
|
+
]
|
|
2868
|
+
}
|
|
2869
|
+
)
|
|
2870
|
+
] }),
|
|
2871
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2872
|
+
"img",
|
|
2873
|
+
{
|
|
2874
|
+
src: previewUrl,
|
|
2875
|
+
alt: attachment.filename,
|
|
2876
|
+
className: [
|
|
2877
|
+
"relative z-0 max-w-[85vw] max-h-[calc(100vh-160px)] object-contain select-none rounded-lg",
|
|
2878
|
+
"transition-opacity duration-300 ease-out",
|
|
2879
|
+
isImageLoaded ? "opacity-100" : "opacity-0"
|
|
2880
|
+
].join(" "),
|
|
2881
|
+
style: {
|
|
2882
|
+
transform: `translate(${position.x}px, ${position.y}px) scale(${scale})`,
|
|
2883
|
+
transformOrigin: "center center",
|
|
2884
|
+
transition: isDragging ? "opacity 0.3s ease-out" : "transform 0.2s ease-out, opacity 0.3s ease-out"
|
|
2885
|
+
},
|
|
2886
|
+
onLoad: () => setIsImageLoaded(true),
|
|
2887
|
+
onError: () => setImageError(true),
|
|
2888
|
+
onMouseDown: handleMouseDown,
|
|
2889
|
+
onDoubleClick: handleDoubleClick,
|
|
2890
|
+
loading: "lazy",
|
|
2891
|
+
draggable: false
|
|
2892
|
+
},
|
|
2893
|
+
retryKey
|
|
2894
|
+
),
|
|
2895
|
+
hasMultipleImages && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2896
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2897
|
+
"button",
|
|
2898
|
+
{
|
|
2899
|
+
onClick: () => onNavigate?.("prev"),
|
|
2900
|
+
disabled: !canNavigatePrev || !isImageLoaded || imageError,
|
|
2901
|
+
className: [
|
|
2902
|
+
"absolute left-4 top-1/2 -translate-y-1/2 z-20",
|
|
2903
|
+
"flex items-center justify-center w-11 h-11 rounded-full",
|
|
2904
|
+
"transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/30",
|
|
2905
|
+
canNavigatePrev && isImageLoaded && !imageError ? "cursor-pointer bg-black/40 hover:bg-black/60 backdrop-blur-md text-white/80 hover:text-white shadow-lg border border-white/10" : "bg-black/20 text-white/20 cursor-not-allowed"
|
|
2906
|
+
].join(" "),
|
|
2907
|
+
"aria-label": t("BiChat.Image.Previous"),
|
|
2908
|
+
type: "button",
|
|
2909
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.CaretLeft, { size: 20, weight: "bold" })
|
|
2910
|
+
}
|
|
2911
|
+
),
|
|
2912
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2913
|
+
"button",
|
|
2914
|
+
{
|
|
2915
|
+
onClick: () => onNavigate?.("next"),
|
|
2916
|
+
disabled: !canNavigateNext || !isImageLoaded || imageError,
|
|
2917
|
+
className: [
|
|
2918
|
+
"absolute right-4 top-1/2 -translate-y-1/2 z-20",
|
|
2919
|
+
"flex items-center justify-center w-11 h-11 rounded-full",
|
|
2920
|
+
"transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/30",
|
|
2921
|
+
canNavigateNext && isImageLoaded && !imageError ? "cursor-pointer bg-black/40 hover:bg-black/60 backdrop-blur-md text-white/80 hover:text-white shadow-lg border border-white/10" : "bg-black/20 text-white/20 cursor-not-allowed"
|
|
2922
|
+
].join(" "),
|
|
2923
|
+
"aria-label": t("BiChat.Image.Next"),
|
|
2924
|
+
type: "button",
|
|
2925
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.CaretRight, { size: 20, weight: "bold" })
|
|
2926
|
+
}
|
|
2927
|
+
)
|
|
2928
|
+
] }),
|
|
2929
|
+
isImageLoaded && !imageError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute bottom-6 left-1/2 -translate-x-1/2 z-20 flex items-center gap-0.5 bg-black/50 backdrop-blur-xl rounded-full px-1.5 py-1.5 border border-white/10 shadow-2xl", children: [
|
|
2930
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2931
|
+
"button",
|
|
2932
|
+
{
|
|
2933
|
+
type: "button",
|
|
2934
|
+
onClick: zoomOut,
|
|
2935
|
+
disabled: scale <= MIN_SCALE,
|
|
2936
|
+
className: "cursor-pointer flex items-center justify-center w-8 h-8 rounded-full text-white/70 hover:text-white hover:bg-white/10 transition-colors disabled:text-white/20 disabled:cursor-not-allowed disabled:hover:bg-transparent",
|
|
2937
|
+
"aria-label": t("BiChat.Image.ZoomOut"),
|
|
2938
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.MagnifyingGlassMinus, { size: 16, weight: "bold" })
|
|
2939
|
+
}
|
|
2940
|
+
),
|
|
2941
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-white/60 tabular-nums font-medium min-w-[3.5rem] text-center select-none", children: [
|
|
2942
|
+
zoomPercent,
|
|
2943
|
+
"%"
|
|
2944
|
+
] }),
|
|
2945
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2946
|
+
"button",
|
|
2947
|
+
{
|
|
2948
|
+
type: "button",
|
|
2949
|
+
onClick: zoomIn,
|
|
2950
|
+
disabled: scale >= MAX_SCALE,
|
|
2951
|
+
className: "cursor-pointer flex items-center justify-center w-8 h-8 rounded-full text-white/70 hover:text-white hover:bg-white/10 transition-colors disabled:text-white/20 disabled:cursor-not-allowed disabled:hover:bg-transparent",
|
|
2952
|
+
"aria-label": t("BiChat.Image.ZoomIn"),
|
|
2953
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.MagnifyingGlassPlus, { size: 16, weight: "bold" })
|
|
2954
|
+
}
|
|
2955
|
+
),
|
|
2956
|
+
isZoomed && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2957
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-4 bg-white/15 mx-1" }),
|
|
2958
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2959
|
+
"button",
|
|
2960
|
+
{
|
|
2961
|
+
type: "button",
|
|
2962
|
+
onClick: resetZoom,
|
|
2963
|
+
className: "cursor-pointer flex items-center justify-center w-8 h-8 rounded-full text-white/70 hover:text-white hover:bg-white/10 transition-colors",
|
|
2964
|
+
"aria-label": t("BiChat.Image.ResetZoom"),
|
|
2965
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.ArrowsIn, { size: 16, weight: "bold" })
|
|
2966
|
+
}
|
|
2967
|
+
)
|
|
2968
|
+
] })
|
|
2969
|
+
] })
|
|
2970
|
+
]
|
|
2971
|
+
}
|
|
2972
|
+
)
|
|
2973
|
+
]
|
|
2974
|
+
}
|
|
2975
|
+
)
|
|
2807
2976
|
] });
|
|
2808
2977
|
}
|
|
2809
2978
|
var ImageModal_default = ImageModal;
|
|
@@ -2855,6 +3024,7 @@ function UserMessage({
|
|
|
2855
3024
|
const [draftContent, setDraftContent] = React.useState("");
|
|
2856
3025
|
const [isCopied, setIsCopied] = React.useState(false);
|
|
2857
3026
|
const copyFeedbackTimeoutRef = React.useRef(null);
|
|
3027
|
+
const editTextareaRef = React.useRef(null);
|
|
2858
3028
|
const classes = mergeClassNames(defaultClassNames, classNameOverrides);
|
|
2859
3029
|
React.useEffect(() => {
|
|
2860
3030
|
return () => {
|
|
@@ -2864,6 +3034,16 @@ function UserMessage({
|
|
|
2864
3034
|
}
|
|
2865
3035
|
};
|
|
2866
3036
|
}, []);
|
|
3037
|
+
React.useEffect(() => {
|
|
3038
|
+
if (isEditing && editTextareaRef.current) {
|
|
3039
|
+
const textarea = editTextareaRef.current;
|
|
3040
|
+
textarea.focus();
|
|
3041
|
+
textarea.selectionStart = textarea.value.length;
|
|
3042
|
+
textarea.selectionEnd = textarea.value.length;
|
|
3043
|
+
textarea.style.height = "auto";
|
|
3044
|
+
textarea.style.height = `${Math.min(textarea.scrollHeight, 300)}px`;
|
|
3045
|
+
}
|
|
3046
|
+
}, [isEditing]);
|
|
2867
3047
|
const normalizedAttachments = turn.attachments.map((attachment) => {
|
|
2868
3048
|
if (!attachment.mimeType.startsWith("image/")) {
|
|
2869
3049
|
return attachment;
|
|
@@ -2948,6 +3128,21 @@ function UserMessage({
|
|
|
2948
3128
|
onEdit(turnId, newContent);
|
|
2949
3129
|
setIsEditing(false);
|
|
2950
3130
|
}, [onEdit, turnId, draftContent, turn.content]);
|
|
3131
|
+
const handleEditKeyDown = React.useCallback((e) => {
|
|
3132
|
+
if (e.key === "Escape") {
|
|
3133
|
+
e.preventDefault();
|
|
3134
|
+
handleEditCancel();
|
|
3135
|
+
} else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
3136
|
+
e.preventDefault();
|
|
3137
|
+
handleEditSave();
|
|
3138
|
+
}
|
|
3139
|
+
}, [handleEditCancel, handleEditSave]);
|
|
3140
|
+
const handleDraftChange = React.useCallback((e) => {
|
|
3141
|
+
setDraftContent(e.target.value);
|
|
3142
|
+
const el = e.target;
|
|
3143
|
+
el.style.height = "auto";
|
|
3144
|
+
el.style.height = `${Math.min(el.scrollHeight, 300)}px`;
|
|
3145
|
+
}, []);
|
|
2951
3146
|
const handleNavigate = React.useCallback(
|
|
2952
3147
|
(direction) => {
|
|
2953
3148
|
if (selectedImageIndex === null) return;
|
|
@@ -2998,36 +3193,48 @@ function UserMessage({
|
|
|
2998
3193
|
}
|
|
2999
3194
|
)
|
|
3000
3195
|
) }),
|
|
3001
|
-
turn.content && /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes.bubble, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes.content, children: isEditing ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-
|
|
3196
|
+
turn.content && /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes.bubble, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes.content, children: isEditing ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
3002
3197
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3003
3198
|
"textarea",
|
|
3004
3199
|
{
|
|
3200
|
+
ref: editTextareaRef,
|
|
3005
3201
|
value: draftContent,
|
|
3006
|
-
onChange:
|
|
3007
|
-
|
|
3008
|
-
"
|
|
3202
|
+
onChange: handleDraftChange,
|
|
3203
|
+
onKeyDown: handleEditKeyDown,
|
|
3204
|
+
className: "w-full min-h-[60px] max-h-[300px] resize-none rounded-xl border border-white/20 bg-white/[0.08] px-3.5 py-2.5 text-sm text-white leading-relaxed outline-none focus:bg-white/[0.12] focus:border-white/30 focus:ring-1 focus:ring-white/20 transition-all duration-200",
|
|
3205
|
+
"aria-label": t("BiChat.Message.EditMessage"),
|
|
3206
|
+
rows: 1
|
|
3009
3207
|
}
|
|
3010
3208
|
),
|
|
3011
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-
|
|
3012
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3013
|
-
"
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3209
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
3210
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[11px] text-white/30 select-none hidden sm:inline", children: [
|
|
3211
|
+
"Esc \xB7 ",
|
|
3212
|
+
typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(
|
|
3213
|
+
navigator.userAgentData?.platform ?? navigator?.platform ?? ""
|
|
3214
|
+
) ? "\u2318" : "Ctrl",
|
|
3215
|
+
"+Enter"
|
|
3216
|
+
] }),
|
|
3217
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 ml-auto", children: [
|
|
3218
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3219
|
+
"button",
|
|
3220
|
+
{
|
|
3221
|
+
type: "button",
|
|
3222
|
+
onClick: handleEditCancel,
|
|
3223
|
+
className: "cursor-pointer px-3 py-1.5 rounded-lg text-white/60 hover:text-white hover:bg-white/10 transition-colors text-sm",
|
|
3224
|
+
children: t("BiChat.Message.Cancel")
|
|
3225
|
+
}
|
|
3226
|
+
),
|
|
3227
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3228
|
+
"button",
|
|
3229
|
+
{
|
|
3230
|
+
type: "button",
|
|
3231
|
+
onClick: handleEditSave,
|
|
3232
|
+
className: "cursor-pointer px-4 py-1.5 rounded-lg bg-white text-primary-700 font-medium text-sm hover:bg-white/90 transition-all shadow-sm disabled:opacity-40 disabled:cursor-not-allowed disabled:shadow-none",
|
|
3233
|
+
disabled: !draftContent.trim() || draftContent === turn.content,
|
|
3234
|
+
children: t("BiChat.Message.Save")
|
|
3235
|
+
}
|
|
3236
|
+
)
|
|
3237
|
+
] })
|
|
3031
3238
|
] })
|
|
3032
3239
|
] }) : renderSlot(slots?.content, contentSlotProps, turn.content) }) }),
|
|
3033
3240
|
!hideActions && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${classes.actions} ${isCopied ? "opacity-100" : ""}`, children: renderSlot(
|
|
@@ -3177,65 +3384,293 @@ var StreamingCursor_default = StreamingCursor;
|
|
|
3177
3384
|
|
|
3178
3385
|
// ui/src/bichat/components/AssistantMessage.tsx
|
|
3179
3386
|
init_ChartCard();
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3387
|
+
|
|
3388
|
+
// ui/src/bichat/components/InteractiveTableCard.tsx
|
|
3389
|
+
init_useTranslation();
|
|
3390
|
+
init_TableExportButton();
|
|
3391
|
+
var PAGE_SIZE_OPTIONS = [10, 25, 50, 100, 200];
|
|
3392
|
+
function formatCell(value) {
|
|
3393
|
+
if (value === null || value === void 0) return "NULL";
|
|
3394
|
+
if (typeof value === "object") {
|
|
3395
|
+
try {
|
|
3396
|
+
return JSON.stringify(value);
|
|
3397
|
+
} catch {
|
|
3398
|
+
return String(value);
|
|
3399
|
+
}
|
|
3183
3400
|
}
|
|
3184
|
-
return
|
|
3185
|
-
|
|
3401
|
+
return String(value);
|
|
3402
|
+
}
|
|
3403
|
+
var InteractiveTableCard = React.memo(function InteractiveTableCard2({
|
|
3404
|
+
table,
|
|
3405
|
+
onSendMessage,
|
|
3406
|
+
sendDisabled = false
|
|
3407
|
+
}) {
|
|
3408
|
+
const { t } = useTranslation();
|
|
3409
|
+
const defaultPageSize = Math.min(Math.max(table.pageSize || 25, 1), 200);
|
|
3410
|
+
const [page, setPage] = React.useState(1);
|
|
3411
|
+
const [pageSize, setPageSize] = React.useState(defaultPageSize);
|
|
3412
|
+
React.useEffect(() => {
|
|
3413
|
+
const nextPageSize = Math.min(Math.max(table.pageSize || 25, 1), 200);
|
|
3414
|
+
setPage(1);
|
|
3415
|
+
setPageSize(nextPageSize);
|
|
3416
|
+
}, [table.id, table.pageSize]);
|
|
3417
|
+
const totalRows = table.rows.length;
|
|
3418
|
+
const totalPages = Math.max(1, Math.ceil(totalRows / pageSize));
|
|
3419
|
+
React.useEffect(() => {
|
|
3420
|
+
if (page > totalPages) {
|
|
3421
|
+
setPage(totalPages);
|
|
3422
|
+
}
|
|
3423
|
+
}, [page, totalPages]);
|
|
3424
|
+
const pageRows = React.useMemo(() => {
|
|
3425
|
+
const start = (page - 1) * pageSize;
|
|
3426
|
+
return table.rows.slice(start, start + pageSize);
|
|
3427
|
+
}, [page, pageSize, table.rows]);
|
|
3428
|
+
const pageSizeOptions = React.useMemo(() => {
|
|
3429
|
+
const set = /* @__PURE__ */ new Set([...PAGE_SIZE_OPTIONS, defaultPageSize]);
|
|
3430
|
+
return [...set].sort((a, b) => a - b);
|
|
3431
|
+
}, [defaultPageSize]);
|
|
3432
|
+
const canExportViaPrompt = !!onSendMessage && !!table.exportPrompt;
|
|
3433
|
+
const exportDisabled = sendDisabled || !table.export?.url && !canExportViaPrompt;
|
|
3434
|
+
const handleExport = React.useCallback(() => {
|
|
3435
|
+
if (table.export?.url) {
|
|
3436
|
+
try {
|
|
3437
|
+
const parsed = new URL(table.export.url, window.location.origin);
|
|
3438
|
+
if (!["http:", "https:", "blob:"].includes(parsed.protocol)) {
|
|
3439
|
+
console.warn("[InteractiveTableCard] Blocked export URL with unsafe protocol:", parsed.protocol);
|
|
3440
|
+
return;
|
|
3441
|
+
}
|
|
3442
|
+
} catch {
|
|
3443
|
+
console.warn("[InteractiveTableCard] Blocked malformed export URL");
|
|
3444
|
+
return;
|
|
3445
|
+
}
|
|
3446
|
+
const link = document.createElement("a");
|
|
3447
|
+
link.href = table.export.url;
|
|
3448
|
+
link.download = table.export.filename || "table_export.xlsx";
|
|
3449
|
+
link.rel = "noopener noreferrer";
|
|
3450
|
+
document.body.appendChild(link);
|
|
3451
|
+
link.click();
|
|
3452
|
+
document.body.removeChild(link);
|
|
3453
|
+
return;
|
|
3454
|
+
}
|
|
3455
|
+
if (canExportViaPrompt && table.exportPrompt) {
|
|
3456
|
+
onSendMessage?.(table.exportPrompt);
|
|
3457
|
+
}
|
|
3458
|
+
}, [canExportViaPrompt, onSendMessage, table.export, table.exportPrompt]);
|
|
3459
|
+
const from = totalRows === 0 ? 0 : (page - 1) * pageSize + 1;
|
|
3460
|
+
const to = Math.min(page * pageSize, totalRows);
|
|
3461
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "w-full rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900/40", children: [
|
|
3462
|
+
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex flex-wrap items-center justify-between gap-2 border-b border-gray-200 dark:border-gray-700 px-3 py-2", children: [
|
|
3463
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
|
|
3464
|
+
/* @__PURE__ */ jsxRuntime.jsx("h4", { className: "truncate text-sm font-semibold text-gray-900 dark:text-gray-100", children: table.title || t("BiChat.Table.QueryResults") }),
|
|
3465
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: [
|
|
3466
|
+
totalRows === 1 ? t("BiChat.Table.OneRowLoaded") : t("BiChat.Table.RowsLoaded", { count: String(totalRows) }),
|
|
3467
|
+
table.truncated ? ` ${t("BiChat.Table.TruncatedSuffix")}` : ""
|
|
3468
|
+
] })
|
|
3469
|
+
] }),
|
|
3186
3470
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3187
|
-
|
|
3471
|
+
exports.TableExportButton,
|
|
3188
3472
|
{
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3194
|
-
"path",
|
|
3195
|
-
{
|
|
3196
|
-
strokeLinecap: "round",
|
|
3197
|
-
strokeLinejoin: "round",
|
|
3198
|
-
strokeWidth: 2,
|
|
3199
|
-
d: "M9 5l7 7-7 7"
|
|
3200
|
-
}
|
|
3201
|
-
)
|
|
3473
|
+
onClick: handleExport,
|
|
3474
|
+
disabled: exportDisabled,
|
|
3475
|
+
label: t("BiChat.Table.ExportToExcel"),
|
|
3476
|
+
disabledTooltip: sendDisabled ? t("BiChat.Table.PleaseWait") : t("BiChat.Table.ExportUnavailable")
|
|
3202
3477
|
}
|
|
3203
|
-
)
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3478
|
+
)
|
|
3479
|
+
] }),
|
|
3480
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-[420px] overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full border-collapse text-sm", children: [
|
|
3481
|
+
/* @__PURE__ */ jsxRuntime.jsx("thead", { className: "sticky top-0 bg-gray-100 dark:bg-gray-800 z-10", children: /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b border-gray-200 dark:border-gray-700", children: table.headers.map((header, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3482
|
+
"th",
|
|
3483
|
+
{
|
|
3484
|
+
className: "px-3 py-2 text-left font-semibold text-gray-700 dark:text-gray-200 whitespace-nowrap",
|
|
3485
|
+
children: header
|
|
3486
|
+
},
|
|
3487
|
+
`${table.id}-header-${index}`
|
|
3488
|
+
)) }) }),
|
|
3489
|
+
/* @__PURE__ */ jsxRuntime.jsxs("tbody", { children: [
|
|
3490
|
+
pageRows.map((row, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3491
|
+
"tr",
|
|
3492
|
+
{
|
|
3493
|
+
className: "border-b border-gray-100 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800/40",
|
|
3494
|
+
children: table.columns.map((_, columnIndex) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3495
|
+
"td",
|
|
3496
|
+
{
|
|
3497
|
+
className: "px-3 py-2 text-gray-700 dark:text-gray-300 align-top",
|
|
3498
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block max-w-[420px] truncate", title: formatCell(row[columnIndex]), children: formatCell(row[columnIndex]) })
|
|
3499
|
+
},
|
|
3500
|
+
`${table.id}-cell-${rowIndex}-${columnIndex}`
|
|
3501
|
+
))
|
|
3502
|
+
},
|
|
3503
|
+
`${table.id}-row-${rowIndex}`
|
|
3504
|
+
)),
|
|
3505
|
+
pageRows.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3506
|
+
"td",
|
|
3507
|
+
{
|
|
3508
|
+
colSpan: table.columns.length,
|
|
3509
|
+
className: "px-3 py-6 text-center text-sm text-gray-500 dark:text-gray-400",
|
|
3510
|
+
children: t("BiChat.Table.NoRows")
|
|
3511
|
+
}
|
|
3512
|
+
) })
|
|
3513
|
+
] })
|
|
3514
|
+
] }) }),
|
|
3515
|
+
/* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "flex flex-wrap items-center justify-between gap-2 border-t border-gray-200 dark:border-gray-700 px-3 py-2", children: [
|
|
3516
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: t("BiChat.Table.Showing", {
|
|
3517
|
+
from: String(from),
|
|
3518
|
+
to: String(to),
|
|
3519
|
+
total: String(totalRows)
|
|
3520
|
+
}) }),
|
|
3521
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3522
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs text-gray-500 dark:text-gray-400", htmlFor: `${table.id}-page-size`, children: t("BiChat.Table.RowsLabel") }),
|
|
3523
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3524
|
+
"select",
|
|
3525
|
+
{
|
|
3526
|
+
id: `${table.id}-page-size`,
|
|
3527
|
+
value: pageSize,
|
|
3528
|
+
onChange: (event) => {
|
|
3529
|
+
setPageSize(Number(event.target.value));
|
|
3530
|
+
setPage(1);
|
|
3531
|
+
},
|
|
3532
|
+
className: "rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-2 py-1 text-xs text-gray-700 dark:text-gray-200",
|
|
3533
|
+
children: pageSizeOptions.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option, children: option }, `${table.id}-size-${option}`))
|
|
3534
|
+
}
|
|
3535
|
+
),
|
|
3536
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3537
|
+
"button",
|
|
3538
|
+
{
|
|
3539
|
+
type: "button",
|
|
3540
|
+
onClick: () => setPage((current) => Math.max(1, current - 1)),
|
|
3541
|
+
disabled: page <= 1,
|
|
3542
|
+
className: "cursor-pointer rounded border border-gray-300 dark:border-gray-600 px-2 py-1 text-xs text-gray-700 dark:text-gray-200 disabled:cursor-not-allowed disabled:opacity-50",
|
|
3543
|
+
children: t("BiChat.Table.Prev")
|
|
3544
|
+
}
|
|
3545
|
+
),
|
|
3546
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: t("BiChat.Table.PageOf", { page: String(page), total: String(totalPages) }) }),
|
|
3547
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3548
|
+
"button",
|
|
3549
|
+
{
|
|
3550
|
+
type: "button",
|
|
3551
|
+
onClick: () => setPage((current) => Math.min(totalPages, current + 1)),
|
|
3552
|
+
disabled: page >= totalPages,
|
|
3553
|
+
className: "cursor-pointer rounded border border-gray-300 dark:border-gray-600 px-2 py-1 text-xs text-gray-700 dark:text-gray-200 disabled:cursor-not-allowed disabled:opacity-50",
|
|
3554
|
+
children: t("BiChat.Table.Next")
|
|
3555
|
+
}
|
|
3556
|
+
)
|
|
3208
3557
|
] })
|
|
3209
3558
|
] }),
|
|
3210
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3211
|
-
|
|
3559
|
+
table.truncated && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "border-t border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-700 dark:border-amber-700/60 dark:bg-amber-900/20 dark:text-amber-300", children: t("BiChat.Table.TruncatedNotice") })
|
|
3560
|
+
] });
|
|
3561
|
+
});
|
|
3562
|
+
|
|
3563
|
+
// ui/src/bichat/components/SourcesPanel.tsx
|
|
3564
|
+
init_useTranslation();
|
|
3565
|
+
function extractDomain(url) {
|
|
3566
|
+
try {
|
|
3567
|
+
return new URL(url).hostname.replace(/^www\./, "");
|
|
3568
|
+
} catch {
|
|
3569
|
+
return "";
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
var PALETTE = [
|
|
3573
|
+
"#c0392b",
|
|
3574
|
+
"#d35400",
|
|
3575
|
+
"#f39c12",
|
|
3576
|
+
"#27ae60",
|
|
3577
|
+
"#16a085",
|
|
3578
|
+
"#2980b9",
|
|
3579
|
+
"#8e44ad",
|
|
3580
|
+
"#d63384"
|
|
3581
|
+
];
|
|
3582
|
+
function domainColor(domain) {
|
|
3583
|
+
let h = 0;
|
|
3584
|
+
for (let i = 0; i < domain.length; i++) h = domain.charCodeAt(i) + ((h << 5) - h);
|
|
3585
|
+
return PALETTE[Math.abs(h) % PALETTE.length];
|
|
3586
|
+
}
|
|
3587
|
+
function SourcesPanel({ citations }) {
|
|
3588
|
+
const { t } = useTranslation();
|
|
3589
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
3590
|
+
const open = React.useCallback(() => setIsOpen(true), []);
|
|
3591
|
+
const close = React.useCallback(() => setIsOpen(false), []);
|
|
3592
|
+
if (!citations?.length) return null;
|
|
3593
|
+
const domains = [...new Set(
|
|
3594
|
+
citations.filter((c) => c.url).map((c) => extractDomain(c.url)).filter(Boolean)
|
|
3595
|
+
)];
|
|
3596
|
+
const previewDomains = domains.slice(0, 5);
|
|
3597
|
+
if (!isOpen) {
|
|
3598
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3599
|
+
"button",
|
|
3212
3600
|
{
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
'"'
|
|
3232
|
-
] })
|
|
3601
|
+
type: "button",
|
|
3602
|
+
onClick: open,
|
|
3603
|
+
className: "cursor-pointer inline-flex items-center gap-2 rounded-full px-3 py-1.5\n bg-gray-50 hover:bg-gray-100 dark:bg-gray-700/50 dark:hover:bg-gray-600/60\n border border-gray-200/70 dark:border-gray-600/40\n transition-colors duration-150\n focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--bichat-primary,theme(colors.blue.500))]/40",
|
|
3604
|
+
children: [
|
|
3605
|
+
previewDomains.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex -space-x-1.5", children: previewDomains.map((domain, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3606
|
+
"span",
|
|
3607
|
+
{
|
|
3608
|
+
className: "relative w-5 h-5 rounded-full flex items-center justify-center text-[8px] font-bold text-white\n ring-2 ring-white dark:ring-gray-800 select-none",
|
|
3609
|
+
style: { backgroundColor: domainColor(domain), zIndex: previewDomains.length - i },
|
|
3610
|
+
"aria-hidden": "true",
|
|
3611
|
+
children: domain[0]?.toUpperCase()
|
|
3612
|
+
},
|
|
3613
|
+
domain
|
|
3614
|
+
)) }),
|
|
3615
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-medium text-gray-600 dark:text-gray-300 tabular-nums", children: [
|
|
3616
|
+
citations.length,
|
|
3617
|
+
" ",
|
|
3618
|
+
t(citations.length === 1 ? "BiChat.Sources.Source" : "BiChat.Sources.Sources")
|
|
3233
3619
|
] })
|
|
3620
|
+
]
|
|
3621
|
+
}
|
|
3622
|
+
) });
|
|
3623
|
+
}
|
|
3624
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800/90 shadow-sm overflow-hidden", children: [
|
|
3625
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-3", children: [
|
|
3626
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: t("BiChat.Sources.Title") }),
|
|
3627
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3628
|
+
"button",
|
|
3629
|
+
{
|
|
3630
|
+
type: "button",
|
|
3631
|
+
onClick: close,
|
|
3632
|
+
className: "cursor-pointer flex items-center justify-center w-7 h-7 rounded-full\n text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300\n hover:bg-gray-100 dark:hover:bg-gray-700\n transition-colors duration-150\n focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--bichat-primary)]/40",
|
|
3633
|
+
"aria-label": t("BiChat.Sources.Close"),
|
|
3634
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.X, { size: 14, weight: "bold" })
|
|
3635
|
+
}
|
|
3636
|
+
)
|
|
3637
|
+
] }),
|
|
3638
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-80 overflow-y-auto", children: citations.map((citation, index) => {
|
|
3639
|
+
const domain = citation.url ? extractDomain(citation.url) : "";
|
|
3640
|
+
const cardContent = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3641
|
+
/* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-sm font-medium leading-snug text-[var(--bichat-color-accent,theme(colors.blue.600))] dark:text-blue-400", children: citation.title || t("BiChat.Sources.SourceN", { n: String(index + 1) }) }),
|
|
3642
|
+
citation.excerpt && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-xs text-gray-500 dark:text-gray-400 line-clamp-2 leading-relaxed", children: citation.excerpt }),
|
|
3643
|
+
domain && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 mt-1.5", children: [
|
|
3644
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3645
|
+
"span",
|
|
3646
|
+
{
|
|
3647
|
+
className: "w-4 h-4 rounded-full flex items-center justify-center text-[7px] font-bold text-white flex-shrink-0 select-none",
|
|
3648
|
+
style: { backgroundColor: domainColor(domain) },
|
|
3649
|
+
"aria-hidden": "true",
|
|
3650
|
+
children: domain[0]?.toUpperCase()
|
|
3651
|
+
}
|
|
3652
|
+
),
|
|
3653
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] text-gray-400 dark:text-gray-500 truncate", children: domain })
|
|
3234
3654
|
] })
|
|
3235
|
-
}
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3655
|
+
] });
|
|
3656
|
+
const cardClass = "block px-4 py-3 border-t border-gray-100 dark:border-gray-700/50";
|
|
3657
|
+
if (citation.url) {
|
|
3658
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3659
|
+
"a",
|
|
3660
|
+
{
|
|
3661
|
+
href: citation.url,
|
|
3662
|
+
target: "_blank",
|
|
3663
|
+
rel: "noopener noreferrer",
|
|
3664
|
+
className: `${cardClass} hover:bg-gray-50 dark:hover:bg-gray-700/40 transition-colors duration-100
|
|
3665
|
+
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--bichat-primary)]/40`,
|
|
3666
|
+
children: cardContent
|
|
3667
|
+
},
|
|
3668
|
+
citation.id
|
|
3669
|
+
);
|
|
3670
|
+
}
|
|
3671
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cardClass, children: cardContent }, citation.id);
|
|
3672
|
+
}) })
|
|
3673
|
+
] });
|
|
3239
3674
|
}
|
|
3240
3675
|
var MIME_BY_TYPE = {
|
|
3241
3676
|
excel: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
@@ -3296,7 +3731,7 @@ function InlineQuestionForm({ pendingQuestion }) {
|
|
|
3296
3731
|
const [currentStep, setCurrentStep] = React.useState(0);
|
|
3297
3732
|
const [answers, setAnswers] = React.useState({});
|
|
3298
3733
|
const [otherTexts, setOtherTexts] = React.useState({});
|
|
3299
|
-
const questions = pendingQuestion.questions;
|
|
3734
|
+
const questions = Array.isArray(pendingQuestion.questions) ? pendingQuestion.questions : [];
|
|
3300
3735
|
const currentQuestion = questions[currentStep];
|
|
3301
3736
|
const isLastStep = currentStep === questions.length - 1;
|
|
3302
3737
|
const isFirstStep = currentStep === 0;
|
|
@@ -3390,9 +3825,31 @@ function InlineQuestionForm({ pendingQuestion }) {
|
|
|
3390
3825
|
e.preventDefault();
|
|
3391
3826
|
handleNext();
|
|
3392
3827
|
};
|
|
3393
|
-
if (!currentQuestion)
|
|
3828
|
+
if (!currentQuestion) {
|
|
3829
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "animate-slide-up rounded-2xl border border-amber-200 dark:border-amber-700/50 bg-gradient-to-b from-amber-50/70 to-white dark:from-amber-950/20 dark:to-gray-900/80 shadow-sm overflow-hidden p-4", children: [
|
|
3830
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-gray-800 dark:text-gray-200", children: t("BiChat.Error.SomethingWentWrong") }),
|
|
3831
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: t("BiChat.Error.UnexpectedError") }),
|
|
3832
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3833
|
+
"button",
|
|
3834
|
+
{
|
|
3835
|
+
type: "button",
|
|
3836
|
+
onClick: handleRejectPendingQuestion,
|
|
3837
|
+
disabled: loading,
|
|
3838
|
+
className: "cursor-pointer inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-lg bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-40",
|
|
3839
|
+
children: [
|
|
3840
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.X, { size: 14, weight: "bold" }),
|
|
3841
|
+
t("BiChat.InlineQuestion.Dismiss")
|
|
3842
|
+
]
|
|
3843
|
+
}
|
|
3844
|
+
) })
|
|
3845
|
+
] });
|
|
3846
|
+
}
|
|
3394
3847
|
const isMultiSelect = currentQuestion.type === "MULTIPLE_CHOICE";
|
|
3395
|
-
const options = currentQuestion.options || []
|
|
3848
|
+
const options = (currentQuestion.options || []).filter((option) => Boolean(option && typeof option.label === "string")).map((option, index) => ({
|
|
3849
|
+
id: option.id || `${currentQuestion.id}-option-${index}`,
|
|
3850
|
+
label: option.label,
|
|
3851
|
+
value: option.value || option.label
|
|
3852
|
+
}));
|
|
3396
3853
|
const isOtherSelected = currentAnswer?.customText !== void 0;
|
|
3397
3854
|
const canProceed = isCurrentAnswerValid();
|
|
3398
3855
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-slide-up rounded-2xl border border-gray-200 dark:border-gray-700/50 bg-gradient-to-b from-primary-50/80 to-white dark:from-primary-950/30 dark:to-gray-900/80 shadow-sm overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, children: [
|
|
@@ -3565,6 +4022,59 @@ function InlineQuestionForm({ pendingQuestion }) {
|
|
|
3565
4022
|
] }) });
|
|
3566
4023
|
}
|
|
3567
4024
|
|
|
4025
|
+
// ui/src/bichat/components/RetryActionArea.tsx
|
|
4026
|
+
init_useTranslation();
|
|
4027
|
+
var RetryActionArea = React.memo(function RetryActionArea2({
|
|
4028
|
+
onRetry
|
|
4029
|
+
}) {
|
|
4030
|
+
const { t } = useTranslation();
|
|
4031
|
+
return (
|
|
4032
|
+
// Wrapper matches TurnBubble layout for assistant messages (justify-start = left-aligned)
|
|
4033
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4034
|
+
framerMotion.motion.div,
|
|
4035
|
+
{
|
|
4036
|
+
initial: { opacity: 0, y: 10 },
|
|
4037
|
+
animate: { opacity: 1, y: 0 },
|
|
4038
|
+
exit: { opacity: 0, y: -10 },
|
|
4039
|
+
transition: { duration: 0.2 },
|
|
4040
|
+
className: "flex justify-start",
|
|
4041
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4042
|
+
"div",
|
|
4043
|
+
{
|
|
4044
|
+
className: "flex flex-col gap-3 max-w-2xl rounded-2xl px-5 py-3 shadow-sm bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700",
|
|
4045
|
+
role: "status",
|
|
4046
|
+
"aria-live": "polite",
|
|
4047
|
+
children: [
|
|
4048
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
4049
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4050
|
+
react.Warning,
|
|
4051
|
+
{
|
|
4052
|
+
className: "w-5 h-5 text-amber-500 dark:text-amber-400 flex-shrink-0",
|
|
4053
|
+
weight: "fill"
|
|
4054
|
+
}
|
|
4055
|
+
),
|
|
4056
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: t("BiChat.Retry.Subtitle") })
|
|
4057
|
+
] }),
|
|
4058
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4059
|
+
"button",
|
|
4060
|
+
{
|
|
4061
|
+
onClick: onRetry,
|
|
4062
|
+
className: "cursor-pointer inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-white bg-primary-600 hover:bg-primary-700 dark:bg-primary-700 dark:hover:bg-primary-600 rounded-lg transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-800",
|
|
4063
|
+
"aria-label": t("BiChat.Retry.Title"),
|
|
4064
|
+
children: [
|
|
4065
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.ArrowClockwise, { size: 16, className: "w-4 h-4" }),
|
|
4066
|
+
t("BiChat.Retry.Button")
|
|
4067
|
+
]
|
|
4068
|
+
}
|
|
4069
|
+
) })
|
|
4070
|
+
]
|
|
4071
|
+
}
|
|
4072
|
+
)
|
|
4073
|
+
}
|
|
4074
|
+
)
|
|
4075
|
+
);
|
|
4076
|
+
});
|
|
4077
|
+
|
|
3568
4078
|
// ui/src/bichat/utils/debugMetrics.ts
|
|
3569
4079
|
function formatGenerationDuration(generationMs) {
|
|
3570
4080
|
return generationMs > 1e3 ? `${(generationMs / 1e3).toFixed(2)}s` : `${generationMs}ms`;
|
|
@@ -3863,6 +4373,7 @@ var defaultClassNames2 = {
|
|
|
3863
4373
|
bubble: "bg-white dark:bg-gray-800 rounded-2xl rounded-bl-sm px-4 py-3 shadow-sm",
|
|
3864
4374
|
codeOutputs: "",
|
|
3865
4375
|
charts: "mb-1 w-full",
|
|
4376
|
+
tables: "mb-1 flex flex-col gap-3",
|
|
3866
4377
|
artifacts: "mb-1 flex flex-wrap gap-2",
|
|
3867
4378
|
sources: "",
|
|
3868
4379
|
explanation: "mt-4 border-t border-gray-100 dark:border-gray-700 pt-4",
|
|
@@ -3879,6 +4390,7 @@ function mergeClassNames2(defaults, overrides) {
|
|
|
3879
4390
|
bubble: overrides.bubble ?? defaults.bubble,
|
|
3880
4391
|
codeOutputs: overrides.codeOutputs ?? defaults.codeOutputs,
|
|
3881
4392
|
charts: overrides.charts ?? defaults.charts,
|
|
4393
|
+
tables: overrides.tables ?? defaults.tables,
|
|
3882
4394
|
artifacts: overrides.artifacts ?? defaults.artifacts,
|
|
3883
4395
|
sources: overrides.sources ?? defaults.sources,
|
|
3884
4396
|
explanation: overrides.explanation ?? defaults.explanation,
|
|
@@ -3923,6 +4435,14 @@ function AssistantMessage({
|
|
|
3923
4435
|
const hasContent = turn.content?.trim().length > 0;
|
|
3924
4436
|
const hasExplanation = !!turn.explanation?.trim();
|
|
3925
4437
|
const hasPendingQuestion = !!pendingQuestion && pendingQuestion.status === "PENDING" && pendingQuestion.turnId === turnId;
|
|
4438
|
+
const hasCodeOutputs = !!turn.codeOutputs?.length;
|
|
4439
|
+
const hasChart = !!turn.chartData;
|
|
4440
|
+
const hasTables = !!turn.renderTables?.length;
|
|
4441
|
+
const hasArtifacts = !!turn.artifacts?.length;
|
|
4442
|
+
const hasDebug = showDebug && !!turn.debug;
|
|
4443
|
+
const hasRenderablePayload = hasContent || hasExplanation || hasPendingQuestion || hasCodeOutputs || hasChart || hasTables || hasArtifacts || hasDebug;
|
|
4444
|
+
const canRegenerate = !!onRegenerate && !!turnId && !isSystemMessage && isLastTurn;
|
|
4445
|
+
const showInlineRetry = !hasRenderablePayload && canRegenerate;
|
|
3926
4446
|
const handleCopyClick = React.useCallback(async () => {
|
|
3927
4447
|
try {
|
|
3928
4448
|
if (onCopy) {
|
|
@@ -3964,15 +4484,18 @@ function AssistantMessage({
|
|
|
3964
4484
|
const codeOutputsSlotProps = {
|
|
3965
4485
|
outputs: turn.codeOutputs || []
|
|
3966
4486
|
};
|
|
4487
|
+
const tablesSlotProps = {
|
|
4488
|
+
tables: turn.renderTables || []
|
|
4489
|
+
};
|
|
3967
4490
|
const artifactsSlotProps = {
|
|
3968
4491
|
artifacts: turn.artifacts || []
|
|
3969
4492
|
};
|
|
3970
4493
|
const actionsSlotProps = {
|
|
3971
4494
|
onCopy: handleCopyClick,
|
|
3972
|
-
onRegenerate:
|
|
4495
|
+
onRegenerate: canRegenerate ? handleRegenerateClick : void 0,
|
|
3973
4496
|
timestamp,
|
|
3974
4497
|
canCopy: hasContent,
|
|
3975
|
-
canRegenerate
|
|
4498
|
+
canRegenerate
|
|
3976
4499
|
};
|
|
3977
4500
|
const explanationSlotProps = {
|
|
3978
4501
|
explanation: turn.explanation || "",
|
|
@@ -3985,14 +4508,30 @@ function AssistantMessage({
|
|
|
3985
4508
|
return slot;
|
|
3986
4509
|
};
|
|
3987
4510
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classes.root, children: [
|
|
3988
|
-
!hideAvatar && /* @__PURE__ */ jsxRuntime.jsx("div", { className: avatarClassName, children: renderSlot(slots?.avatar, avatarSlotProps, isSystemMessage ? "SYS" : "AI") }),
|
|
4511
|
+
!hideAvatar && !showInlineRetry && /* @__PURE__ */ jsxRuntime.jsx("div", { className: avatarClassName, children: renderSlot(slots?.avatar, avatarSlotProps, isSystemMessage ? "SYS" : "AI") }),
|
|
3989
4512
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: classes.wrapper, children: [
|
|
4513
|
+
showInlineRetry && /* @__PURE__ */ jsxRuntime.jsx(RetryActionArea, { onRetry: () => {
|
|
4514
|
+
void handleRegenerateClick();
|
|
4515
|
+
} }),
|
|
3990
4516
|
turn.codeOutputs && turn.codeOutputs.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes.codeOutputs, children: renderSlot(
|
|
3991
4517
|
slots?.codeOutputs,
|
|
3992
4518
|
codeOutputsSlotProps,
|
|
3993
4519
|
/* @__PURE__ */ jsxRuntime.jsx(CodeOutputsPanel_default, { outputs: turn.codeOutputs })
|
|
3994
4520
|
) }),
|
|
3995
4521
|
turn.chartData && /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes.charts, children: renderSlot(slots?.charts, chartsSlotProps, /* @__PURE__ */ jsxRuntime.jsx(ChartCard, { chartData: turn.chartData })) }),
|
|
4522
|
+
turn.renderTables && turn.renderTables.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes.tables, children: renderSlot(
|
|
4523
|
+
slots?.tables,
|
|
4524
|
+
tablesSlotProps,
|
|
4525
|
+
turn.renderTables.map((table) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4526
|
+
InteractiveTableCard,
|
|
4527
|
+
{
|
|
4528
|
+
table,
|
|
4529
|
+
onSendMessage,
|
|
4530
|
+
sendDisabled: sendDisabled || isStreaming
|
|
4531
|
+
},
|
|
4532
|
+
table.id
|
|
4533
|
+
))
|
|
4534
|
+
) }),
|
|
3996
4535
|
hasContent && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: bubbleClassName, children: [
|
|
3997
4536
|
renderSlot(
|
|
3998
4537
|
slots?.content,
|
|
@@ -4082,7 +4621,7 @@ function AssistantMessage({
|
|
|
4082
4621
|
children: isCopied ? /* @__PURE__ */ jsxRuntime.jsx(react.Check, { size: 14, weight: "bold" }) : /* @__PURE__ */ jsxRuntime.jsx(react.Copy, { size: 14, weight: "regular" })
|
|
4083
4622
|
}
|
|
4084
4623
|
),
|
|
4085
|
-
|
|
4624
|
+
canRegenerate && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4086
4625
|
"button",
|
|
4087
4626
|
{
|
|
4088
4627
|
onClick: handleRegenerateClick,
|
|
@@ -4306,7 +4845,8 @@ function TurnBubble({
|
|
|
4306
4845
|
userTurn: classNames?.userTurn ?? defaultClassNames3.userTurn,
|
|
4307
4846
|
assistantTurn: classNames?.assistantTurn ?? defaultClassNames3.assistantTurn
|
|
4308
4847
|
};
|
|
4309
|
-
const
|
|
4848
|
+
const userContent = typeof turn.userTurn?.content === "string" ? turn.userTurn.content : "";
|
|
4849
|
+
const isSystemSummaryTurn = userContent.trim() === "" && turn.assistantTurn?.role === "system";
|
|
4310
4850
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classes.root, "data-turn-id": turn.id, children: [
|
|
4311
4851
|
!isSystemSummaryTurn && /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes.userTurn, children: renderUserTurn ? renderUserTurn(turn) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
4312
4852
|
UserTurnView,
|
|
@@ -8084,12 +8624,63 @@ function DefaultErrorContent({
|
|
|
8084
8624
|
] })
|
|
8085
8625
|
] });
|
|
8086
8626
|
}
|
|
8627
|
+
function StaticEmergencyErrorContent({
|
|
8628
|
+
error,
|
|
8629
|
+
onReset
|
|
8630
|
+
}) {
|
|
8631
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col items-center justify-center p-8 text-center min-h-[200px]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-col items-center", children: [
|
|
8632
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative mb-5", children: [
|
|
8633
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 rounded-full bg-red-100 scale-150 blur-md" }),
|
|
8634
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex items-center justify-center w-14 h-14 rounded-full bg-red-50 border border-red-200/60", children: /* @__PURE__ */ jsxRuntime.jsx(react.WarningCircle, { size: 28, className: "text-red-500", weight: "fill" }) })
|
|
8635
|
+
] }),
|
|
8636
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-gray-900 mb-1.5", children: "Something went wrong" }),
|
|
8637
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mb-5 max-w-md leading-relaxed", children: error?.message || "An unexpected UI error occurred." }),
|
|
8638
|
+
onReset && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8639
|
+
"button",
|
|
8640
|
+
{
|
|
8641
|
+
type: "button",
|
|
8642
|
+
onClick: onReset,
|
|
8643
|
+
className: "flex items-center gap-2 px-5 py-2.5 bg-red-600 hover:bg-red-700 active:bg-red-800 text-white rounded-lg transition-colors shadow-sm text-sm font-medium",
|
|
8644
|
+
children: [
|
|
8645
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.ArrowClockwise, { size: 16, weight: "bold" }),
|
|
8646
|
+
"Try again"
|
|
8647
|
+
]
|
|
8648
|
+
}
|
|
8649
|
+
)
|
|
8650
|
+
] }) });
|
|
8651
|
+
}
|
|
8652
|
+
var FallbackGuard = class extends React.Component {
|
|
8653
|
+
constructor(props) {
|
|
8654
|
+
super(props);
|
|
8655
|
+
this.state = { fallbackFailed: false };
|
|
8656
|
+
}
|
|
8657
|
+
static getDerivedStateFromError() {
|
|
8658
|
+
return { fallbackFailed: true };
|
|
8659
|
+
}
|
|
8660
|
+
componentDidCatch(error, errorInfo) {
|
|
8661
|
+
this.props.onFallbackError?.(error, errorInfo);
|
|
8662
|
+
}
|
|
8663
|
+
render() {
|
|
8664
|
+
if (this.state.fallbackFailed) {
|
|
8665
|
+
return /* @__PURE__ */ jsxRuntime.jsx(StaticEmergencyErrorContent, { error: this.props.primaryError, onReset: this.props.onReset });
|
|
8666
|
+
}
|
|
8667
|
+
return this.props.renderFallback();
|
|
8668
|
+
}
|
|
8669
|
+
};
|
|
8087
8670
|
var ErrorBoundary = class extends React.Component {
|
|
8088
8671
|
constructor(props) {
|
|
8089
8672
|
super(props);
|
|
8090
8673
|
this.handleReset = () => {
|
|
8091
8674
|
this.setState({ hasError: false, error: null });
|
|
8092
8675
|
};
|
|
8676
|
+
this.handleFallbackError = (error, errorInfo) => {
|
|
8677
|
+
console.error("React Error Boundary fallback crashed:", {
|
|
8678
|
+
primaryError: this.state.error,
|
|
8679
|
+
fallbackError: error,
|
|
8680
|
+
errorInfo
|
|
8681
|
+
});
|
|
8682
|
+
this.props.onError?.(error, errorInfo);
|
|
8683
|
+
};
|
|
8093
8684
|
this.state = { hasError: false, error: null };
|
|
8094
8685
|
}
|
|
8095
8686
|
static getDerivedStateFromError(error) {
|
|
@@ -8101,13 +8692,24 @@ var ErrorBoundary = class extends React.Component {
|
|
|
8101
8692
|
}
|
|
8102
8693
|
render() {
|
|
8103
8694
|
if (this.state.hasError) {
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8695
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
8696
|
+
FallbackGuard,
|
|
8697
|
+
{
|
|
8698
|
+
primaryError: this.state.error,
|
|
8699
|
+
onReset: this.handleReset,
|
|
8700
|
+
onFallbackError: this.handleFallbackError,
|
|
8701
|
+
renderFallback: () => {
|
|
8702
|
+
if (this.props.fallback) {
|
|
8703
|
+
if (typeof this.props.fallback === "function") {
|
|
8704
|
+
return this.props.fallback(this.state.error, this.handleReset);
|
|
8705
|
+
}
|
|
8706
|
+
return this.props.fallback;
|
|
8707
|
+
}
|
|
8708
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DefaultErrorContent, { error: this.state.error, onReset: this.handleReset });
|
|
8709
|
+
}
|
|
8710
|
+
},
|
|
8711
|
+
`${this.state.error?.name ?? "Error"}:${this.state.error?.message ?? ""}`
|
|
8712
|
+
);
|
|
8111
8713
|
}
|
|
8112
8714
|
return this.props.children;
|
|
8113
8715
|
}
|
|
@@ -9141,6 +9743,10 @@ function ErrorAlert({ error }) {
|
|
|
9141
9743
|
);
|
|
9142
9744
|
}
|
|
9143
9745
|
var COLLAPSE_STORAGE_KEY = "bichat-sidebar-collapsed";
|
|
9746
|
+
var SESSION_RECONCILE_POLL_INTERVAL_MS = 2e3;
|
|
9747
|
+
var SESSION_RECONCILE_MAX_POLLS = 30;
|
|
9748
|
+
var ACTIVE_SESSION_MISS_MAX_RETRIES = 8;
|
|
9749
|
+
var ACTIVE_SESSION_MISS_RETRY_DELAY_MS = 1e3;
|
|
9144
9750
|
function useSidebarCollapse() {
|
|
9145
9751
|
const [isCollapsed, setIsCollapsed] = React.useState(() => {
|
|
9146
9752
|
try {
|
|
@@ -9198,7 +9804,7 @@ function Sidebar2({
|
|
|
9198
9804
|
const shouldReduceMotion = framerMotion.useReducedMotion();
|
|
9199
9805
|
const sessionListRef = React.useRef(null);
|
|
9200
9806
|
const searchContainerRef = React.useRef(null);
|
|
9201
|
-
const
|
|
9807
|
+
const activeSessionMissRetriesRef = React.useRef({});
|
|
9202
9808
|
const { isCollapsed, toggle, collapse } = useSidebarCollapse();
|
|
9203
9809
|
const collapsible = !onClose;
|
|
9204
9810
|
const handleSidebarClick = React.useCallback(
|
|
@@ -9242,6 +9848,7 @@ function Sidebar2({
|
|
|
9242
9848
|
const [actionError, setActionError] = React.useState(null);
|
|
9243
9849
|
const accessDenied = loadError?.isPermissionDenied === true;
|
|
9244
9850
|
const [refreshKey, setRefreshKey] = React.useState(0);
|
|
9851
|
+
const [reconcilePollToken, setReconcilePollToken] = React.useState(0);
|
|
9245
9852
|
const [showConfirm, setShowConfirm] = React.useState(false);
|
|
9246
9853
|
const [sessionToArchive, setSessionToArchive] = React.useState(null);
|
|
9247
9854
|
const fetchSessions = React.useCallback(async () => {
|
|
@@ -9262,8 +9869,13 @@ function Sidebar2({
|
|
|
9262
9869
|
fetchSessions();
|
|
9263
9870
|
}, [fetchSessions, refreshKey]);
|
|
9264
9871
|
React.useEffect(() => {
|
|
9265
|
-
const handleSessionsUpdated = () => {
|
|
9872
|
+
const handleSessionsUpdated = (event) => {
|
|
9266
9873
|
setRefreshKey((k) => k + 1);
|
|
9874
|
+
const detail = event.detail;
|
|
9875
|
+
const reason = detail?.reason;
|
|
9876
|
+
if (!reason || reason === "session_created" || reason === "message_sent" || reason === "title_regenerate_requested") {
|
|
9877
|
+
setReconcilePollToken((k) => k + 1);
|
|
9878
|
+
}
|
|
9267
9879
|
};
|
|
9268
9880
|
window.addEventListener("bichat:sessions-updated", handleSessionsUpdated);
|
|
9269
9881
|
return () => {
|
|
@@ -9271,31 +9883,33 @@ function Sidebar2({
|
|
|
9271
9883
|
};
|
|
9272
9884
|
}, []);
|
|
9273
9885
|
React.useEffect(() => {
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9886
|
+
activeSessionMissRetriesRef.current = {};
|
|
9887
|
+
}, [activeSessionId]);
|
|
9888
|
+
React.useEffect(() => {
|
|
9889
|
+
if (!activeSessionId) return;
|
|
9278
9890
|
if (loading) return;
|
|
9279
9891
|
const hasActiveSession = sessions.some((session) => session.id === activeSessionId);
|
|
9280
9892
|
if (hasActiveSession) {
|
|
9281
|
-
|
|
9282
|
-
refreshForActiveSessionRef.current = null;
|
|
9283
|
-
}
|
|
9893
|
+
delete activeSessionMissRetriesRef.current[activeSessionId];
|
|
9284
9894
|
return;
|
|
9285
9895
|
}
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9896
|
+
const attempts = activeSessionMissRetriesRef.current[activeSessionId] ?? 0;
|
|
9897
|
+
if (attempts >= ACTIVE_SESSION_MISS_MAX_RETRIES) {
|
|
9898
|
+
return;
|
|
9289
9899
|
}
|
|
9900
|
+
activeSessionMissRetriesRef.current[activeSessionId] = attempts + 1;
|
|
9901
|
+
const timeoutId = window.setTimeout(() => {
|
|
9902
|
+
setRefreshKey((k) => k + 1);
|
|
9903
|
+
setReconcilePollToken((k) => k + 1);
|
|
9904
|
+
}, ACTIVE_SESSION_MISS_RETRY_DELAY_MS);
|
|
9905
|
+
return () => window.clearTimeout(timeoutId);
|
|
9290
9906
|
}, [activeSessionId, loading, sessions]);
|
|
9291
9907
|
const hasPlaceholderTitles = React.useMemo(() => {
|
|
9292
9908
|
const newChatLabel = t("BiChat.Chat.NewChat");
|
|
9293
9909
|
return Array.isArray(sessions) && sessions.some((s) => s && (!s.title || s.title === newChatLabel));
|
|
9294
9910
|
}, [sessions, t]);
|
|
9295
9911
|
React.useEffect(() => {
|
|
9296
|
-
if (!hasPlaceholderTitles) return;
|
|
9297
|
-
const pollInterval = 2e3;
|
|
9298
|
-
const maxPolls = 5;
|
|
9912
|
+
if (!hasPlaceholderTitles && reconcilePollToken === 0) return;
|
|
9299
9913
|
let pollCount = 0;
|
|
9300
9914
|
const intervalId = setInterval(async () => {
|
|
9301
9915
|
pollCount++;
|
|
@@ -9304,12 +9918,12 @@ function Sidebar2({
|
|
|
9304
9918
|
setSessions(result.sessions);
|
|
9305
9919
|
} catch {
|
|
9306
9920
|
}
|
|
9307
|
-
if (pollCount >=
|
|
9921
|
+
if (pollCount >= SESSION_RECONCILE_MAX_POLLS) {
|
|
9308
9922
|
clearInterval(intervalId);
|
|
9309
9923
|
}
|
|
9310
|
-
},
|
|
9924
|
+
}, SESSION_RECONCILE_POLL_INTERVAL_MS);
|
|
9311
9925
|
return () => clearInterval(intervalId);
|
|
9312
|
-
}, [hasPlaceholderTitles, dataSource]);
|
|
9926
|
+
}, [hasPlaceholderTitles, dataSource, reconcilePollToken]);
|
|
9313
9927
|
const handleArchiveRequest = (sessionId) => {
|
|
9314
9928
|
setSessionToArchive(sessionId);
|
|
9315
9929
|
setShowConfirm(true);
|
|
@@ -9367,7 +9981,9 @@ function Sidebar2({
|
|
|
9367
9981
|
try {
|
|
9368
9982
|
await dataSource.regenerateSessionTitle(sessionId);
|
|
9369
9983
|
toast.success(t("BiChat.Sidebar.TitleRegenerated"));
|
|
9370
|
-
|
|
9984
|
+
window.dispatchEvent(new CustomEvent("bichat:sessions-updated", {
|
|
9985
|
+
detail: { reason: "title_regenerate_requested", sessionId }
|
|
9986
|
+
}));
|
|
9371
9987
|
} catch (err) {
|
|
9372
9988
|
console.error("Failed to regenerate title:", err);
|
|
9373
9989
|
const display = toErrorDisplay(err, t("BiChat.Sidebar.FailedToRegenerateTitle"));
|
|
@@ -10180,59 +10796,6 @@ function BiChatLayout({
|
|
|
10180
10796
|
] })
|
|
10181
10797
|
] });
|
|
10182
10798
|
}
|
|
10183
|
-
|
|
10184
|
-
// ui/src/bichat/components/RetryActionArea.tsx
|
|
10185
|
-
init_useTranslation();
|
|
10186
|
-
var RetryActionArea = React.memo(function RetryActionArea2({
|
|
10187
|
-
onRetry
|
|
10188
|
-
}) {
|
|
10189
|
-
const { t } = useTranslation();
|
|
10190
|
-
return (
|
|
10191
|
-
// Wrapper matches TurnBubble layout for assistant messages (justify-start = left-aligned)
|
|
10192
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10193
|
-
framerMotion.motion.div,
|
|
10194
|
-
{
|
|
10195
|
-
initial: { opacity: 0, y: 10 },
|
|
10196
|
-
animate: { opacity: 1, y: 0 },
|
|
10197
|
-
exit: { opacity: 0, y: -10 },
|
|
10198
|
-
transition: { duration: 0.2 },
|
|
10199
|
-
className: "flex justify-start",
|
|
10200
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
10201
|
-
"div",
|
|
10202
|
-
{
|
|
10203
|
-
className: "flex flex-col gap-3 max-w-2xl rounded-2xl px-5 py-3 shadow-sm bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700",
|
|
10204
|
-
role: "status",
|
|
10205
|
-
"aria-live": "polite",
|
|
10206
|
-
children: [
|
|
10207
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
10208
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10209
|
-
react.Warning,
|
|
10210
|
-
{
|
|
10211
|
-
className: "w-5 h-5 text-amber-500 dark:text-amber-400 flex-shrink-0",
|
|
10212
|
-
weight: "fill"
|
|
10213
|
-
}
|
|
10214
|
-
),
|
|
10215
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: t("BiChat.Retry.Subtitle") })
|
|
10216
|
-
] }),
|
|
10217
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
10218
|
-
"button",
|
|
10219
|
-
{
|
|
10220
|
-
onClick: onRetry,
|
|
10221
|
-
className: "cursor-pointer inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-white bg-primary-600 hover:bg-primary-700 dark:bg-primary-700 dark:hover:bg-primary-600 rounded-lg transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-800",
|
|
10222
|
-
"aria-label": t("BiChat.Retry.Title"),
|
|
10223
|
-
children: [
|
|
10224
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.ArrowClockwise, { size: 16, className: "w-4 h-4" }),
|
|
10225
|
-
t("BiChat.Retry.Button")
|
|
10226
|
-
]
|
|
10227
|
-
}
|
|
10228
|
-
) })
|
|
10229
|
-
]
|
|
10230
|
-
}
|
|
10231
|
-
)
|
|
10232
|
-
}
|
|
10233
|
-
)
|
|
10234
|
-
);
|
|
10235
|
-
});
|
|
10236
10799
|
init_useTranslation();
|
|
10237
10800
|
function MessageActions({
|
|
10238
10801
|
message,
|
|
@@ -12050,6 +12613,75 @@ function toStreamEvent(chunk) {
|
|
|
12050
12613
|
}
|
|
12051
12614
|
}
|
|
12052
12615
|
|
|
12616
|
+
// ui/src/bichat/utils/tableSpec.ts
|
|
12617
|
+
function isRecord2(value) {
|
|
12618
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12619
|
+
}
|
|
12620
|
+
function readString(value) {
|
|
12621
|
+
if (typeof value !== "string") return null;
|
|
12622
|
+
const trimmed = value.trim();
|
|
12623
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
12624
|
+
}
|
|
12625
|
+
function readPositiveInteger(value) {
|
|
12626
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
12627
|
+
const n = Math.floor(value);
|
|
12628
|
+
return n > 0 ? n : null;
|
|
12629
|
+
}
|
|
12630
|
+
function normalizeRows(value) {
|
|
12631
|
+
if (!Array.isArray(value)) return [];
|
|
12632
|
+
const rows = [];
|
|
12633
|
+
for (const row of value) {
|
|
12634
|
+
if (!Array.isArray(row)) continue;
|
|
12635
|
+
rows.push(row);
|
|
12636
|
+
}
|
|
12637
|
+
return rows;
|
|
12638
|
+
}
|
|
12639
|
+
function parseExport(value) {
|
|
12640
|
+
if (!isRecord2(value)) return void 0;
|
|
12641
|
+
const url = readString(value.url);
|
|
12642
|
+
if (!url) return void 0;
|
|
12643
|
+
return {
|
|
12644
|
+
url,
|
|
12645
|
+
filename: readString(value.filename) || "table_export.xlsx",
|
|
12646
|
+
rowCount: readPositiveInteger(value.row_count) || readPositiveInteger(value.rowCount) || void 0,
|
|
12647
|
+
fileSizeKB: readPositiveInteger(value.file_size_kb) || readPositiveInteger(value.fileSizeKB) || void 0
|
|
12648
|
+
};
|
|
12649
|
+
}
|
|
12650
|
+
function parseRenderTableDataFromJsonString(json, fallbackId) {
|
|
12651
|
+
const trimmed = json.trim();
|
|
12652
|
+
if (!trimmed) return null;
|
|
12653
|
+
let parsed;
|
|
12654
|
+
try {
|
|
12655
|
+
parsed = JSON.parse(trimmed);
|
|
12656
|
+
} catch {
|
|
12657
|
+
return null;
|
|
12658
|
+
}
|
|
12659
|
+
if (!isRecord2(parsed)) return null;
|
|
12660
|
+
const columns = Array.isArray(parsed.columns) ? parsed.columns.map((column) => readString(column)).filter((column) => column !== null) : [];
|
|
12661
|
+
if (columns.length === 0) return null;
|
|
12662
|
+
const rows = normalizeRows(parsed.rows);
|
|
12663
|
+
const headersRaw = Array.isArray(parsed.headers) ? parsed.headers.map((header) => readString(header)).filter((header) => header !== null) : [];
|
|
12664
|
+
const headers = headersRaw.length === columns.length ? headersRaw : columns;
|
|
12665
|
+
const totalRows = readPositiveInteger(parsed.total_rows) || readPositiveInteger(parsed.totalRows) || rows.length;
|
|
12666
|
+
const pageSize = readPositiveInteger(parsed.page_size) || readPositiveInteger(parsed.pageSize) || 25;
|
|
12667
|
+
const query = readString(parsed.query) || readString(parsed.sql);
|
|
12668
|
+
if (!query) return null;
|
|
12669
|
+
return {
|
|
12670
|
+
id: readString(parsed.id) || fallbackId,
|
|
12671
|
+
title: readString(parsed.title) || void 0,
|
|
12672
|
+
query,
|
|
12673
|
+
columns,
|
|
12674
|
+
headers,
|
|
12675
|
+
rows,
|
|
12676
|
+
totalRows,
|
|
12677
|
+
pageSize,
|
|
12678
|
+
truncated: parsed.truncated === true,
|
|
12679
|
+
truncatedReason: readString(parsed.truncated_reason) || readString(parsed.truncatedReason) || void 0,
|
|
12680
|
+
export: parseExport(parsed.export),
|
|
12681
|
+
exportPrompt: readString(parsed.export_prompt) || readString(parsed.exportPrompt) || void 0
|
|
12682
|
+
};
|
|
12683
|
+
}
|
|
12684
|
+
|
|
12053
12685
|
// ui/src/bichat/data/HttpDataSource.ts
|
|
12054
12686
|
function isSessionNotFoundError(err) {
|
|
12055
12687
|
if (!(err instanceof AppletRPCException)) return false;
|
|
@@ -12066,6 +12698,7 @@ function toSessionArtifact(artifact) {
|
|
|
12066
12698
|
id: artifact.id,
|
|
12067
12699
|
sessionId: artifact.sessionId,
|
|
12068
12700
|
messageId: artifact.messageId,
|
|
12701
|
+
uploadId: artifact.uploadId,
|
|
12069
12702
|
type: artifact.type,
|
|
12070
12703
|
name: artifact.name,
|
|
12071
12704
|
description: artifact.description,
|
|
@@ -12076,21 +12709,313 @@ function toSessionArtifact(artifact) {
|
|
|
12076
12709
|
createdAt: artifact.createdAt
|
|
12077
12710
|
};
|
|
12078
12711
|
}
|
|
12079
|
-
function
|
|
12080
|
-
|
|
12081
|
-
|
|
12082
|
-
|
|
12083
|
-
|
|
12084
|
-
|
|
12085
|
-
|
|
12086
|
-
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
|
|
12712
|
+
function warnMalformedSessionPayload(message, details) {
|
|
12713
|
+
console.warn(`[BiChat] ${message}`, details || {});
|
|
12714
|
+
}
|
|
12715
|
+
function readString2(value, fallback = "") {
|
|
12716
|
+
return typeof value === "string" ? value : fallback;
|
|
12717
|
+
}
|
|
12718
|
+
function readNonEmptyString(value) {
|
|
12719
|
+
if (typeof value !== "string") return null;
|
|
12720
|
+
const trimmed = value.trim();
|
|
12721
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
12722
|
+
}
|
|
12723
|
+
function readFiniteNumber(value, fallback = 0) {
|
|
12724
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
12725
|
+
}
|
|
12726
|
+
function readOptionalFiniteNumber(value) {
|
|
12727
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
12728
|
+
}
|
|
12729
|
+
var MIME_TO_EXTENSION = {
|
|
12730
|
+
"image/jpeg": "jpg",
|
|
12731
|
+
"image/png": "png",
|
|
12732
|
+
"image/gif": "gif",
|
|
12733
|
+
"application/pdf": "pdf"
|
|
12734
|
+
};
|
|
12735
|
+
var SAFE_AUTOCORRECT_MIME_TYPES = new Set(Object.keys(MIME_TO_EXTENSION));
|
|
12736
|
+
function detectMimeFromSignature(bytes) {
|
|
12737
|
+
if (bytes.length >= 8) {
|
|
12738
|
+
const isPng = bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[4] === 13 && bytes[5] === 10 && bytes[6] === 26 && bytes[7] === 10;
|
|
12739
|
+
if (isPng) return "image/png";
|
|
12740
|
+
}
|
|
12741
|
+
if (bytes.length >= 3) {
|
|
12742
|
+
const isJpeg = bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
|
|
12743
|
+
if (isJpeg) return "image/jpeg";
|
|
12744
|
+
}
|
|
12745
|
+
if (bytes.length >= 6) {
|
|
12746
|
+
const isGif = bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 56 && (bytes[4] === 55 || bytes[4] === 57) && bytes[5] === 97;
|
|
12747
|
+
if (isGif) return "image/gif";
|
|
12748
|
+
}
|
|
12749
|
+
if (bytes.length >= 4) {
|
|
12750
|
+
const isPdf = bytes[0] === 37 && bytes[1] === 80 && bytes[2] === 68 && bytes[3] === 70;
|
|
12751
|
+
if (isPdf) return "application/pdf";
|
|
12752
|
+
}
|
|
12753
|
+
return void 0;
|
|
12754
|
+
}
|
|
12755
|
+
function normalizeFilenameForMime(filename, mimeType) {
|
|
12756
|
+
const expectedExt = MIME_TO_EXTENSION[mimeType];
|
|
12757
|
+
if (!expectedExt) return filename;
|
|
12758
|
+
const lower = filename.toLowerCase();
|
|
12759
|
+
if (mimeType === "image/jpeg" && (lower.endsWith(".jpg") || lower.endsWith(".jpeg"))) {
|
|
12760
|
+
return filename;
|
|
12761
|
+
}
|
|
12762
|
+
if (lower.endsWith(`.${expectedExt}`)) {
|
|
12763
|
+
return filename;
|
|
12764
|
+
}
|
|
12765
|
+
const dotIndex = filename.lastIndexOf(".");
|
|
12766
|
+
const baseName = dotIndex > 0 ? filename.slice(0, dotIndex) : filename;
|
|
12767
|
+
return `${baseName}.${expectedExt}`;
|
|
12768
|
+
}
|
|
12769
|
+
function normalizeQuestionType(rawType) {
|
|
12770
|
+
const normalized = readString2(rawType).trim().toUpperCase().replace(/[\s-]+/g, "_");
|
|
12771
|
+
return normalized === "MULTIPLE_CHOICE" ? "MULTIPLE_CHOICE" : "SINGLE_CHOICE";
|
|
12772
|
+
}
|
|
12773
|
+
function normalizeMessageRole(rawRole) {
|
|
12774
|
+
const normalized = readString2(rawRole).trim().toLowerCase();
|
|
12775
|
+
if (normalized === "user" /* User */) return "user" /* User */;
|
|
12776
|
+
if (normalized === "system" /* System */) return "system" /* System */;
|
|
12777
|
+
if (normalized === "tool" /* Tool */) return "tool" /* Tool */;
|
|
12778
|
+
return "assistant" /* Assistant */;
|
|
12779
|
+
}
|
|
12780
|
+
function sanitizeAttachment(rawAttachment, turnId, index) {
|
|
12781
|
+
if (!isRecord(rawAttachment)) {
|
|
12782
|
+
warnMalformedSessionPayload("Dropped malformed attachment entry", { turnId, index });
|
|
12783
|
+
return null;
|
|
12784
|
+
}
|
|
12785
|
+
const filename = readString2(rawAttachment.filename, "attachment");
|
|
12786
|
+
const mimeType = readString2(rawAttachment.mimeType, "application/octet-stream");
|
|
12787
|
+
const id = readNonEmptyString(rawAttachment.id) || void 0;
|
|
12788
|
+
const clientKey = readNonEmptyString(rawAttachment.clientKey) || id || `${turnId}-attachment-${index}`;
|
|
12789
|
+
return {
|
|
12790
|
+
id,
|
|
12791
|
+
clientKey,
|
|
12792
|
+
filename,
|
|
12793
|
+
mimeType,
|
|
12794
|
+
sizeBytes: readFiniteNumber(rawAttachment.sizeBytes),
|
|
12795
|
+
uploadId: readOptionalFiniteNumber(rawAttachment.uploadId),
|
|
12796
|
+
base64Data: readNonEmptyString(rawAttachment.base64Data) || void 0,
|
|
12797
|
+
url: readNonEmptyString(rawAttachment.url) || void 0,
|
|
12798
|
+
preview: readNonEmptyString(rawAttachment.preview) || void 0
|
|
12799
|
+
};
|
|
12800
|
+
}
|
|
12801
|
+
function sanitizeUserAttachments(rawAttachments, turnId) {
|
|
12802
|
+
if (!Array.isArray(rawAttachments)) return [];
|
|
12803
|
+
const result = [];
|
|
12804
|
+
for (let i = 0; i < rawAttachments.length; i++) {
|
|
12805
|
+
const sanitized = sanitizeAttachment(rawAttachments[i], turnId, i);
|
|
12806
|
+
if (sanitized) result.push(sanitized);
|
|
12807
|
+
}
|
|
12808
|
+
return result;
|
|
12809
|
+
}
|
|
12810
|
+
function sanitizeAssistantArtifacts(rawArtifacts, turnId) {
|
|
12811
|
+
if (!Array.isArray(rawArtifacts)) return [];
|
|
12812
|
+
const artifacts = [];
|
|
12813
|
+
for (let i = 0; i < rawArtifacts.length; i++) {
|
|
12814
|
+
const raw = rawArtifacts[i];
|
|
12815
|
+
if (!isRecord(raw)) {
|
|
12816
|
+
warnMalformedSessionPayload("Dropped malformed assistant artifact", { turnId, index: i });
|
|
12817
|
+
continue;
|
|
12818
|
+
}
|
|
12819
|
+
const type = readString2(raw.type).toLowerCase();
|
|
12820
|
+
if (type !== "excel" && type !== "pdf") {
|
|
12821
|
+
continue;
|
|
12822
|
+
}
|
|
12823
|
+
const url = readNonEmptyString(raw.url);
|
|
12824
|
+
if (!url) {
|
|
12825
|
+
warnMalformedSessionPayload("Dropped assistant artifact without url", { turnId, index: i });
|
|
12826
|
+
continue;
|
|
12827
|
+
}
|
|
12828
|
+
artifacts.push({
|
|
12829
|
+
type,
|
|
12830
|
+
filename: readString2(raw.filename, "download"),
|
|
12831
|
+
url,
|
|
12832
|
+
sizeReadable: readNonEmptyString(raw.sizeReadable) || void 0,
|
|
12833
|
+
rowCount: typeof raw.rowCount === "number" && Number.isFinite(raw.rowCount) ? raw.rowCount : void 0,
|
|
12834
|
+
description: readNonEmptyString(raw.description) || void 0
|
|
12835
|
+
});
|
|
12836
|
+
}
|
|
12837
|
+
return artifacts;
|
|
12838
|
+
}
|
|
12839
|
+
function sanitizeAssistantTurn(rawAssistantTurn, fallbackCreatedAt, turnId) {
|
|
12840
|
+
if (rawAssistantTurn == null) return void 0;
|
|
12841
|
+
if (!isRecord(rawAssistantTurn)) {
|
|
12842
|
+
warnMalformedSessionPayload("Dropped malformed assistant turn payload", { turnId });
|
|
12843
|
+
return void 0;
|
|
12844
|
+
}
|
|
12845
|
+
const assistantID = readNonEmptyString(rawAssistantTurn.id);
|
|
12846
|
+
if (!assistantID) {
|
|
12847
|
+
warnMalformedSessionPayload("Dropped assistant turn without id", { turnId });
|
|
12848
|
+
return void 0;
|
|
12849
|
+
}
|
|
12850
|
+
const citations = Array.isArray(rawAssistantTurn.citations) ? rawAssistantTurn.citations.filter((item) => isRecord(item)).map((item, index) => ({
|
|
12851
|
+
id: readString2(item.id, `${assistantID}-citation-${index}`),
|
|
12852
|
+
type: readString2(item.type),
|
|
12853
|
+
title: readString2(item.title),
|
|
12854
|
+
url: readString2(item.url),
|
|
12855
|
+
startIndex: readFiniteNumber(item.startIndex),
|
|
12856
|
+
endIndex: readFiniteNumber(item.endIndex),
|
|
12857
|
+
excerpt: readNonEmptyString(item.excerpt) || void 0
|
|
12858
|
+
})) : [];
|
|
12859
|
+
const toolCalls = Array.isArray(rawAssistantTurn.toolCalls) ? rawAssistantTurn.toolCalls.filter((item) => isRecord(item)).map((item, index) => ({
|
|
12860
|
+
id: readString2(item.id, `${assistantID}-tool-${index}`),
|
|
12861
|
+
name: readString2(item.name),
|
|
12862
|
+
arguments: readString2(item.arguments),
|
|
12863
|
+
result: readNonEmptyString(item.result) || void 0,
|
|
12864
|
+
error: readNonEmptyString(item.error) || void 0,
|
|
12865
|
+
durationMs: readFiniteNumber(item.durationMs)
|
|
12866
|
+
})) : [];
|
|
12867
|
+
const codeOutputs = Array.isArray(rawAssistantTurn.codeOutputs) ? rawAssistantTurn.codeOutputs.filter((item) => isRecord(item)).map((item) => ({
|
|
12868
|
+
type: (() => {
|
|
12869
|
+
const normalizedType = readString2(item.type, "text").toLowerCase();
|
|
12870
|
+
if (normalizedType === "image" || normalizedType === "error") return normalizedType;
|
|
12871
|
+
return "text";
|
|
12872
|
+
})(),
|
|
12873
|
+
content: readString2(item.content),
|
|
12874
|
+
filename: readNonEmptyString(item.filename) || void 0,
|
|
12875
|
+
mimeType: readNonEmptyString(item.mimeType) || void 0,
|
|
12876
|
+
sizeBytes: readOptionalFiniteNumber(item.sizeBytes)
|
|
12877
|
+
})) : [];
|
|
12878
|
+
const debugTrace = isRecord(rawAssistantTurn.debug) ? {
|
|
12879
|
+
generationMs: readOptionalFiniteNumber(rawAssistantTurn.debug.generationMs),
|
|
12880
|
+
usage: isRecord(rawAssistantTurn.debug.usage) ? {
|
|
12881
|
+
promptTokens: readFiniteNumber(rawAssistantTurn.debug.usage.promptTokens),
|
|
12882
|
+
completionTokens: readFiniteNumber(rawAssistantTurn.debug.usage.completionTokens),
|
|
12883
|
+
totalTokens: readFiniteNumber(rawAssistantTurn.debug.usage.totalTokens),
|
|
12884
|
+
cachedTokens: readOptionalFiniteNumber(rawAssistantTurn.debug.usage.cachedTokens),
|
|
12885
|
+
cost: readOptionalFiniteNumber(rawAssistantTurn.debug.usage.cost)
|
|
12886
|
+
} : void 0,
|
|
12887
|
+
tools: Array.isArray(rawAssistantTurn.debug.tools) ? rawAssistantTurn.debug.tools.filter((tool) => isRecord(tool)).map((tool) => ({
|
|
12888
|
+
callId: readNonEmptyString(tool.callId) || void 0,
|
|
12889
|
+
name: readString2(tool.name),
|
|
12890
|
+
arguments: readNonEmptyString(tool.arguments) || void 0,
|
|
12891
|
+
result: readNonEmptyString(tool.result) || void 0,
|
|
12892
|
+
error: readNonEmptyString(tool.error) || void 0,
|
|
12893
|
+
durationMs: readOptionalFiniteNumber(tool.durationMs)
|
|
12894
|
+
})) : []
|
|
12895
|
+
} : void 0;
|
|
12896
|
+
return {
|
|
12897
|
+
id: assistantID,
|
|
12898
|
+
role: normalizeMessageRole(rawAssistantTurn.role),
|
|
12899
|
+
content: readString2(rawAssistantTurn.content),
|
|
12900
|
+
explanation: readNonEmptyString(rawAssistantTurn.explanation) || void 0,
|
|
12901
|
+
citations,
|
|
12902
|
+
toolCalls,
|
|
12903
|
+
chartData: void 0,
|
|
12904
|
+
renderTables: void 0,
|
|
12905
|
+
artifacts: sanitizeAssistantArtifacts(rawAssistantTurn.artifacts, turnId),
|
|
12906
|
+
codeOutputs,
|
|
12907
|
+
debug: debugTrace,
|
|
12908
|
+
createdAt: readString2(rawAssistantTurn.createdAt, fallbackCreatedAt)
|
|
12909
|
+
};
|
|
12910
|
+
}
|
|
12911
|
+
function sanitizeConversationTurn(rawTurn, index, fallbackSessionID) {
|
|
12912
|
+
if (!isRecord(rawTurn)) {
|
|
12913
|
+
warnMalformedSessionPayload("Dropped malformed turn payload (not an object)", { index });
|
|
12914
|
+
return null;
|
|
12915
|
+
}
|
|
12916
|
+
if (!isRecord(rawTurn.userTurn)) {
|
|
12917
|
+
warnMalformedSessionPayload("Dropped malformed turn payload (missing user turn)", { index });
|
|
12918
|
+
return null;
|
|
12919
|
+
}
|
|
12920
|
+
const userTurnID = readNonEmptyString(rawTurn.userTurn.id);
|
|
12921
|
+
if (!userTurnID) {
|
|
12922
|
+
warnMalformedSessionPayload("Dropped malformed turn payload (missing user turn id)", { index });
|
|
12923
|
+
return null;
|
|
12924
|
+
}
|
|
12925
|
+
const turnID = readString2(rawTurn.id, userTurnID);
|
|
12926
|
+
const createdAt = readString2(
|
|
12927
|
+
rawTurn.createdAt,
|
|
12928
|
+
readString2(rawTurn.userTurn.createdAt, (/* @__PURE__ */ new Date()).toISOString())
|
|
12929
|
+
);
|
|
12930
|
+
return {
|
|
12931
|
+
id: turnID,
|
|
12932
|
+
sessionId: readString2(rawTurn.sessionId, fallbackSessionID),
|
|
12933
|
+
userTurn: {
|
|
12934
|
+
id: userTurnID,
|
|
12935
|
+
content: readString2(rawTurn.userTurn.content),
|
|
12936
|
+
attachments: sanitizeUserAttachments(rawTurn.userTurn.attachments, turnID),
|
|
12937
|
+
createdAt: readString2(rawTurn.userTurn.createdAt, createdAt)
|
|
12938
|
+
},
|
|
12939
|
+
assistantTurn: sanitizeAssistantTurn(rawTurn.assistantTurn, createdAt, turnID),
|
|
12940
|
+
createdAt
|
|
12941
|
+
};
|
|
12942
|
+
}
|
|
12943
|
+
function sanitizeConversationTurns(rawTurns, sessionID) {
|
|
12944
|
+
if (!Array.isArray(rawTurns)) {
|
|
12945
|
+
warnMalformedSessionPayload("Session payload contained non-array turns field", { sessionID });
|
|
12946
|
+
return [];
|
|
12947
|
+
}
|
|
12948
|
+
const turns = [];
|
|
12949
|
+
let dropped = 0;
|
|
12950
|
+
for (let i = 0; i < rawTurns.length; i++) {
|
|
12951
|
+
const sanitizedTurn = sanitizeConversationTurn(rawTurns[i], i, sessionID);
|
|
12952
|
+
if (sanitizedTurn) {
|
|
12953
|
+
turns.push(sanitizedTurn);
|
|
12954
|
+
} else {
|
|
12955
|
+
dropped++;
|
|
12956
|
+
}
|
|
12957
|
+
}
|
|
12958
|
+
if (dropped > 0) {
|
|
12959
|
+
warnMalformedSessionPayload("Dropped malformed turns from session payload", {
|
|
12960
|
+
sessionID,
|
|
12961
|
+
dropped,
|
|
12962
|
+
total: rawTurns.length
|
|
12963
|
+
});
|
|
12964
|
+
}
|
|
12965
|
+
return turns;
|
|
12966
|
+
}
|
|
12967
|
+
function sanitizePendingQuestion(rawPendingQuestion, sessionID) {
|
|
12968
|
+
if (!rawPendingQuestion) return null;
|
|
12969
|
+
const checkpointID = readNonEmptyString(rawPendingQuestion.checkpointId);
|
|
12970
|
+
if (!checkpointID) {
|
|
12971
|
+
warnMalformedSessionPayload("Dropped malformed pendingQuestion without checkpointId", { sessionID });
|
|
12972
|
+
return null;
|
|
12973
|
+
}
|
|
12974
|
+
if (!Array.isArray(rawPendingQuestion.questions)) {
|
|
12975
|
+
warnMalformedSessionPayload("Pending question had non-array questions payload", {
|
|
12976
|
+
sessionID,
|
|
12977
|
+
checkpointID
|
|
12978
|
+
});
|
|
12979
|
+
}
|
|
12980
|
+
const questions = Array.isArray(rawPendingQuestion.questions) ? rawPendingQuestion.questions.filter((question) => {
|
|
12981
|
+
if (!question || !isRecord(question)) {
|
|
12982
|
+
warnMalformedSessionPayload("Dropped malformed question from pendingQuestion", {
|
|
12983
|
+
sessionID,
|
|
12984
|
+
checkpointID
|
|
12985
|
+
});
|
|
12986
|
+
return false;
|
|
12987
|
+
}
|
|
12988
|
+
return true;
|
|
12989
|
+
}).map((question, index) => {
|
|
12990
|
+
const questionID = readString2(question.id, `${checkpointID}-q-${index}`);
|
|
12991
|
+
const options = Array.isArray(question.options) ? question.options.filter((option) => {
|
|
12992
|
+
if (!option || !isRecord(option)) {
|
|
12993
|
+
warnMalformedSessionPayload("Dropped malformed pendingQuestion option", {
|
|
12994
|
+
sessionID,
|
|
12995
|
+
checkpointID,
|
|
12996
|
+
questionID
|
|
12997
|
+
});
|
|
12998
|
+
return false;
|
|
12999
|
+
}
|
|
13000
|
+
return true;
|
|
13001
|
+
}).map((option, optionIndex) => {
|
|
13002
|
+
const label = readString2(option.label);
|
|
13003
|
+
return {
|
|
13004
|
+
id: readString2(option.id, `${questionID}-opt-${optionIndex}`),
|
|
13005
|
+
label,
|
|
13006
|
+
value: label
|
|
13007
|
+
};
|
|
13008
|
+
}) : [];
|
|
13009
|
+
return {
|
|
13010
|
+
id: questionID,
|
|
13011
|
+
text: readString2(question.text),
|
|
13012
|
+
type: normalizeQuestionType(question.type),
|
|
13013
|
+
options
|
|
13014
|
+
};
|
|
13015
|
+
}) : [];
|
|
12091
13016
|
return {
|
|
12092
|
-
id:
|
|
12093
|
-
turnId:
|
|
13017
|
+
id: checkpointID,
|
|
13018
|
+
turnId: readString2(rawPendingQuestion.turnId),
|
|
12094
13019
|
questions,
|
|
12095
13020
|
status: "PENDING"
|
|
12096
13021
|
};
|
|
@@ -12162,6 +13087,18 @@ function extractChartDataFromToolCalls(toolCalls) {
|
|
|
12162
13087
|
}
|
|
12163
13088
|
return void 0;
|
|
12164
13089
|
}
|
|
13090
|
+
function extractRenderTablesFromToolCalls(toolCalls) {
|
|
13091
|
+
if (!toolCalls) return [];
|
|
13092
|
+
const tables = [];
|
|
13093
|
+
for (const tc of toolCalls) {
|
|
13094
|
+
if (tc.name !== "renderTable" || !tc.result) continue;
|
|
13095
|
+
const parsed = parseRenderTableDataFromJsonString(tc.result, tc.id);
|
|
13096
|
+
if (parsed) {
|
|
13097
|
+
tables.push(parsed);
|
|
13098
|
+
}
|
|
13099
|
+
}
|
|
13100
|
+
return tables;
|
|
13101
|
+
}
|
|
12165
13102
|
var EXPORT_TOOL_NAMES = {
|
|
12166
13103
|
export_query_to_excel: "excel",
|
|
12167
13104
|
export_data_to_excel: "excel",
|
|
@@ -12197,6 +13134,7 @@ function extractDownloadArtifactsFromToolCalls(toolCalls) {
|
|
|
12197
13134
|
function normalizeAssistantTurn(turn) {
|
|
12198
13135
|
const existingArtifacts = turn.artifacts || [];
|
|
12199
13136
|
const fromToolCalls = extractDownloadArtifactsFromToolCalls(turn.toolCalls);
|
|
13137
|
+
const renderTables = turn.renderTables || extractRenderTablesFromToolCalls(turn.toolCalls);
|
|
12200
13138
|
const merged = [...existingArtifacts];
|
|
12201
13139
|
for (const a of fromToolCalls) {
|
|
12202
13140
|
if (!merged.some((e) => e.url === a.url && e.filename === a.filename)) {
|
|
@@ -12207,6 +13145,7 @@ function normalizeAssistantTurn(turn) {
|
|
|
12207
13145
|
...turn,
|
|
12208
13146
|
role: turn.role || "assistant" /* Assistant */,
|
|
12209
13147
|
chartData: turn.chartData || extractChartDataFromToolCalls(turn.toolCalls),
|
|
13148
|
+
renderTables,
|
|
12210
13149
|
citations: turn.citations || [],
|
|
12211
13150
|
artifacts: merged,
|
|
12212
13151
|
codeOutputs: turn.codeOutputs || []
|
|
@@ -12286,6 +13225,7 @@ var HttpDataSource = class {
|
|
|
12286
13225
|
this.abortController = null;
|
|
12287
13226
|
this.config = {
|
|
12288
13227
|
streamEndpoint: "/stream",
|
|
13228
|
+
uploadEndpoint: "/api/uploads",
|
|
12289
13229
|
timeout: 12e4,
|
|
12290
13230
|
...config
|
|
12291
13231
|
};
|
|
@@ -12321,6 +13261,216 @@ var HttpDataSource = class {
|
|
|
12321
13261
|
}
|
|
12322
13262
|
return headers;
|
|
12323
13263
|
}
|
|
13264
|
+
createUploadHeaders(additionalHeaders) {
|
|
13265
|
+
const headers = new Headers({
|
|
13266
|
+
...this.config.headers,
|
|
13267
|
+
...additionalHeaders
|
|
13268
|
+
});
|
|
13269
|
+
const csrfToken = this.getCSRFToken();
|
|
13270
|
+
if (csrfToken) {
|
|
13271
|
+
headers.set("X-CSRF-Token", csrfToken);
|
|
13272
|
+
}
|
|
13273
|
+
headers.delete("Content-Type");
|
|
13274
|
+
return headers;
|
|
13275
|
+
}
|
|
13276
|
+
logAttachmentLifecycle(event, details) {
|
|
13277
|
+
const payload = {
|
|
13278
|
+
source: "HttpDataSource",
|
|
13279
|
+
event,
|
|
13280
|
+
...details
|
|
13281
|
+
};
|
|
13282
|
+
if (event.endsWith("_fail")) {
|
|
13283
|
+
console.warn("[bichat.attachments]", payload);
|
|
13284
|
+
return;
|
|
13285
|
+
}
|
|
13286
|
+
}
|
|
13287
|
+
async normalizeAttachmentFile(attachment, file) {
|
|
13288
|
+
const signatureBytes = new Uint8Array(await file.slice(0, 16).arrayBuffer());
|
|
13289
|
+
const detectedMimeType = detectMimeFromSignature(signatureBytes);
|
|
13290
|
+
const declaredMimeType = (attachment.mimeType || file.type || "").trim().toLowerCase();
|
|
13291
|
+
let resolvedMimeType = declaredMimeType || detectedMimeType || "application/octet-stream";
|
|
13292
|
+
let correctedFromDeclared = false;
|
|
13293
|
+
if (detectedMimeType && declaredMimeType && detectedMimeType !== declaredMimeType) {
|
|
13294
|
+
const safeToCorrect = SAFE_AUTOCORRECT_MIME_TYPES.has(detectedMimeType) && SAFE_AUTOCORRECT_MIME_TYPES.has(declaredMimeType);
|
|
13295
|
+
if (!safeToCorrect) {
|
|
13296
|
+
throw new Error(
|
|
13297
|
+
`Attachment "${attachment.filename}" MIME mismatch: declared "${declaredMimeType}", detected "${detectedMimeType}"`
|
|
13298
|
+
);
|
|
13299
|
+
}
|
|
13300
|
+
resolvedMimeType = detectedMimeType;
|
|
13301
|
+
correctedFromDeclared = true;
|
|
13302
|
+
} else if (detectedMimeType && !declaredMimeType) {
|
|
13303
|
+
resolvedMimeType = detectedMimeType;
|
|
13304
|
+
}
|
|
13305
|
+
const normalizedName = normalizeFilenameForMime(attachment.filename, resolvedMimeType);
|
|
13306
|
+
const normalized = new File([file], normalizedName, {
|
|
13307
|
+
type: resolvedMimeType,
|
|
13308
|
+
lastModified: file.lastModified
|
|
13309
|
+
});
|
|
13310
|
+
this.logAttachmentLifecycle("attachment_decode_success", {
|
|
13311
|
+
attachmentKey: attachment.clientKey,
|
|
13312
|
+
filename: attachment.filename,
|
|
13313
|
+
normalizedFilename: normalized.name,
|
|
13314
|
+
declaredMimeType: declaredMimeType || void 0,
|
|
13315
|
+
detectedMimeType,
|
|
13316
|
+
resolvedMimeType,
|
|
13317
|
+
correctedFromDeclared,
|
|
13318
|
+
sizeBytes: normalized.size
|
|
13319
|
+
});
|
|
13320
|
+
return normalized;
|
|
13321
|
+
}
|
|
13322
|
+
async uploadFile(file) {
|
|
13323
|
+
const formData = new FormData();
|
|
13324
|
+
formData.append("file", file);
|
|
13325
|
+
const response = await fetch(`${this.config.baseUrl}${this.config.uploadEndpoint}`, {
|
|
13326
|
+
method: "POST",
|
|
13327
|
+
headers: this.createUploadHeaders(),
|
|
13328
|
+
body: formData
|
|
13329
|
+
});
|
|
13330
|
+
let payload = null;
|
|
13331
|
+
try {
|
|
13332
|
+
payload = await response.json();
|
|
13333
|
+
} catch {
|
|
13334
|
+
payload = null;
|
|
13335
|
+
}
|
|
13336
|
+
if (!response.ok) {
|
|
13337
|
+
const errorMessage = isRecord(payload) && typeof payload.error === "string" ? payload.error : `Upload failed: HTTP ${response.status}`;
|
|
13338
|
+
throw new Error(errorMessage);
|
|
13339
|
+
}
|
|
13340
|
+
if (!isRecord(payload) || typeof payload.id !== "number" || payload.id <= 0) {
|
|
13341
|
+
throw new Error("Upload failed: invalid response payload");
|
|
13342
|
+
}
|
|
13343
|
+
return {
|
|
13344
|
+
id: payload.id,
|
|
13345
|
+
url: typeof payload.url === "string" ? payload.url : "",
|
|
13346
|
+
path: typeof payload.path === "string" ? payload.path : "",
|
|
13347
|
+
name: typeof payload.name === "string" ? payload.name : file.name,
|
|
13348
|
+
mimetype: typeof payload.mimetype === "string" ? payload.mimetype : file.type,
|
|
13349
|
+
size: typeof payload.size === "number" && Number.isFinite(payload.size) ? payload.size : file.size
|
|
13350
|
+
};
|
|
13351
|
+
}
|
|
13352
|
+
async attachmentToFile(attachment) {
|
|
13353
|
+
if (attachment.base64Data && attachment.base64Data.trim().length > 0) {
|
|
13354
|
+
try {
|
|
13355
|
+
const base64Data = attachment.base64Data.trim();
|
|
13356
|
+
const dataUrl = base64Data.startsWith("data:") ? base64Data : `data:${attachment.mimeType || "application/octet-stream"};base64,${base64Data}`;
|
|
13357
|
+
const blob = await fetch(dataUrl).then((response) => response.blob());
|
|
13358
|
+
return new File([blob], attachment.filename, {
|
|
13359
|
+
type: attachment.mimeType || blob.type || "application/octet-stream"
|
|
13360
|
+
});
|
|
13361
|
+
} catch (err) {
|
|
13362
|
+
const message = err instanceof Error ? err.message : "Unknown decode error";
|
|
13363
|
+
throw new Error(`Attachment "${attachment.filename}" decode failed: ${message}`);
|
|
13364
|
+
}
|
|
13365
|
+
}
|
|
13366
|
+
if (attachment.url) {
|
|
13367
|
+
let parsed;
|
|
13368
|
+
try {
|
|
13369
|
+
parsed = new URL(attachment.url, window.location?.origin ?? "https://localhost");
|
|
13370
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
13371
|
+
throw new Error(`Attachment "${attachment.filename}" URL has disallowed protocol: ${parsed.protocol}`);
|
|
13372
|
+
}
|
|
13373
|
+
} catch (err) {
|
|
13374
|
+
if (err instanceof Error && err.message.includes("Attachment")) throw err;
|
|
13375
|
+
throw new Error(`Attachment "${attachment.filename}" has invalid or malformed URL`);
|
|
13376
|
+
}
|
|
13377
|
+
const response = await fetch(parsed.href);
|
|
13378
|
+
if (!response.ok) {
|
|
13379
|
+
throw new Error(`Attachment "${attachment.filename}" decode failed: source HTTP ${response.status}`);
|
|
13380
|
+
}
|
|
13381
|
+
const blob = await response.blob();
|
|
13382
|
+
return new File([blob], attachment.filename, {
|
|
13383
|
+
type: attachment.mimeType || blob.type || "application/octet-stream"
|
|
13384
|
+
});
|
|
13385
|
+
}
|
|
13386
|
+
throw new Error(`Attachment "${attachment.filename}" has no uploadable data`);
|
|
13387
|
+
}
|
|
13388
|
+
assertUploadReferences(uploads) {
|
|
13389
|
+
return uploads.map((upload, index) => {
|
|
13390
|
+
if (typeof upload.id !== "number" || !Number.isFinite(upload.id) || upload.id <= 0) {
|
|
13391
|
+
throw new Error(`Attachment upload reference is invalid at index ${index}`);
|
|
13392
|
+
}
|
|
13393
|
+
return { uploadId: upload.id };
|
|
13394
|
+
});
|
|
13395
|
+
}
|
|
13396
|
+
async ensureAttachmentUpload(attachment, context) {
|
|
13397
|
+
if (typeof attachment.uploadId === "number" && attachment.uploadId > 0) {
|
|
13398
|
+
this.logAttachmentLifecycle("attachment_upload_success", {
|
|
13399
|
+
sessionId: context.sessionId,
|
|
13400
|
+
attachmentIndex: context.attachmentIndex,
|
|
13401
|
+
attachmentKey: attachment.clientKey,
|
|
13402
|
+
filename: attachment.filename,
|
|
13403
|
+
uploadId: attachment.uploadId,
|
|
13404
|
+
reusedUploadId: true
|
|
13405
|
+
});
|
|
13406
|
+
return {
|
|
13407
|
+
id: attachment.uploadId,
|
|
13408
|
+
url: attachment.url || "",
|
|
13409
|
+
path: "",
|
|
13410
|
+
name: attachment.filename,
|
|
13411
|
+
mimetype: attachment.mimeType,
|
|
13412
|
+
size: attachment.sizeBytes
|
|
13413
|
+
};
|
|
13414
|
+
}
|
|
13415
|
+
this.logAttachmentLifecycle("attachment_decode_start", {
|
|
13416
|
+
sessionId: context.sessionId,
|
|
13417
|
+
attachmentIndex: context.attachmentIndex,
|
|
13418
|
+
attachmentKey: attachment.clientKey,
|
|
13419
|
+
filename: attachment.filename,
|
|
13420
|
+
hasBase64Data: Boolean(attachment.base64Data && attachment.base64Data.trim().length > 0),
|
|
13421
|
+
hasURL: Boolean(attachment.url)
|
|
13422
|
+
});
|
|
13423
|
+
let file;
|
|
13424
|
+
try {
|
|
13425
|
+
const rawFile = await this.attachmentToFile(attachment);
|
|
13426
|
+
file = await this.normalizeAttachmentFile(attachment, rawFile);
|
|
13427
|
+
validateAttachmentFile(file);
|
|
13428
|
+
} catch (err) {
|
|
13429
|
+
const message = err instanceof Error ? err.message : "Unknown attachment decode/validation error";
|
|
13430
|
+
this.logAttachmentLifecycle("attachment_decode_fail", {
|
|
13431
|
+
sessionId: context.sessionId,
|
|
13432
|
+
attachmentIndex: context.attachmentIndex,
|
|
13433
|
+
attachmentKey: attachment.clientKey,
|
|
13434
|
+
filename: attachment.filename,
|
|
13435
|
+
error: message
|
|
13436
|
+
});
|
|
13437
|
+
throw new Error(message);
|
|
13438
|
+
}
|
|
13439
|
+
this.logAttachmentLifecycle("attachment_upload_start", {
|
|
13440
|
+
sessionId: context.sessionId,
|
|
13441
|
+
attachmentIndex: context.attachmentIndex,
|
|
13442
|
+
attachmentKey: attachment.clientKey,
|
|
13443
|
+
filename: file.name,
|
|
13444
|
+
mimeType: file.type,
|
|
13445
|
+
sizeBytes: file.size
|
|
13446
|
+
});
|
|
13447
|
+
try {
|
|
13448
|
+
const upload = await this.uploadFile(file);
|
|
13449
|
+
attachment.uploadId = upload.id;
|
|
13450
|
+
attachment.mimeType = upload.mimetype || file.type;
|
|
13451
|
+
attachment.filename = upload.name || file.name;
|
|
13452
|
+
attachment.sizeBytes = upload.size || file.size;
|
|
13453
|
+
this.logAttachmentLifecycle("attachment_upload_success", {
|
|
13454
|
+
sessionId: context.sessionId,
|
|
13455
|
+
attachmentIndex: context.attachmentIndex,
|
|
13456
|
+
attachmentKey: attachment.clientKey,
|
|
13457
|
+
filename: attachment.filename,
|
|
13458
|
+
uploadId: upload.id,
|
|
13459
|
+
reusedUploadId: false
|
|
13460
|
+
});
|
|
13461
|
+
return upload;
|
|
13462
|
+
} catch (err) {
|
|
13463
|
+
const message = err instanceof Error ? err.message : "Unknown upload error";
|
|
13464
|
+
this.logAttachmentLifecycle("attachment_upload_fail", {
|
|
13465
|
+
sessionId: context.sessionId,
|
|
13466
|
+
attachmentIndex: context.attachmentIndex,
|
|
13467
|
+
attachmentKey: attachment.clientKey,
|
|
13468
|
+
filename: file.name,
|
|
13469
|
+
error: message
|
|
13470
|
+
});
|
|
13471
|
+
throw new Error(`Attachment "${attachment.filename}" upload failed: ${message}`);
|
|
13472
|
+
}
|
|
13473
|
+
}
|
|
12324
13474
|
async callRPC(method, params) {
|
|
12325
13475
|
return this.rpc.callTyped(method, params);
|
|
12326
13476
|
}
|
|
@@ -12343,11 +13493,22 @@ var HttpDataSource = class {
|
|
|
12343
13493
|
return { artifacts: [], hasMore: false, nextOffset: 0 };
|
|
12344
13494
|
})
|
|
12345
13495
|
]);
|
|
12346
|
-
const
|
|
13496
|
+
const sanitizedTurns = sanitizeConversationTurns(data.turns, id);
|
|
13497
|
+
const turns = attachArtifactsToTurns(
|
|
13498
|
+
normalizeTurns(sanitizedTurns),
|
|
13499
|
+
artifactsData.artifacts || []
|
|
13500
|
+
);
|
|
13501
|
+
const pendingQuestion = sanitizePendingQuestion(data.pendingQuestion, id);
|
|
13502
|
+
if (data.pendingQuestion && pendingQuestion && pendingQuestion.questions.length === 0) {
|
|
13503
|
+
warnMalformedSessionPayload("Pending question normalized to zero renderable questions", {
|
|
13504
|
+
sessionID: id,
|
|
13505
|
+
checkpointID: pendingQuestion.id
|
|
13506
|
+
});
|
|
13507
|
+
}
|
|
12347
13508
|
return {
|
|
12348
13509
|
session: toSession(data.session),
|
|
12349
13510
|
turns,
|
|
12350
|
-
pendingQuestion
|
|
13511
|
+
pendingQuestion
|
|
12351
13512
|
};
|
|
12352
13513
|
} catch (err) {
|
|
12353
13514
|
if (isSessionNotFoundError(err)) {
|
|
@@ -12379,27 +13540,12 @@ var HttpDataSource = class {
|
|
|
12379
13540
|
return { artifacts: [] };
|
|
12380
13541
|
}
|
|
12381
13542
|
validateFileCount(0, files.length, 10);
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
validateAttachmentFile(file);
|
|
12385
|
-
const base64Data = await convertToBase64(file);
|
|
12386
|
-
attachments.push({
|
|
12387
|
-
clientKey: crypto.randomUUID(),
|
|
12388
|
-
filename: file.name,
|
|
12389
|
-
mimeType: file.type,
|
|
12390
|
-
sizeBytes: file.size,
|
|
12391
|
-
base64Data
|
|
12392
|
-
});
|
|
12393
|
-
}
|
|
13543
|
+
files.forEach((file) => validateAttachmentFile(file));
|
|
13544
|
+
const uploads = await Promise.all(files.map((file) => this.uploadFile(file)));
|
|
12394
13545
|
const data = await this.callRPC("bichat.session.uploadArtifacts", {
|
|
12395
13546
|
sessionId,
|
|
12396
|
-
attachments:
|
|
12397
|
-
|
|
12398
|
-
// Backend will assign ID
|
|
12399
|
-
filename: a.filename,
|
|
12400
|
-
mimeType: a.mimeType,
|
|
12401
|
-
sizeBytes: a.sizeBytes,
|
|
12402
|
-
base64Data: a.base64Data
|
|
13547
|
+
attachments: uploads.map((upload) => ({
|
|
13548
|
+
uploadId: upload.id
|
|
12403
13549
|
}))
|
|
12404
13550
|
});
|
|
12405
13551
|
return {
|
|
@@ -12430,22 +13576,26 @@ var HttpDataSource = class {
|
|
|
12430
13576
|
signal.addEventListener("abort", onExternalAbort);
|
|
12431
13577
|
}
|
|
12432
13578
|
const url = `${this.config.baseUrl}${this.config.streamEndpoint}`;
|
|
12433
|
-
const payload = {
|
|
12434
|
-
sessionId,
|
|
12435
|
-
content,
|
|
12436
|
-
debugMode: options?.debugMode ?? false,
|
|
12437
|
-
replaceFromMessageId: options?.replaceFromMessageID,
|
|
12438
|
-
attachments: attachments.map((a) => ({
|
|
12439
|
-
filename: a.filename,
|
|
12440
|
-
mimeType: a.mimeType,
|
|
12441
|
-
sizeBytes: a.sizeBytes,
|
|
12442
|
-
base64Data: a.base64Data,
|
|
12443
|
-
url: a.url
|
|
12444
|
-
}))
|
|
12445
|
-
};
|
|
12446
13579
|
let connectionTimeoutID;
|
|
12447
13580
|
let connectionTimedOut = false;
|
|
12448
13581
|
try {
|
|
13582
|
+
const uploads = await Promise.all(
|
|
13583
|
+
attachments.map(
|
|
13584
|
+
(attachment, attachmentIndex) => this.ensureAttachmentUpload(attachment, { sessionId, attachmentIndex })
|
|
13585
|
+
)
|
|
13586
|
+
);
|
|
13587
|
+
const streamAttachments = this.assertUploadReferences(uploads);
|
|
13588
|
+
this.logAttachmentLifecycle("stream_send_with_upload_ids", {
|
|
13589
|
+
sessionId,
|
|
13590
|
+
attachmentCount: streamAttachments.length
|
|
13591
|
+
});
|
|
13592
|
+
const payload = {
|
|
13593
|
+
sessionId,
|
|
13594
|
+
content,
|
|
13595
|
+
debugMode: options?.debugMode ?? false,
|
|
13596
|
+
replaceFromMessageId: options?.replaceFromMessageID,
|
|
13597
|
+
attachments: streamAttachments
|
|
13598
|
+
};
|
|
12449
13599
|
const timeoutMs = this.config.timeout ?? 0;
|
|
12450
13600
|
if (timeoutMs > 0) {
|
|
12451
13601
|
connectionTimeoutID = setTimeout(() => {
|
|
@@ -12648,6 +13798,7 @@ exports.ErrorBoundary = ErrorBoundary;
|
|
|
12648
13798
|
exports.HttpDataSource = HttpDataSource;
|
|
12649
13799
|
exports.ImageModal = ImageModal;
|
|
12650
13800
|
exports.InlineQuestionForm = InlineQuestionForm;
|
|
13801
|
+
exports.InteractiveTableCard = InteractiveTableCard;
|
|
12651
13802
|
exports.IotaContextProvider = IotaContextProvider;
|
|
12652
13803
|
exports.ListItemSkeleton = ListItemSkeleton;
|
|
12653
13804
|
exports.LoadingSpinner = MemoizedLoadingSpinner;
|