@iota-uz/sdk 0.4.16 → 0.4.19

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.
@@ -2,7 +2,7 @@ import React, { createContext, lazy, memo, forwardRef, useState, useRef, useMemo
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import ReactApexChart from 'react-apexcharts';
4
4
  import ApexCharts from 'apexcharts';
5
- import { X, Bug, ArrowUp, ArrowDown, Stack, Paperclip, PaperPlaneRight, CircleNotch, ArrowUUpLeft, PencilSimple, Check, Bookmark, ArrowsClockwise, Archive, Trash, DotsThree, Warning, ArrowClockwise, Image, ArrowCounterClockwise, ImageBroken, CaretLeft, CaretRight, Info, CheckCircle, XCircle, Spinner, MagnifyingGlass, WarningCircle, CaretDown, Copy, FilePdf, FileXls, FileCsv, FileDoc, FileCode, FileText, File, ChartBar, DownloadSimple, Download, ChatCircleDots, PencilSimpleLine, ArrowLeft, PaperPlaneTilt, ArrowRight, Timer, Lightning, Database, Wrench, ClockCounterClockwise, Lightbulb, Package, Plus, ArrowsCounterClockwise, Gear, Users, List, CaretLineLeft, CaretLineRight, Code, ArrowSquareOut, SpinnerGap, FloppyDisk, Sidebar } from '@phosphor-icons/react';
5
+ import { X, Bug, ArrowUp, ArrowDown, Stack, Paperclip, PaperPlaneRight, CircleNotch, ArrowUUpLeft, PencilSimple, Check, Bookmark, ArrowsClockwise, Archive, Trash, DotsThree, Warning, ArrowClockwise, Image, ArrowCounterClockwise, ImageBroken, CaretLeft, CaretRight, Info, CheckCircle, XCircle, Spinner, MagnifyingGlass, WarningCircle, CaretDown, Copy, FilePdf, FileXls, FileCsv, FileDoc, FileCode, FileText, File as File$1, ChartBar, DownloadSimple, Download, ChatCircleDots, PencilSimpleLine, ArrowLeft, PaperPlaneTilt, ArrowRight, Timer, Lightning, Database, Wrench, ClockCounterClockwise, Lightbulb, Package, Plus, ArrowsCounterClockwise, Gear, Users, List, CaretLineLeft, CaretLineRight, Code, ArrowSquareOut, SpinnerGap, FloppyDisk, Sidebar } from '@phosphor-icons/react';
6
6
  import { Prism } from 'react-syntax-highlighter';
7
7
  import { vscDarkPlus, vs } from 'react-syntax-highlighter/dist/esm/styles/prism';
8
8
  import ReactMarkdown from 'react-markdown';
@@ -54,14 +54,15 @@ function useTranslation() {
54
54
  const { translations, language } = locale;
55
55
  const t = useCallback(
56
56
  (key2, params) => {
57
- let text = translations[key2] || key2;
57
+ const raw = translations[key2];
58
+ let text = typeof raw === "string" && raw.trim() ? raw : key2;
58
59
  if (params) {
59
60
  Object.keys(params).forEach((paramKey) => {
60
61
  const value = params[paramKey];
61
62
  text = text.replace(new RegExp(`{{${paramKey}}}`, "g"), String(value));
62
63
  });
63
64
  }
64
- return text;
65
+ return text.trim() ? text : key2;
65
66
  },
66
67
  [translations]
67
68
  );
@@ -1507,6 +1508,10 @@ var ChatMachine = class {
1507
1508
  }
1508
1509
  /** Sets turns from fetch, preserving pending user-only turns if server hasn't caught up. */
1509
1510
  _setTurnsFromFetch(fetchedTurns) {
1511
+ if (!Array.isArray(fetchedTurns)) {
1512
+ console.warn("[ChatMachine] Ignoring malformed turns payload from fetchSession");
1513
+ return;
1514
+ }
1510
1515
  const prev = this.state.messaging.turns;
1511
1516
  const hasPendingUserOnly = prev.length > 0 && !prev[prev.length - 1].assistantTurn;
1512
1517
  if (hasPendingUserOnly && (!fetchedTurns || fetchedTurns.length === 0)) {
@@ -1896,6 +1901,7 @@ var ChatMachine = class {
1896
1901
  const curSessionId = this.state.session.currentSessionId;
1897
1902
  const curPendingQuestion = this.state.messaging.pendingQuestion;
1898
1903
  if (!curSessionId || !curPendingQuestion) return;
1904
+ const previousTurns = this.state.messaging.turns;
1899
1905
  this._updateMessaging({ loading: true });
1900
1906
  this._updateSession({ error: null, errorRetryable: false });
1901
1907
  const previousPendingQuestion = curPendingQuestion;
@@ -1913,19 +1919,28 @@ var ChatMachine = class {
1913
1919
  const fetchResult = await this.dataSource.fetchSession(curSessionId);
1914
1920
  if (this.disposed) return;
1915
1921
  if (fetchResult) {
1916
- this._updateMessaging({
1917
- turns: fetchResult.turns,
1918
- pendingQuestion: fetchResult.pendingQuestion || null
1919
- });
1922
+ this._updateSession({ session: fetchResult.session });
1923
+ this._updateMessaging({ pendingQuestion: fetchResult.pendingQuestion || null });
1924
+ const hasMalformedRefresh = previousTurns.length > 0 && Array.isArray(fetchResult.turns) && fetchResult.turns.length === 0;
1925
+ if (hasMalformedRefresh) {
1926
+ console.warn("[ChatMachine] Preserving previous turns due to empty post-HITL refetch payload", {
1927
+ sessionId: curSessionId,
1928
+ previousTurnCount: previousTurns.length
1929
+ });
1930
+ this._updateSession({
1931
+ error: "Failed to fully refresh session. Showing last known messages.",
1932
+ errorRetryable: true
1933
+ });
1934
+ } else {
1935
+ this._setTurnsFromFetch(fetchResult.turns);
1936
+ }
1920
1937
  } else {
1921
- this._updateMessaging({ pendingQuestion: previousPendingQuestion });
1922
- this._updateSession({ error: "Failed to load updated session", errorRetryable: false });
1938
+ this._updateSession({ error: "Failed to load updated session", errorRetryable: true });
1923
1939
  }
1924
1940
  } catch (fetchErr) {
1925
1941
  if (this.disposed) return;
1926
- this._updateMessaging({ pendingQuestion: previousPendingQuestion });
1927
1942
  const normalized = normalizeRPCError(fetchErr, "Failed to load updated session");
1928
- this._updateSession({ error: normalized.userMessage, errorRetryable: normalized.retryable });
1943
+ this._updateSession({ error: normalized.userMessage, errorRetryable: true });
1929
1944
  }
1930
1945
  }
1931
1946
  } else {
@@ -2447,7 +2462,7 @@ function getFileVisual(mimeType, filename) {
2447
2462
  };
2448
2463
  }
2449
2464
  return {
2450
- icon: File,
2465
+ icon: File$1,
2451
2466
  iconColor: "text-gray-400 dark:text-gray-500",
2452
2467
  bgColor: "bg-gray-100 dark:bg-gray-800",
2453
2468
  label: (mime.split("/")[1] || "FILE").toUpperCase().slice(0, 4)
@@ -3285,7 +3300,7 @@ function InlineQuestionForm({ pendingQuestion }) {
3285
3300
  const [currentStep, setCurrentStep] = useState(0);
3286
3301
  const [answers, setAnswers] = useState({});
3287
3302
  const [otherTexts, setOtherTexts] = useState({});
3288
- const questions = pendingQuestion.questions;
3303
+ const questions = Array.isArray(pendingQuestion.questions) ? pendingQuestion.questions : [];
3289
3304
  const currentQuestion = questions[currentStep];
3290
3305
  const isLastStep = currentStep === questions.length - 1;
3291
3306
  const isFirstStep = currentStep === 0;
@@ -3379,9 +3394,31 @@ function InlineQuestionForm({ pendingQuestion }) {
3379
3394
  e.preventDefault();
3380
3395
  handleNext();
3381
3396
  };
3382
- if (!currentQuestion) return null;
3397
+ if (!currentQuestion) {
3398
+ return /* @__PURE__ */ 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: [
3399
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-800 dark:text-gray-200", children: t("BiChat.Error.SomethingWentWrong") }),
3400
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: t("BiChat.Error.UnexpectedError") }),
3401
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxs(
3402
+ "button",
3403
+ {
3404
+ type: "button",
3405
+ onClick: handleRejectPendingQuestion,
3406
+ disabled: loading,
3407
+ 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",
3408
+ children: [
3409
+ /* @__PURE__ */ jsx(X, { size: 14, weight: "bold" }),
3410
+ t("BiChat.InlineQuestion.Dismiss")
3411
+ ]
3412
+ }
3413
+ ) })
3414
+ ] });
3415
+ }
3383
3416
  const isMultiSelect = currentQuestion.type === "MULTIPLE_CHOICE";
3384
- const options = currentQuestion.options || [];
3417
+ const options = (currentQuestion.options || []).filter((option) => Boolean(option && typeof option.label === "string")).map((option, index) => ({
3418
+ id: option.id || `${currentQuestion.id}-option-${index}`,
3419
+ label: option.label,
3420
+ value: option.value || option.label
3421
+ }));
3385
3422
  const isOtherSelected = currentAnswer?.customText !== void 0;
3386
3423
  const canProceed = isCurrentAnswerValid();
3387
3424
  return /* @__PURE__ */ 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__ */ jsxs("form", { onSubmit: handleSubmit, children: [
@@ -4295,7 +4332,8 @@ function TurnBubble({
4295
4332
  userTurn: classNames?.userTurn ?? defaultClassNames3.userTurn,
4296
4333
  assistantTurn: classNames?.assistantTurn ?? defaultClassNames3.assistantTurn
4297
4334
  };
4298
- const isSystemSummaryTurn = turn.userTurn.content.trim() === "" && turn.assistantTurn?.role === "system";
4335
+ const userContent = typeof turn.userTurn?.content === "string" ? turn.userTurn.content : "";
4336
+ const isSystemSummaryTurn = userContent.trim() === "" && turn.assistantTurn?.role === "system";
4299
4337
  return /* @__PURE__ */ jsxs("div", { className: classes.root, "data-turn-id": turn.id, children: [
4300
4338
  !isSystemSummaryTurn && /* @__PURE__ */ jsx("div", { className: classes.userTurn, children: renderUserTurn ? renderUserTurn(turn) : /* @__PURE__ */ jsx(
4301
4339
  UserTurnView,
@@ -6062,6 +6100,8 @@ function WarningBox({ message }) {
6062
6100
  }
6063
6101
  function ArtifactActions({ url }) {
6064
6102
  const { t } = useTranslation();
6103
+ const openLabel = t("BiChat.Artifacts.OpenInNewTab");
6104
+ const downloadLabel = t("BiChat.Artifacts.Download");
6065
6105
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
6066
6106
  /* @__PURE__ */ jsxs(
6067
6107
  "a",
@@ -6072,7 +6112,7 @@ function ArtifactActions({ url }) {
6072
6112
  className: "inline-flex items-center gap-2 rounded-lg border border-gray-200 px-3 py-1.5 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:text-gray-200 dark:hover:bg-gray-800",
6073
6113
  children: [
6074
6114
  /* @__PURE__ */ jsx(ArrowSquareOut, { className: "h-3.5 w-3.5", weight: "bold" }),
6075
- t("BiChat.Artifacts.OpenInNewTab")
6115
+ openLabel
6076
6116
  ]
6077
6117
  }
6078
6118
  ),
@@ -6086,7 +6126,7 @@ function ArtifactActions({ url }) {
6086
6126
  className: "inline-flex items-center gap-2 rounded-lg bg-primary-600 px-3 py-1.5 text-xs font-medium text-white shadow-sm transition-colors hover:bg-primary-700",
6087
6127
  children: [
6088
6128
  /* @__PURE__ */ jsx(DownloadSimple, { className: "h-3.5 w-3.5", weight: "bold" }),
6089
- t("BiChat.Artifacts.Download")
6129
+ downloadLabel
6090
6130
  ]
6091
6131
  }
6092
6132
  )
@@ -7406,6 +7446,7 @@ var EditableText = forwardRef(
7406
7446
  setIsEditing(false);
7407
7447
  };
7408
7448
  const handleKeyDown = (e) => {
7449
+ e.stopPropagation();
7409
7450
  if (e.key === "Enter") {
7410
7451
  e.preventDefault();
7411
7452
  handleSave();
@@ -7439,6 +7480,7 @@ var EditableText = forwardRef(
7439
7480
  value: editValue,
7440
7481
  onChange: (e) => setEditValue(e.target.value),
7441
7482
  onKeyDown: handleKeyDown,
7483
+ onKeyUp: (e) => e.stopPropagation(),
7442
7484
  onBlur: handleBlur,
7443
7485
  maxLength,
7444
7486
  placeholder: resolvedPlaceholder,
@@ -8069,12 +8111,63 @@ function DefaultErrorContent({
8069
8111
  ] })
8070
8112
  ] });
8071
8113
  }
8114
+ function StaticEmergencyErrorContent({
8115
+ error,
8116
+ onReset
8117
+ }) {
8118
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col items-center justify-center p-8 text-center min-h-[200px]", children: /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col items-center", children: [
8119
+ /* @__PURE__ */ jsxs("div", { className: "relative mb-5", children: [
8120
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-full bg-red-100 scale-150 blur-md" }),
8121
+ /* @__PURE__ */ 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__ */ jsx(WarningCircle, { size: 28, className: "text-red-500", weight: "fill" }) })
8122
+ ] }),
8123
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-gray-900 mb-1.5", children: "Something went wrong" }),
8124
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mb-5 max-w-md leading-relaxed", children: error?.message || "An unexpected UI error occurred." }),
8125
+ onReset && /* @__PURE__ */ jsxs(
8126
+ "button",
8127
+ {
8128
+ type: "button",
8129
+ onClick: onReset,
8130
+ 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",
8131
+ children: [
8132
+ /* @__PURE__ */ jsx(ArrowClockwise, { size: 16, weight: "bold" }),
8133
+ "Try again"
8134
+ ]
8135
+ }
8136
+ )
8137
+ ] }) });
8138
+ }
8139
+ var FallbackGuard = class extends Component {
8140
+ constructor(props) {
8141
+ super(props);
8142
+ this.state = { fallbackFailed: false };
8143
+ }
8144
+ static getDerivedStateFromError() {
8145
+ return { fallbackFailed: true };
8146
+ }
8147
+ componentDidCatch(error, errorInfo) {
8148
+ this.props.onFallbackError?.(error, errorInfo);
8149
+ }
8150
+ render() {
8151
+ if (this.state.fallbackFailed) {
8152
+ return /* @__PURE__ */ jsx(StaticEmergencyErrorContent, { error: this.props.primaryError, onReset: this.props.onReset });
8153
+ }
8154
+ return this.props.renderFallback();
8155
+ }
8156
+ };
8072
8157
  var ErrorBoundary = class extends Component {
8073
8158
  constructor(props) {
8074
8159
  super(props);
8075
8160
  this.handleReset = () => {
8076
8161
  this.setState({ hasError: false, error: null });
8077
8162
  };
8163
+ this.handleFallbackError = (error, errorInfo) => {
8164
+ console.error("React Error Boundary fallback crashed:", {
8165
+ primaryError: this.state.error,
8166
+ fallbackError: error,
8167
+ errorInfo
8168
+ });
8169
+ this.props.onError?.(error, errorInfo);
8170
+ };
8078
8171
  this.state = { hasError: false, error: null };
8079
8172
  }
8080
8173
  static getDerivedStateFromError(error) {
@@ -8086,13 +8179,24 @@ var ErrorBoundary = class extends Component {
8086
8179
  }
8087
8180
  render() {
8088
8181
  if (this.state.hasError) {
8089
- if (this.props.fallback) {
8090
- if (typeof this.props.fallback === "function") {
8091
- return this.props.fallback(this.state.error, this.handleReset);
8092
- }
8093
- return this.props.fallback;
8094
- }
8095
- return /* @__PURE__ */ jsx(DefaultErrorContent, { error: this.state.error, onReset: this.handleReset });
8182
+ return /* @__PURE__ */ jsx(
8183
+ FallbackGuard,
8184
+ {
8185
+ primaryError: this.state.error,
8186
+ onReset: this.handleReset,
8187
+ onFallbackError: this.handleFallbackError,
8188
+ renderFallback: () => {
8189
+ if (this.props.fallback) {
8190
+ if (typeof this.props.fallback === "function") {
8191
+ return this.props.fallback(this.state.error, this.handleReset);
8192
+ }
8193
+ return this.props.fallback;
8194
+ }
8195
+ return /* @__PURE__ */ jsx(DefaultErrorContent, { error: this.state.error, onReset: this.handleReset });
8196
+ }
8197
+ },
8198
+ `${this.state.error?.name ?? "Error"}:${this.state.error?.message ?? ""}`
8199
+ );
8096
8200
  }
8097
8201
  return this.props.children;
8098
8202
  }
@@ -8485,6 +8589,9 @@ var SessionItem = memo(
8485
8589
  onSelect(session.id);
8486
8590
  },
8487
8591
  onKeyDown: (e) => {
8592
+ const target = e.target;
8593
+ const isFromEditable = !!target?.closest('input, textarea, [contenteditable="true"]');
8594
+ if (isFromEditable) return;
8488
8595
  if (e.key === "Enter" || e.key === " ") {
8489
8596
  e.preventDefault();
8490
8597
  onSelect(session.id);
@@ -9243,6 +9350,15 @@ function Sidebar2({
9243
9350
  useEffect(() => {
9244
9351
  fetchSessions();
9245
9352
  }, [fetchSessions, refreshKey]);
9353
+ useEffect(() => {
9354
+ const handleSessionsUpdated = () => {
9355
+ setRefreshKey((k) => k + 1);
9356
+ };
9357
+ window.addEventListener("bichat:sessions-updated", handleSessionsUpdated);
9358
+ return () => {
9359
+ window.removeEventListener("bichat:sessions-updated", handleSessionsUpdated);
9360
+ };
9361
+ }, []);
9246
9362
  useEffect(() => {
9247
9363
  if (!activeSessionId) {
9248
9364
  refreshForActiveSessionRef.current = null;
@@ -9617,13 +9733,14 @@ function Sidebar2({
9617
9733
  anchor: "top start",
9618
9734
  className: "w-48 bg-white/95 dark:bg-gray-900/95 backdrop-blur-lg rounded-xl shadow-lg border border-gray-200/80 dark:border-gray-700/60 z-30 [--anchor-gap:8px] mb-1 p-1.5",
9619
9735
  children: [
9620
- onArchivedView && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxs(
9736
+ onArchivedView && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus, close }) => /* @__PURE__ */ jsxs(
9621
9737
  "button",
9622
9738
  {
9623
9739
  onClick: (e) => {
9624
9740
  e.preventDefault();
9625
9741
  e.stopPropagation();
9626
9742
  onArchivedView();
9743
+ close();
9627
9744
  },
9628
9745
  className: `cursor-pointer flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-[13px] text-gray-600 dark:text-gray-300 transition-colors ${focus ? "bg-gray-100 dark:bg-gray-800/70" : ""}`,
9629
9746
  "aria-label": t("BiChat.Sidebar.ArchivedChats"),
@@ -9633,13 +9750,14 @@ function Sidebar2({
9633
9750
  ]
9634
9751
  }
9635
9752
  ) }),
9636
- showAllChatsTab && activeTab !== "all-chats" && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxs(
9753
+ showAllChatsTab && activeTab !== "all-chats" && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus, close }) => /* @__PURE__ */ jsxs(
9637
9754
  "button",
9638
9755
  {
9639
9756
  onClick: (e) => {
9640
9757
  e.preventDefault();
9641
9758
  e.stopPropagation();
9642
9759
  setActiveTab("all-chats");
9760
+ close();
9643
9761
  },
9644
9762
  className: `cursor-pointer flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-[13px] text-gray-600 dark:text-gray-300 transition-colors ${focus ? "bg-gray-100 dark:bg-gray-800/70" : ""}`,
9645
9763
  "aria-label": t("BiChat.Sidebar.AllChats"),
@@ -9649,13 +9767,14 @@ function Sidebar2({
9649
9767
  ]
9650
9768
  }
9651
9769
  ) }),
9652
- showAllChatsTab && activeTab === "all-chats" && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxs(
9770
+ showAllChatsTab && activeTab === "all-chats" && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus, close }) => /* @__PURE__ */ jsxs(
9653
9771
  "button",
9654
9772
  {
9655
9773
  onClick: (e) => {
9656
9774
  e.preventDefault();
9657
9775
  e.stopPropagation();
9658
9776
  setActiveTab("my-chats");
9777
+ close();
9659
9778
  },
9660
9779
  className: `cursor-pointer flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-[13px] text-gray-600 dark:text-gray-300 transition-colors ${focus ? "bg-gray-100 dark:bg-gray-800/70" : ""}`,
9661
9780
  "aria-label": t("BiChat.Sidebar.MyChats"),
@@ -9770,7 +9889,11 @@ function ArchivedChatList({
9770
9889
  const confirmRestore = async () => {
9771
9890
  if (!sessionToRestore) return;
9772
9891
  try {
9773
- await dataSource.unarchiveSession(sessionToRestore);
9892
+ const restoredSessionID = sessionToRestore;
9893
+ await dataSource.unarchiveSession(restoredSessionID);
9894
+ window.dispatchEvent(new CustomEvent("bichat:sessions-updated", {
9895
+ detail: { reason: "restored", sessionId: restoredSessionID }
9896
+ }));
9774
9897
  setRefreshKey((k) => k + 1);
9775
9898
  toast.success(t("BiChat.Archived.ChatRestoredSuccessfully"));
9776
9899
  } catch (err) {
@@ -12032,6 +12155,7 @@ function toSessionArtifact(artifact) {
12032
12155
  id: artifact.id,
12033
12156
  sessionId: artifact.sessionId,
12034
12157
  messageId: artifact.messageId,
12158
+ uploadId: artifact.uploadId,
12035
12159
  type: artifact.type,
12036
12160
  name: artifact.name,
12037
12161
  description: artifact.description,
@@ -12042,21 +12166,272 @@ function toSessionArtifact(artifact) {
12042
12166
  createdAt: artifact.createdAt
12043
12167
  };
12044
12168
  }
12045
- function toPendingQuestion(rpc) {
12046
- if (!rpc) return null;
12047
- const questions = (rpc.questions || []).map((q) => ({
12048
- id: q.id,
12049
- text: q.text,
12050
- type: q.type,
12051
- options: (q.options || []).map((o) => ({
12052
- id: o.id,
12053
- label: o.label,
12054
- value: o.label
12055
- }))
12056
- }));
12169
+ function warnMalformedSessionPayload(message, details) {
12170
+ console.warn(`[BiChat] ${message}`, details || {});
12171
+ }
12172
+ function readString(value, fallback = "") {
12173
+ return typeof value === "string" ? value : fallback;
12174
+ }
12175
+ function readNonEmptyString(value) {
12176
+ if (typeof value !== "string") return null;
12177
+ const trimmed = value.trim();
12178
+ return trimmed.length > 0 ? trimmed : null;
12179
+ }
12180
+ function readFiniteNumber(value, fallback = 0) {
12181
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
12182
+ }
12183
+ function readOptionalFiniteNumber(value) {
12184
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
12185
+ }
12186
+ function normalizeQuestionType(rawType) {
12187
+ const normalized = readString(rawType).trim().toUpperCase().replace(/[\s-]+/g, "_");
12188
+ return normalized === "MULTIPLE_CHOICE" ? "MULTIPLE_CHOICE" : "SINGLE_CHOICE";
12189
+ }
12190
+ function normalizeMessageRole(rawRole) {
12191
+ const normalized = readString(rawRole).trim().toLowerCase();
12192
+ if (normalized === "user" /* User */) return "user" /* User */;
12193
+ if (normalized === "system" /* System */) return "system" /* System */;
12194
+ if (normalized === "tool" /* Tool */) return "tool" /* Tool */;
12195
+ return "assistant" /* Assistant */;
12196
+ }
12197
+ function sanitizeAttachment(rawAttachment, turnId, index) {
12198
+ if (!isRecord(rawAttachment)) {
12199
+ warnMalformedSessionPayload("Dropped malformed attachment entry", { turnId, index });
12200
+ return null;
12201
+ }
12202
+ const filename = readString(rawAttachment.filename, "attachment");
12203
+ const mimeType = readString(rawAttachment.mimeType, "application/octet-stream");
12204
+ const id = readNonEmptyString(rawAttachment.id) || void 0;
12205
+ const clientKey = readNonEmptyString(rawAttachment.clientKey) || id || `${turnId}-attachment-${index}`;
12057
12206
  return {
12058
- id: rpc.checkpointId,
12059
- turnId: rpc.turnId || "",
12207
+ id,
12208
+ clientKey,
12209
+ filename,
12210
+ mimeType,
12211
+ sizeBytes: readFiniteNumber(rawAttachment.sizeBytes),
12212
+ uploadId: readOptionalFiniteNumber(rawAttachment.uploadId),
12213
+ base64Data: readNonEmptyString(rawAttachment.base64Data) || void 0,
12214
+ url: readNonEmptyString(rawAttachment.url) || void 0,
12215
+ preview: readNonEmptyString(rawAttachment.preview) || void 0
12216
+ };
12217
+ }
12218
+ function sanitizeUserAttachments(rawAttachments, turnId) {
12219
+ if (!Array.isArray(rawAttachments)) return [];
12220
+ const result = [];
12221
+ for (let i = 0; i < rawAttachments.length; i++) {
12222
+ const sanitized = sanitizeAttachment(rawAttachments[i], turnId, i);
12223
+ if (sanitized) result.push(sanitized);
12224
+ }
12225
+ return result;
12226
+ }
12227
+ function sanitizeAssistantArtifacts(rawArtifacts, turnId) {
12228
+ if (!Array.isArray(rawArtifacts)) return [];
12229
+ const artifacts = [];
12230
+ for (let i = 0; i < rawArtifacts.length; i++) {
12231
+ const raw = rawArtifacts[i];
12232
+ if (!isRecord(raw)) {
12233
+ warnMalformedSessionPayload("Dropped malformed assistant artifact", { turnId, index: i });
12234
+ continue;
12235
+ }
12236
+ const type = readString(raw.type).toLowerCase();
12237
+ if (type !== "excel" && type !== "pdf") {
12238
+ continue;
12239
+ }
12240
+ const url = readNonEmptyString(raw.url);
12241
+ if (!url) {
12242
+ warnMalformedSessionPayload("Dropped assistant artifact without url", { turnId, index: i });
12243
+ continue;
12244
+ }
12245
+ artifacts.push({
12246
+ type,
12247
+ filename: readString(raw.filename, "download"),
12248
+ url,
12249
+ sizeReadable: readNonEmptyString(raw.sizeReadable) || void 0,
12250
+ rowCount: typeof raw.rowCount === "number" && Number.isFinite(raw.rowCount) ? raw.rowCount : void 0,
12251
+ description: readNonEmptyString(raw.description) || void 0
12252
+ });
12253
+ }
12254
+ return artifacts;
12255
+ }
12256
+ function sanitizeAssistantTurn(rawAssistantTurn, fallbackCreatedAt, turnId) {
12257
+ if (rawAssistantTurn == null) return void 0;
12258
+ if (!isRecord(rawAssistantTurn)) {
12259
+ warnMalformedSessionPayload("Dropped malformed assistant turn payload", { turnId });
12260
+ return void 0;
12261
+ }
12262
+ const assistantID = readNonEmptyString(rawAssistantTurn.id);
12263
+ if (!assistantID) {
12264
+ warnMalformedSessionPayload("Dropped assistant turn without id", { turnId });
12265
+ return void 0;
12266
+ }
12267
+ const citations = Array.isArray(rawAssistantTurn.citations) ? rawAssistantTurn.citations.filter((item) => isRecord(item)).map((item, index) => ({
12268
+ id: readString(item.id, `${assistantID}-citation-${index}`),
12269
+ type: readString(item.type),
12270
+ title: readString(item.title),
12271
+ url: readString(item.url),
12272
+ startIndex: readFiniteNumber(item.startIndex),
12273
+ endIndex: readFiniteNumber(item.endIndex),
12274
+ excerpt: readNonEmptyString(item.excerpt) || void 0
12275
+ })) : [];
12276
+ const toolCalls = Array.isArray(rawAssistantTurn.toolCalls) ? rawAssistantTurn.toolCalls.filter((item) => isRecord(item)).map((item, index) => ({
12277
+ id: readString(item.id, `${assistantID}-tool-${index}`),
12278
+ name: readString(item.name),
12279
+ arguments: readString(item.arguments),
12280
+ result: readNonEmptyString(item.result) || void 0,
12281
+ error: readNonEmptyString(item.error) || void 0,
12282
+ durationMs: readFiniteNumber(item.durationMs)
12283
+ })) : [];
12284
+ const codeOutputs = Array.isArray(rawAssistantTurn.codeOutputs) ? rawAssistantTurn.codeOutputs.filter((item) => isRecord(item)).map((item) => ({
12285
+ type: (() => {
12286
+ const normalizedType = readString(item.type, "text").toLowerCase();
12287
+ if (normalizedType === "image" || normalizedType === "error") return normalizedType;
12288
+ return "text";
12289
+ })(),
12290
+ content: readString(item.content),
12291
+ filename: readNonEmptyString(item.filename) || void 0,
12292
+ mimeType: readNonEmptyString(item.mimeType) || void 0,
12293
+ sizeBytes: readOptionalFiniteNumber(item.sizeBytes)
12294
+ })) : [];
12295
+ const debugTrace = isRecord(rawAssistantTurn.debug) ? {
12296
+ generationMs: readOptionalFiniteNumber(rawAssistantTurn.debug.generationMs),
12297
+ usage: isRecord(rawAssistantTurn.debug.usage) ? {
12298
+ promptTokens: readFiniteNumber(rawAssistantTurn.debug.usage.promptTokens),
12299
+ completionTokens: readFiniteNumber(rawAssistantTurn.debug.usage.completionTokens),
12300
+ totalTokens: readFiniteNumber(rawAssistantTurn.debug.usage.totalTokens),
12301
+ cachedTokens: readOptionalFiniteNumber(rawAssistantTurn.debug.usage.cachedTokens),
12302
+ cost: readOptionalFiniteNumber(rawAssistantTurn.debug.usage.cost)
12303
+ } : void 0,
12304
+ tools: Array.isArray(rawAssistantTurn.debug.tools) ? rawAssistantTurn.debug.tools.filter((tool) => isRecord(tool)).map((tool) => ({
12305
+ callId: readNonEmptyString(tool.callId) || void 0,
12306
+ name: readString(tool.name),
12307
+ arguments: readNonEmptyString(tool.arguments) || void 0,
12308
+ result: readNonEmptyString(tool.result) || void 0,
12309
+ error: readNonEmptyString(tool.error) || void 0,
12310
+ durationMs: readOptionalFiniteNumber(tool.durationMs)
12311
+ })) : []
12312
+ } : void 0;
12313
+ return {
12314
+ id: assistantID,
12315
+ role: normalizeMessageRole(rawAssistantTurn.role),
12316
+ content: readString(rawAssistantTurn.content),
12317
+ explanation: readNonEmptyString(rawAssistantTurn.explanation) || void 0,
12318
+ citations,
12319
+ toolCalls,
12320
+ chartData: void 0,
12321
+ artifacts: sanitizeAssistantArtifacts(rawAssistantTurn.artifacts, turnId),
12322
+ codeOutputs,
12323
+ debug: debugTrace,
12324
+ createdAt: readString(rawAssistantTurn.createdAt, fallbackCreatedAt)
12325
+ };
12326
+ }
12327
+ function sanitizeConversationTurn(rawTurn, index, fallbackSessionID) {
12328
+ if (!isRecord(rawTurn)) {
12329
+ warnMalformedSessionPayload("Dropped malformed turn payload (not an object)", { index });
12330
+ return null;
12331
+ }
12332
+ if (!isRecord(rawTurn.userTurn)) {
12333
+ warnMalformedSessionPayload("Dropped malformed turn payload (missing user turn)", { index });
12334
+ return null;
12335
+ }
12336
+ const userTurnID = readNonEmptyString(rawTurn.userTurn.id);
12337
+ if (!userTurnID) {
12338
+ warnMalformedSessionPayload("Dropped malformed turn payload (missing user turn id)", { index });
12339
+ return null;
12340
+ }
12341
+ const turnID = readString(rawTurn.id, userTurnID);
12342
+ const createdAt = readString(
12343
+ rawTurn.createdAt,
12344
+ readString(rawTurn.userTurn.createdAt, (/* @__PURE__ */ new Date()).toISOString())
12345
+ );
12346
+ return {
12347
+ id: turnID,
12348
+ sessionId: readString(rawTurn.sessionId, fallbackSessionID),
12349
+ userTurn: {
12350
+ id: userTurnID,
12351
+ content: readString(rawTurn.userTurn.content),
12352
+ attachments: sanitizeUserAttachments(rawTurn.userTurn.attachments, turnID),
12353
+ createdAt: readString(rawTurn.userTurn.createdAt, createdAt)
12354
+ },
12355
+ assistantTurn: sanitizeAssistantTurn(rawTurn.assistantTurn, createdAt, turnID),
12356
+ createdAt
12357
+ };
12358
+ }
12359
+ function sanitizeConversationTurns(rawTurns, sessionID) {
12360
+ if (!Array.isArray(rawTurns)) {
12361
+ warnMalformedSessionPayload("Session payload contained non-array turns field", { sessionID });
12362
+ return [];
12363
+ }
12364
+ const turns = [];
12365
+ let dropped = 0;
12366
+ for (let i = 0; i < rawTurns.length; i++) {
12367
+ const sanitizedTurn = sanitizeConversationTurn(rawTurns[i], i, sessionID);
12368
+ if (sanitizedTurn) {
12369
+ turns.push(sanitizedTurn);
12370
+ } else {
12371
+ dropped++;
12372
+ }
12373
+ }
12374
+ if (dropped > 0) {
12375
+ warnMalformedSessionPayload("Dropped malformed turns from session payload", {
12376
+ sessionID,
12377
+ dropped,
12378
+ total: rawTurns.length
12379
+ });
12380
+ }
12381
+ return turns;
12382
+ }
12383
+ function sanitizePendingQuestion(rawPendingQuestion, sessionID) {
12384
+ if (!rawPendingQuestion) return null;
12385
+ const checkpointID = readNonEmptyString(rawPendingQuestion.checkpointId);
12386
+ if (!checkpointID) {
12387
+ warnMalformedSessionPayload("Dropped malformed pendingQuestion without checkpointId", { sessionID });
12388
+ return null;
12389
+ }
12390
+ if (!Array.isArray(rawPendingQuestion.questions)) {
12391
+ warnMalformedSessionPayload("Pending question had non-array questions payload", {
12392
+ sessionID,
12393
+ checkpointID
12394
+ });
12395
+ }
12396
+ const questions = Array.isArray(rawPendingQuestion.questions) ? rawPendingQuestion.questions.filter((question) => {
12397
+ if (!question || !isRecord(question)) {
12398
+ warnMalformedSessionPayload("Dropped malformed question from pendingQuestion", {
12399
+ sessionID,
12400
+ checkpointID
12401
+ });
12402
+ return false;
12403
+ }
12404
+ return true;
12405
+ }).map((question, index) => {
12406
+ const questionID = readString(question.id, `${checkpointID}-q-${index}`);
12407
+ const options = Array.isArray(question.options) ? question.options.filter((option) => {
12408
+ if (!option || !isRecord(option)) {
12409
+ warnMalformedSessionPayload("Dropped malformed pendingQuestion option", {
12410
+ sessionID,
12411
+ checkpointID,
12412
+ questionID
12413
+ });
12414
+ return false;
12415
+ }
12416
+ return true;
12417
+ }).map((option, optionIndex) => {
12418
+ const label = readString(option.label);
12419
+ return {
12420
+ id: readString(option.id, `${questionID}-opt-${optionIndex}`),
12421
+ label,
12422
+ value: label
12423
+ };
12424
+ }) : [];
12425
+ return {
12426
+ id: questionID,
12427
+ text: readString(question.text),
12428
+ type: normalizeQuestionType(question.type),
12429
+ options
12430
+ };
12431
+ }) : [];
12432
+ return {
12433
+ id: checkpointID,
12434
+ turnId: readString(rawPendingQuestion.turnId),
12060
12435
  questions,
12061
12436
  status: "PENDING"
12062
12437
  };
@@ -12252,7 +12627,8 @@ var HttpDataSource = class {
12252
12627
  this.abortController = null;
12253
12628
  this.config = {
12254
12629
  streamEndpoint: "/stream",
12255
- timeout: 3e4,
12630
+ uploadEndpoint: "/api/uploads",
12631
+ timeout: 12e4,
12256
12632
  ...config
12257
12633
  };
12258
12634
  if (config.navigateToSession) {
@@ -12287,6 +12663,83 @@ var HttpDataSource = class {
12287
12663
  }
12288
12664
  return headers;
12289
12665
  }
12666
+ createUploadHeaders(additionalHeaders) {
12667
+ const headers = new Headers({
12668
+ ...this.config.headers,
12669
+ ...additionalHeaders
12670
+ });
12671
+ const csrfToken = this.getCSRFToken();
12672
+ if (csrfToken) {
12673
+ headers.set("X-CSRF-Token", csrfToken);
12674
+ }
12675
+ headers.delete("Content-Type");
12676
+ return headers;
12677
+ }
12678
+ async uploadFile(file) {
12679
+ const formData = new FormData();
12680
+ formData.append("file", file);
12681
+ const response = await fetch(`${this.config.baseUrl}${this.config.uploadEndpoint}`, {
12682
+ method: "POST",
12683
+ headers: this.createUploadHeaders(),
12684
+ body: formData
12685
+ });
12686
+ let payload = null;
12687
+ try {
12688
+ payload = await response.json();
12689
+ } catch {
12690
+ payload = null;
12691
+ }
12692
+ if (!response.ok) {
12693
+ const errorMessage = isRecord(payload) && typeof payload.error === "string" ? payload.error : `Upload failed: HTTP ${response.status}`;
12694
+ throw new Error(errorMessage);
12695
+ }
12696
+ if (!isRecord(payload) || typeof payload.id !== "number" || payload.id <= 0) {
12697
+ throw new Error("Upload failed: invalid response payload");
12698
+ }
12699
+ return {
12700
+ id: payload.id,
12701
+ url: typeof payload.url === "string" ? payload.url : "",
12702
+ path: typeof payload.path === "string" ? payload.path : "",
12703
+ name: typeof payload.name === "string" ? payload.name : file.name,
12704
+ mimetype: typeof payload.mimetype === "string" ? payload.mimetype : file.type,
12705
+ size: typeof payload.size === "number" && Number.isFinite(payload.size) ? payload.size : file.size
12706
+ };
12707
+ }
12708
+ async attachmentToFile(attachment) {
12709
+ if (attachment.base64Data && attachment.base64Data.trim().length > 0) {
12710
+ const base64Data = attachment.base64Data.trim();
12711
+ const dataUrl = base64Data.startsWith("data:") ? base64Data : `data:${attachment.mimeType || "application/octet-stream"};base64,${base64Data}`;
12712
+ const blob = await fetch(dataUrl).then((response) => response.blob());
12713
+ return new File([blob], attachment.filename, {
12714
+ type: attachment.mimeType || blob.type || "application/octet-stream"
12715
+ });
12716
+ }
12717
+ if (attachment.url) {
12718
+ const response = await fetch(attachment.url);
12719
+ if (!response.ok) {
12720
+ throw new Error(`Failed to read attachment source: HTTP ${response.status}`);
12721
+ }
12722
+ const blob = await response.blob();
12723
+ return new File([blob], attachment.filename, {
12724
+ type: attachment.mimeType || blob.type || "application/octet-stream"
12725
+ });
12726
+ }
12727
+ throw new Error(`Attachment "${attachment.filename}" has no uploadable data`);
12728
+ }
12729
+ async ensureAttachmentUpload(attachment) {
12730
+ if (typeof attachment.uploadId === "number" && attachment.uploadId > 0) {
12731
+ return {
12732
+ id: attachment.uploadId,
12733
+ url: attachment.url || "",
12734
+ path: "",
12735
+ name: attachment.filename,
12736
+ mimetype: attachment.mimeType,
12737
+ size: attachment.sizeBytes
12738
+ };
12739
+ }
12740
+ const file = await this.attachmentToFile(attachment);
12741
+ return this.uploadFile(file);
12742
+ }
12290
12743
  async callRPC(method, params) {
12291
12744
  return this.rpc.callTyped(method, params);
12292
12745
  }
@@ -12309,11 +12762,22 @@ var HttpDataSource = class {
12309
12762
  return { artifacts: [], hasMore: false, nextOffset: 0 };
12310
12763
  })
12311
12764
  ]);
12312
- const turns = attachArtifactsToTurns(normalizeTurns(data.turns), artifactsData.artifacts || []);
12765
+ const sanitizedTurns = sanitizeConversationTurns(data.turns, id);
12766
+ const turns = attachArtifactsToTurns(
12767
+ normalizeTurns(sanitizedTurns),
12768
+ artifactsData.artifacts || []
12769
+ );
12770
+ const pendingQuestion = sanitizePendingQuestion(data.pendingQuestion, id);
12771
+ if (data.pendingQuestion && pendingQuestion && pendingQuestion.questions.length === 0) {
12772
+ warnMalformedSessionPayload("Pending question normalized to zero renderable questions", {
12773
+ sessionID: id,
12774
+ checkpointID: pendingQuestion.id
12775
+ });
12776
+ }
12313
12777
  return {
12314
12778
  session: toSession(data.session),
12315
12779
  turns,
12316
- pendingQuestion: toPendingQuestion(data.pendingQuestion)
12780
+ pendingQuestion
12317
12781
  };
12318
12782
  } catch (err) {
12319
12783
  if (isSessionNotFoundError(err)) {
@@ -12345,27 +12809,12 @@ var HttpDataSource = class {
12345
12809
  return { artifacts: [] };
12346
12810
  }
12347
12811
  validateFileCount(0, files.length, 10);
12348
- const attachments = [];
12349
- for (const file of files) {
12350
- validateAttachmentFile(file);
12351
- const base64Data = await convertToBase64(file);
12352
- attachments.push({
12353
- clientKey: crypto.randomUUID(),
12354
- filename: file.name,
12355
- mimeType: file.type,
12356
- sizeBytes: file.size,
12357
- base64Data
12358
- });
12359
- }
12812
+ files.forEach((file) => validateAttachmentFile(file));
12813
+ const uploads = await Promise.all(files.map((file) => this.uploadFile(file)));
12360
12814
  const data = await this.callRPC("bichat.session.uploadArtifacts", {
12361
12815
  sessionId,
12362
- attachments: attachments.map((a) => ({
12363
- id: "",
12364
- // Backend will assign ID
12365
- filename: a.filename,
12366
- mimeType: a.mimeType,
12367
- sizeBytes: a.sizeBytes,
12368
- base64Data: a.base64Data
12816
+ attachments: uploads.map((upload) => ({
12817
+ uploadId: upload.id
12369
12818
  }))
12370
12819
  });
12371
12820
  return {
@@ -12396,22 +12845,21 @@ var HttpDataSource = class {
12396
12845
  signal.addEventListener("abort", onExternalAbort);
12397
12846
  }
12398
12847
  const url = `${this.config.baseUrl}${this.config.streamEndpoint}`;
12399
- const payload = {
12400
- sessionId,
12401
- content,
12402
- debugMode: options?.debugMode ?? false,
12403
- replaceFromMessageId: options?.replaceFromMessageID,
12404
- attachments: attachments.map((a) => ({
12405
- filename: a.filename,
12406
- mimeType: a.mimeType,
12407
- sizeBytes: a.sizeBytes,
12408
- base64Data: a.base64Data,
12409
- url: a.url
12410
- }))
12411
- };
12412
12848
  let connectionTimeoutID;
12413
12849
  let connectionTimedOut = false;
12414
12850
  try {
12851
+ const uploads = await Promise.all(
12852
+ attachments.map((attachment) => this.ensureAttachmentUpload(attachment))
12853
+ );
12854
+ const payload = {
12855
+ sessionId,
12856
+ content,
12857
+ debugMode: options?.debugMode ?? false,
12858
+ replaceFromMessageId: options?.replaceFromMessageID,
12859
+ attachments: uploads.map((upload) => ({
12860
+ uploadId: upload.id
12861
+ }))
12862
+ };
12415
12863
  const timeoutMs = this.config.timeout ?? 0;
12416
12864
  if (timeoutMs > 0) {
12417
12865
  connectionTimeoutID = setTimeout(() => {