@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.
@@ -64,14 +64,15 @@ function useTranslation() {
64
64
  const { translations, language } = locale;
65
65
  const t = React.useCallback(
66
66
  (key2, params) => {
67
- let text = translations[key2] || key2;
67
+ const raw = translations[key2];
68
+ let text = typeof raw === "string" && raw.trim() ? raw : key2;
68
69
  if (params) {
69
70
  Object.keys(params).forEach((paramKey) => {
70
71
  const value = params[paramKey];
71
72
  text = text.replace(new RegExp(`{{${paramKey}}}`, "g"), String(value));
72
73
  });
73
74
  }
74
- return text;
75
+ return text.trim() ? text : key2;
75
76
  },
76
77
  [translations]
77
78
  );
@@ -1517,6 +1518,10 @@ var ChatMachine = class {
1517
1518
  }
1518
1519
  /** Sets turns from fetch, preserving pending user-only turns if server hasn't caught up. */
1519
1520
  _setTurnsFromFetch(fetchedTurns) {
1521
+ if (!Array.isArray(fetchedTurns)) {
1522
+ console.warn("[ChatMachine] Ignoring malformed turns payload from fetchSession");
1523
+ return;
1524
+ }
1520
1525
  const prev = this.state.messaging.turns;
1521
1526
  const hasPendingUserOnly = prev.length > 0 && !prev[prev.length - 1].assistantTurn;
1522
1527
  if (hasPendingUserOnly && (!fetchedTurns || fetchedTurns.length === 0)) {
@@ -1906,6 +1911,7 @@ var ChatMachine = class {
1906
1911
  const curSessionId = this.state.session.currentSessionId;
1907
1912
  const curPendingQuestion = this.state.messaging.pendingQuestion;
1908
1913
  if (!curSessionId || !curPendingQuestion) return;
1914
+ const previousTurns = this.state.messaging.turns;
1909
1915
  this._updateMessaging({ loading: true });
1910
1916
  this._updateSession({ error: null, errorRetryable: false });
1911
1917
  const previousPendingQuestion = curPendingQuestion;
@@ -1923,19 +1929,28 @@ var ChatMachine = class {
1923
1929
  const fetchResult = await this.dataSource.fetchSession(curSessionId);
1924
1930
  if (this.disposed) return;
1925
1931
  if (fetchResult) {
1926
- this._updateMessaging({
1927
- turns: fetchResult.turns,
1928
- pendingQuestion: fetchResult.pendingQuestion || null
1929
- });
1932
+ this._updateSession({ session: fetchResult.session });
1933
+ this._updateMessaging({ pendingQuestion: fetchResult.pendingQuestion || null });
1934
+ const hasMalformedRefresh = previousTurns.length > 0 && Array.isArray(fetchResult.turns) && fetchResult.turns.length === 0;
1935
+ if (hasMalformedRefresh) {
1936
+ console.warn("[ChatMachine] Preserving previous turns due to empty post-HITL refetch payload", {
1937
+ sessionId: curSessionId,
1938
+ previousTurnCount: previousTurns.length
1939
+ });
1940
+ this._updateSession({
1941
+ error: "Failed to fully refresh session. Showing last known messages.",
1942
+ errorRetryable: true
1943
+ });
1944
+ } else {
1945
+ this._setTurnsFromFetch(fetchResult.turns);
1946
+ }
1930
1947
  } else {
1931
- this._updateMessaging({ pendingQuestion: previousPendingQuestion });
1932
- this._updateSession({ error: "Failed to load updated session", errorRetryable: false });
1948
+ this._updateSession({ error: "Failed to load updated session", errorRetryable: true });
1933
1949
  }
1934
1950
  } catch (fetchErr) {
1935
1951
  if (this.disposed) return;
1936
- this._updateMessaging({ pendingQuestion: previousPendingQuestion });
1937
1952
  const normalized = normalizeRPCError(fetchErr, "Failed to load updated session");
1938
- this._updateSession({ error: normalized.userMessage, errorRetryable: normalized.retryable });
1953
+ this._updateSession({ error: normalized.userMessage, errorRetryable: true });
1939
1954
  }
1940
1955
  }
1941
1956
  } else {
@@ -3295,7 +3310,7 @@ function InlineQuestionForm({ pendingQuestion }) {
3295
3310
  const [currentStep, setCurrentStep] = React.useState(0);
3296
3311
  const [answers, setAnswers] = React.useState({});
3297
3312
  const [otherTexts, setOtherTexts] = React.useState({});
3298
- const questions = pendingQuestion.questions;
3313
+ const questions = Array.isArray(pendingQuestion.questions) ? pendingQuestion.questions : [];
3299
3314
  const currentQuestion = questions[currentStep];
3300
3315
  const isLastStep = currentStep === questions.length - 1;
3301
3316
  const isFirstStep = currentStep === 0;
@@ -3389,9 +3404,31 @@ function InlineQuestionForm({ pendingQuestion }) {
3389
3404
  e.preventDefault();
3390
3405
  handleNext();
3391
3406
  };
3392
- if (!currentQuestion) return null;
3407
+ if (!currentQuestion) {
3408
+ 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: [
3409
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-gray-800 dark:text-gray-200", children: t("BiChat.Error.SomethingWentWrong") }),
3410
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: t("BiChat.Error.UnexpectedError") }),
3411
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxRuntime.jsxs(
3412
+ "button",
3413
+ {
3414
+ type: "button",
3415
+ onClick: handleRejectPendingQuestion,
3416
+ disabled: loading,
3417
+ 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",
3418
+ children: [
3419
+ /* @__PURE__ */ jsxRuntime.jsx(react.X, { size: 14, weight: "bold" }),
3420
+ t("BiChat.InlineQuestion.Dismiss")
3421
+ ]
3422
+ }
3423
+ ) })
3424
+ ] });
3425
+ }
3393
3426
  const isMultiSelect = currentQuestion.type === "MULTIPLE_CHOICE";
3394
- const options = currentQuestion.options || [];
3427
+ const options = (currentQuestion.options || []).filter((option) => Boolean(option && typeof option.label === "string")).map((option, index) => ({
3428
+ id: option.id || `${currentQuestion.id}-option-${index}`,
3429
+ label: option.label,
3430
+ value: option.value || option.label
3431
+ }));
3395
3432
  const isOtherSelected = currentAnswer?.customText !== void 0;
3396
3433
  const canProceed = isCurrentAnswerValid();
3397
3434
  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: [
@@ -4305,7 +4342,8 @@ function TurnBubble({
4305
4342
  userTurn: classNames?.userTurn ?? defaultClassNames3.userTurn,
4306
4343
  assistantTurn: classNames?.assistantTurn ?? defaultClassNames3.assistantTurn
4307
4344
  };
4308
- const isSystemSummaryTurn = turn.userTurn.content.trim() === "" && turn.assistantTurn?.role === "system";
4345
+ const userContent = typeof turn.userTurn?.content === "string" ? turn.userTurn.content : "";
4346
+ const isSystemSummaryTurn = userContent.trim() === "" && turn.assistantTurn?.role === "system";
4309
4347
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classes.root, "data-turn-id": turn.id, children: [
4310
4348
  !isSystemSummaryTurn && /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes.userTurn, children: renderUserTurn ? renderUserTurn(turn) : /* @__PURE__ */ jsxRuntime.jsx(
4311
4349
  UserTurnView,
@@ -6072,6 +6110,8 @@ function WarningBox({ message }) {
6072
6110
  }
6073
6111
  function ArtifactActions({ url }) {
6074
6112
  const { t } = useTranslation();
6113
+ const openLabel = t("BiChat.Artifacts.OpenInNewTab");
6114
+ const downloadLabel = t("BiChat.Artifacts.Download");
6075
6115
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
6076
6116
  /* @__PURE__ */ jsxRuntime.jsxs(
6077
6117
  "a",
@@ -6082,7 +6122,7 @@ function ArtifactActions({ url }) {
6082
6122
  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",
6083
6123
  children: [
6084
6124
  /* @__PURE__ */ jsxRuntime.jsx(react.ArrowSquareOut, { className: "h-3.5 w-3.5", weight: "bold" }),
6085
- t("BiChat.Artifacts.OpenInNewTab")
6125
+ openLabel
6086
6126
  ]
6087
6127
  }
6088
6128
  ),
@@ -6096,7 +6136,7 @@ function ArtifactActions({ url }) {
6096
6136
  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",
6097
6137
  children: [
6098
6138
  /* @__PURE__ */ jsxRuntime.jsx(react.DownloadSimple, { className: "h-3.5 w-3.5", weight: "bold" }),
6099
- t("BiChat.Artifacts.Download")
6139
+ downloadLabel
6100
6140
  ]
6101
6141
  }
6102
6142
  )
@@ -7416,6 +7456,7 @@ var EditableText = React.forwardRef(
7416
7456
  setIsEditing(false);
7417
7457
  };
7418
7458
  const handleKeyDown = (e) => {
7459
+ e.stopPropagation();
7419
7460
  if (e.key === "Enter") {
7420
7461
  e.preventDefault();
7421
7462
  handleSave();
@@ -7449,6 +7490,7 @@ var EditableText = React.forwardRef(
7449
7490
  value: editValue,
7450
7491
  onChange: (e) => setEditValue(e.target.value),
7451
7492
  onKeyDown: handleKeyDown,
7493
+ onKeyUp: (e) => e.stopPropagation(),
7452
7494
  onBlur: handleBlur,
7453
7495
  maxLength,
7454
7496
  placeholder: resolvedPlaceholder,
@@ -8079,12 +8121,63 @@ function DefaultErrorContent({
8079
8121
  ] })
8080
8122
  ] });
8081
8123
  }
8124
+ function StaticEmergencyErrorContent({
8125
+ error,
8126
+ onReset
8127
+ }) {
8128
+ 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: [
8129
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative mb-5", children: [
8130
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 rounded-full bg-red-100 scale-150 blur-md" }),
8131
+ /* @__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" }) })
8132
+ ] }),
8133
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-gray-900 mb-1.5", children: "Something went wrong" }),
8134
+ /* @__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." }),
8135
+ onReset && /* @__PURE__ */ jsxRuntime.jsxs(
8136
+ "button",
8137
+ {
8138
+ type: "button",
8139
+ onClick: onReset,
8140
+ 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",
8141
+ children: [
8142
+ /* @__PURE__ */ jsxRuntime.jsx(react.ArrowClockwise, { size: 16, weight: "bold" }),
8143
+ "Try again"
8144
+ ]
8145
+ }
8146
+ )
8147
+ ] }) });
8148
+ }
8149
+ var FallbackGuard = class extends React.Component {
8150
+ constructor(props) {
8151
+ super(props);
8152
+ this.state = { fallbackFailed: false };
8153
+ }
8154
+ static getDerivedStateFromError() {
8155
+ return { fallbackFailed: true };
8156
+ }
8157
+ componentDidCatch(error, errorInfo) {
8158
+ this.props.onFallbackError?.(error, errorInfo);
8159
+ }
8160
+ render() {
8161
+ if (this.state.fallbackFailed) {
8162
+ return /* @__PURE__ */ jsxRuntime.jsx(StaticEmergencyErrorContent, { error: this.props.primaryError, onReset: this.props.onReset });
8163
+ }
8164
+ return this.props.renderFallback();
8165
+ }
8166
+ };
8082
8167
  var ErrorBoundary = class extends React.Component {
8083
8168
  constructor(props) {
8084
8169
  super(props);
8085
8170
  this.handleReset = () => {
8086
8171
  this.setState({ hasError: false, error: null });
8087
8172
  };
8173
+ this.handleFallbackError = (error, errorInfo) => {
8174
+ console.error("React Error Boundary fallback crashed:", {
8175
+ primaryError: this.state.error,
8176
+ fallbackError: error,
8177
+ errorInfo
8178
+ });
8179
+ this.props.onError?.(error, errorInfo);
8180
+ };
8088
8181
  this.state = { hasError: false, error: null };
8089
8182
  }
8090
8183
  static getDerivedStateFromError(error) {
@@ -8096,13 +8189,24 @@ var ErrorBoundary = class extends React.Component {
8096
8189
  }
8097
8190
  render() {
8098
8191
  if (this.state.hasError) {
8099
- if (this.props.fallback) {
8100
- if (typeof this.props.fallback === "function") {
8101
- return this.props.fallback(this.state.error, this.handleReset);
8102
- }
8103
- return this.props.fallback;
8104
- }
8105
- return /* @__PURE__ */ jsxRuntime.jsx(DefaultErrorContent, { error: this.state.error, onReset: this.handleReset });
8192
+ return /* @__PURE__ */ jsxRuntime.jsx(
8193
+ FallbackGuard,
8194
+ {
8195
+ primaryError: this.state.error,
8196
+ onReset: this.handleReset,
8197
+ onFallbackError: this.handleFallbackError,
8198
+ renderFallback: () => {
8199
+ if (this.props.fallback) {
8200
+ if (typeof this.props.fallback === "function") {
8201
+ return this.props.fallback(this.state.error, this.handleReset);
8202
+ }
8203
+ return this.props.fallback;
8204
+ }
8205
+ return /* @__PURE__ */ jsxRuntime.jsx(DefaultErrorContent, { error: this.state.error, onReset: this.handleReset });
8206
+ }
8207
+ },
8208
+ `${this.state.error?.name ?? "Error"}:${this.state.error?.message ?? ""}`
8209
+ );
8106
8210
  }
8107
8211
  return this.props.children;
8108
8212
  }
@@ -8495,6 +8599,9 @@ var SessionItem = React.memo(
8495
8599
  onSelect(session.id);
8496
8600
  },
8497
8601
  onKeyDown: (e) => {
8602
+ const target = e.target;
8603
+ const isFromEditable = !!target?.closest('input, textarea, [contenteditable="true"]');
8604
+ if (isFromEditable) return;
8498
8605
  if (e.key === "Enter" || e.key === " ") {
8499
8606
  e.preventDefault();
8500
8607
  onSelect(session.id);
@@ -9253,6 +9360,15 @@ function Sidebar2({
9253
9360
  React.useEffect(() => {
9254
9361
  fetchSessions();
9255
9362
  }, [fetchSessions, refreshKey]);
9363
+ React.useEffect(() => {
9364
+ const handleSessionsUpdated = () => {
9365
+ setRefreshKey((k) => k + 1);
9366
+ };
9367
+ window.addEventListener("bichat:sessions-updated", handleSessionsUpdated);
9368
+ return () => {
9369
+ window.removeEventListener("bichat:sessions-updated", handleSessionsUpdated);
9370
+ };
9371
+ }, []);
9256
9372
  React.useEffect(() => {
9257
9373
  if (!activeSessionId) {
9258
9374
  refreshForActiveSessionRef.current = null;
@@ -9627,13 +9743,14 @@ function Sidebar2({
9627
9743
  anchor: "top start",
9628
9744
  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",
9629
9745
  children: [
9630
- onArchivedView && /* @__PURE__ */ jsxRuntime.jsx(react$1.MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxRuntime.jsxs(
9746
+ onArchivedView && /* @__PURE__ */ jsxRuntime.jsx(react$1.MenuItem, { children: ({ focus, close }) => /* @__PURE__ */ jsxRuntime.jsxs(
9631
9747
  "button",
9632
9748
  {
9633
9749
  onClick: (e) => {
9634
9750
  e.preventDefault();
9635
9751
  e.stopPropagation();
9636
9752
  onArchivedView();
9753
+ close();
9637
9754
  },
9638
9755
  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" : ""}`,
9639
9756
  "aria-label": t("BiChat.Sidebar.ArchivedChats"),
@@ -9643,13 +9760,14 @@ function Sidebar2({
9643
9760
  ]
9644
9761
  }
9645
9762
  ) }),
9646
- showAllChatsTab && activeTab !== "all-chats" && /* @__PURE__ */ jsxRuntime.jsx(react$1.MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxRuntime.jsxs(
9763
+ showAllChatsTab && activeTab !== "all-chats" && /* @__PURE__ */ jsxRuntime.jsx(react$1.MenuItem, { children: ({ focus, close }) => /* @__PURE__ */ jsxRuntime.jsxs(
9647
9764
  "button",
9648
9765
  {
9649
9766
  onClick: (e) => {
9650
9767
  e.preventDefault();
9651
9768
  e.stopPropagation();
9652
9769
  setActiveTab("all-chats");
9770
+ close();
9653
9771
  },
9654
9772
  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" : ""}`,
9655
9773
  "aria-label": t("BiChat.Sidebar.AllChats"),
@@ -9659,13 +9777,14 @@ function Sidebar2({
9659
9777
  ]
9660
9778
  }
9661
9779
  ) }),
9662
- showAllChatsTab && activeTab === "all-chats" && /* @__PURE__ */ jsxRuntime.jsx(react$1.MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxRuntime.jsxs(
9780
+ showAllChatsTab && activeTab === "all-chats" && /* @__PURE__ */ jsxRuntime.jsx(react$1.MenuItem, { children: ({ focus, close }) => /* @__PURE__ */ jsxRuntime.jsxs(
9663
9781
  "button",
9664
9782
  {
9665
9783
  onClick: (e) => {
9666
9784
  e.preventDefault();
9667
9785
  e.stopPropagation();
9668
9786
  setActiveTab("my-chats");
9787
+ close();
9669
9788
  },
9670
9789
  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" : ""}`,
9671
9790
  "aria-label": t("BiChat.Sidebar.MyChats"),
@@ -9780,7 +9899,11 @@ function ArchivedChatList({
9780
9899
  const confirmRestore = async () => {
9781
9900
  if (!sessionToRestore) return;
9782
9901
  try {
9783
- await dataSource.unarchiveSession(sessionToRestore);
9902
+ const restoredSessionID = sessionToRestore;
9903
+ await dataSource.unarchiveSession(restoredSessionID);
9904
+ window.dispatchEvent(new CustomEvent("bichat:sessions-updated", {
9905
+ detail: { reason: "restored", sessionId: restoredSessionID }
9906
+ }));
9784
9907
  setRefreshKey((k) => k + 1);
9785
9908
  toast.success(t("BiChat.Archived.ChatRestoredSuccessfully"));
9786
9909
  } catch (err) {
@@ -12042,6 +12165,7 @@ function toSessionArtifact(artifact) {
12042
12165
  id: artifact.id,
12043
12166
  sessionId: artifact.sessionId,
12044
12167
  messageId: artifact.messageId,
12168
+ uploadId: artifact.uploadId,
12045
12169
  type: artifact.type,
12046
12170
  name: artifact.name,
12047
12171
  description: artifact.description,
@@ -12052,21 +12176,272 @@ function toSessionArtifact(artifact) {
12052
12176
  createdAt: artifact.createdAt
12053
12177
  };
12054
12178
  }
12055
- function toPendingQuestion(rpc) {
12056
- if (!rpc) return null;
12057
- const questions = (rpc.questions || []).map((q) => ({
12058
- id: q.id,
12059
- text: q.text,
12060
- type: q.type,
12061
- options: (q.options || []).map((o) => ({
12062
- id: o.id,
12063
- label: o.label,
12064
- value: o.label
12065
- }))
12066
- }));
12179
+ function warnMalformedSessionPayload(message, details) {
12180
+ console.warn(`[BiChat] ${message}`, details || {});
12181
+ }
12182
+ function readString(value, fallback = "") {
12183
+ return typeof value === "string" ? value : fallback;
12184
+ }
12185
+ function readNonEmptyString(value) {
12186
+ if (typeof value !== "string") return null;
12187
+ const trimmed = value.trim();
12188
+ return trimmed.length > 0 ? trimmed : null;
12189
+ }
12190
+ function readFiniteNumber(value, fallback = 0) {
12191
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
12192
+ }
12193
+ function readOptionalFiniteNumber(value) {
12194
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
12195
+ }
12196
+ function normalizeQuestionType(rawType) {
12197
+ const normalized = readString(rawType).trim().toUpperCase().replace(/[\s-]+/g, "_");
12198
+ return normalized === "MULTIPLE_CHOICE" ? "MULTIPLE_CHOICE" : "SINGLE_CHOICE";
12199
+ }
12200
+ function normalizeMessageRole(rawRole) {
12201
+ const normalized = readString(rawRole).trim().toLowerCase();
12202
+ if (normalized === "user" /* User */) return "user" /* User */;
12203
+ if (normalized === "system" /* System */) return "system" /* System */;
12204
+ if (normalized === "tool" /* Tool */) return "tool" /* Tool */;
12205
+ return "assistant" /* Assistant */;
12206
+ }
12207
+ function sanitizeAttachment(rawAttachment, turnId, index) {
12208
+ if (!isRecord(rawAttachment)) {
12209
+ warnMalformedSessionPayload("Dropped malformed attachment entry", { turnId, index });
12210
+ return null;
12211
+ }
12212
+ const filename = readString(rawAttachment.filename, "attachment");
12213
+ const mimeType = readString(rawAttachment.mimeType, "application/octet-stream");
12214
+ const id = readNonEmptyString(rawAttachment.id) || void 0;
12215
+ const clientKey = readNonEmptyString(rawAttachment.clientKey) || id || `${turnId}-attachment-${index}`;
12067
12216
  return {
12068
- id: rpc.checkpointId,
12069
- turnId: rpc.turnId || "",
12217
+ id,
12218
+ clientKey,
12219
+ filename,
12220
+ mimeType,
12221
+ sizeBytes: readFiniteNumber(rawAttachment.sizeBytes),
12222
+ uploadId: readOptionalFiniteNumber(rawAttachment.uploadId),
12223
+ base64Data: readNonEmptyString(rawAttachment.base64Data) || void 0,
12224
+ url: readNonEmptyString(rawAttachment.url) || void 0,
12225
+ preview: readNonEmptyString(rawAttachment.preview) || void 0
12226
+ };
12227
+ }
12228
+ function sanitizeUserAttachments(rawAttachments, turnId) {
12229
+ if (!Array.isArray(rawAttachments)) return [];
12230
+ const result = [];
12231
+ for (let i = 0; i < rawAttachments.length; i++) {
12232
+ const sanitized = sanitizeAttachment(rawAttachments[i], turnId, i);
12233
+ if (sanitized) result.push(sanitized);
12234
+ }
12235
+ return result;
12236
+ }
12237
+ function sanitizeAssistantArtifacts(rawArtifacts, turnId) {
12238
+ if (!Array.isArray(rawArtifacts)) return [];
12239
+ const artifacts = [];
12240
+ for (let i = 0; i < rawArtifacts.length; i++) {
12241
+ const raw = rawArtifacts[i];
12242
+ if (!isRecord(raw)) {
12243
+ warnMalformedSessionPayload("Dropped malformed assistant artifact", { turnId, index: i });
12244
+ continue;
12245
+ }
12246
+ const type = readString(raw.type).toLowerCase();
12247
+ if (type !== "excel" && type !== "pdf") {
12248
+ continue;
12249
+ }
12250
+ const url = readNonEmptyString(raw.url);
12251
+ if (!url) {
12252
+ warnMalformedSessionPayload("Dropped assistant artifact without url", { turnId, index: i });
12253
+ continue;
12254
+ }
12255
+ artifacts.push({
12256
+ type,
12257
+ filename: readString(raw.filename, "download"),
12258
+ url,
12259
+ sizeReadable: readNonEmptyString(raw.sizeReadable) || void 0,
12260
+ rowCount: typeof raw.rowCount === "number" && Number.isFinite(raw.rowCount) ? raw.rowCount : void 0,
12261
+ description: readNonEmptyString(raw.description) || void 0
12262
+ });
12263
+ }
12264
+ return artifacts;
12265
+ }
12266
+ function sanitizeAssistantTurn(rawAssistantTurn, fallbackCreatedAt, turnId) {
12267
+ if (rawAssistantTurn == null) return void 0;
12268
+ if (!isRecord(rawAssistantTurn)) {
12269
+ warnMalformedSessionPayload("Dropped malformed assistant turn payload", { turnId });
12270
+ return void 0;
12271
+ }
12272
+ const assistantID = readNonEmptyString(rawAssistantTurn.id);
12273
+ if (!assistantID) {
12274
+ warnMalformedSessionPayload("Dropped assistant turn without id", { turnId });
12275
+ return void 0;
12276
+ }
12277
+ const citations = Array.isArray(rawAssistantTurn.citations) ? rawAssistantTurn.citations.filter((item) => isRecord(item)).map((item, index) => ({
12278
+ id: readString(item.id, `${assistantID}-citation-${index}`),
12279
+ type: readString(item.type),
12280
+ title: readString(item.title),
12281
+ url: readString(item.url),
12282
+ startIndex: readFiniteNumber(item.startIndex),
12283
+ endIndex: readFiniteNumber(item.endIndex),
12284
+ excerpt: readNonEmptyString(item.excerpt) || void 0
12285
+ })) : [];
12286
+ const toolCalls = Array.isArray(rawAssistantTurn.toolCalls) ? rawAssistantTurn.toolCalls.filter((item) => isRecord(item)).map((item, index) => ({
12287
+ id: readString(item.id, `${assistantID}-tool-${index}`),
12288
+ name: readString(item.name),
12289
+ arguments: readString(item.arguments),
12290
+ result: readNonEmptyString(item.result) || void 0,
12291
+ error: readNonEmptyString(item.error) || void 0,
12292
+ durationMs: readFiniteNumber(item.durationMs)
12293
+ })) : [];
12294
+ const codeOutputs = Array.isArray(rawAssistantTurn.codeOutputs) ? rawAssistantTurn.codeOutputs.filter((item) => isRecord(item)).map((item) => ({
12295
+ type: (() => {
12296
+ const normalizedType = readString(item.type, "text").toLowerCase();
12297
+ if (normalizedType === "image" || normalizedType === "error") return normalizedType;
12298
+ return "text";
12299
+ })(),
12300
+ content: readString(item.content),
12301
+ filename: readNonEmptyString(item.filename) || void 0,
12302
+ mimeType: readNonEmptyString(item.mimeType) || void 0,
12303
+ sizeBytes: readOptionalFiniteNumber(item.sizeBytes)
12304
+ })) : [];
12305
+ const debugTrace = isRecord(rawAssistantTurn.debug) ? {
12306
+ generationMs: readOptionalFiniteNumber(rawAssistantTurn.debug.generationMs),
12307
+ usage: isRecord(rawAssistantTurn.debug.usage) ? {
12308
+ promptTokens: readFiniteNumber(rawAssistantTurn.debug.usage.promptTokens),
12309
+ completionTokens: readFiniteNumber(rawAssistantTurn.debug.usage.completionTokens),
12310
+ totalTokens: readFiniteNumber(rawAssistantTurn.debug.usage.totalTokens),
12311
+ cachedTokens: readOptionalFiniteNumber(rawAssistantTurn.debug.usage.cachedTokens),
12312
+ cost: readOptionalFiniteNumber(rawAssistantTurn.debug.usage.cost)
12313
+ } : void 0,
12314
+ tools: Array.isArray(rawAssistantTurn.debug.tools) ? rawAssistantTurn.debug.tools.filter((tool) => isRecord(tool)).map((tool) => ({
12315
+ callId: readNonEmptyString(tool.callId) || void 0,
12316
+ name: readString(tool.name),
12317
+ arguments: readNonEmptyString(tool.arguments) || void 0,
12318
+ result: readNonEmptyString(tool.result) || void 0,
12319
+ error: readNonEmptyString(tool.error) || void 0,
12320
+ durationMs: readOptionalFiniteNumber(tool.durationMs)
12321
+ })) : []
12322
+ } : void 0;
12323
+ return {
12324
+ id: assistantID,
12325
+ role: normalizeMessageRole(rawAssistantTurn.role),
12326
+ content: readString(rawAssistantTurn.content),
12327
+ explanation: readNonEmptyString(rawAssistantTurn.explanation) || void 0,
12328
+ citations,
12329
+ toolCalls,
12330
+ chartData: void 0,
12331
+ artifacts: sanitizeAssistantArtifacts(rawAssistantTurn.artifacts, turnId),
12332
+ codeOutputs,
12333
+ debug: debugTrace,
12334
+ createdAt: readString(rawAssistantTurn.createdAt, fallbackCreatedAt)
12335
+ };
12336
+ }
12337
+ function sanitizeConversationTurn(rawTurn, index, fallbackSessionID) {
12338
+ if (!isRecord(rawTurn)) {
12339
+ warnMalformedSessionPayload("Dropped malformed turn payload (not an object)", { index });
12340
+ return null;
12341
+ }
12342
+ if (!isRecord(rawTurn.userTurn)) {
12343
+ warnMalformedSessionPayload("Dropped malformed turn payload (missing user turn)", { index });
12344
+ return null;
12345
+ }
12346
+ const userTurnID = readNonEmptyString(rawTurn.userTurn.id);
12347
+ if (!userTurnID) {
12348
+ warnMalformedSessionPayload("Dropped malformed turn payload (missing user turn id)", { index });
12349
+ return null;
12350
+ }
12351
+ const turnID = readString(rawTurn.id, userTurnID);
12352
+ const createdAt = readString(
12353
+ rawTurn.createdAt,
12354
+ readString(rawTurn.userTurn.createdAt, (/* @__PURE__ */ new Date()).toISOString())
12355
+ );
12356
+ return {
12357
+ id: turnID,
12358
+ sessionId: readString(rawTurn.sessionId, fallbackSessionID),
12359
+ userTurn: {
12360
+ id: userTurnID,
12361
+ content: readString(rawTurn.userTurn.content),
12362
+ attachments: sanitizeUserAttachments(rawTurn.userTurn.attachments, turnID),
12363
+ createdAt: readString(rawTurn.userTurn.createdAt, createdAt)
12364
+ },
12365
+ assistantTurn: sanitizeAssistantTurn(rawTurn.assistantTurn, createdAt, turnID),
12366
+ createdAt
12367
+ };
12368
+ }
12369
+ function sanitizeConversationTurns(rawTurns, sessionID) {
12370
+ if (!Array.isArray(rawTurns)) {
12371
+ warnMalformedSessionPayload("Session payload contained non-array turns field", { sessionID });
12372
+ return [];
12373
+ }
12374
+ const turns = [];
12375
+ let dropped = 0;
12376
+ for (let i = 0; i < rawTurns.length; i++) {
12377
+ const sanitizedTurn = sanitizeConversationTurn(rawTurns[i], i, sessionID);
12378
+ if (sanitizedTurn) {
12379
+ turns.push(sanitizedTurn);
12380
+ } else {
12381
+ dropped++;
12382
+ }
12383
+ }
12384
+ if (dropped > 0) {
12385
+ warnMalformedSessionPayload("Dropped malformed turns from session payload", {
12386
+ sessionID,
12387
+ dropped,
12388
+ total: rawTurns.length
12389
+ });
12390
+ }
12391
+ return turns;
12392
+ }
12393
+ function sanitizePendingQuestion(rawPendingQuestion, sessionID) {
12394
+ if (!rawPendingQuestion) return null;
12395
+ const checkpointID = readNonEmptyString(rawPendingQuestion.checkpointId);
12396
+ if (!checkpointID) {
12397
+ warnMalformedSessionPayload("Dropped malformed pendingQuestion without checkpointId", { sessionID });
12398
+ return null;
12399
+ }
12400
+ if (!Array.isArray(rawPendingQuestion.questions)) {
12401
+ warnMalformedSessionPayload("Pending question had non-array questions payload", {
12402
+ sessionID,
12403
+ checkpointID
12404
+ });
12405
+ }
12406
+ const questions = Array.isArray(rawPendingQuestion.questions) ? rawPendingQuestion.questions.filter((question) => {
12407
+ if (!question || !isRecord(question)) {
12408
+ warnMalformedSessionPayload("Dropped malformed question from pendingQuestion", {
12409
+ sessionID,
12410
+ checkpointID
12411
+ });
12412
+ return false;
12413
+ }
12414
+ return true;
12415
+ }).map((question, index) => {
12416
+ const questionID = readString(question.id, `${checkpointID}-q-${index}`);
12417
+ const options = Array.isArray(question.options) ? question.options.filter((option) => {
12418
+ if (!option || !isRecord(option)) {
12419
+ warnMalformedSessionPayload("Dropped malformed pendingQuestion option", {
12420
+ sessionID,
12421
+ checkpointID,
12422
+ questionID
12423
+ });
12424
+ return false;
12425
+ }
12426
+ return true;
12427
+ }).map((option, optionIndex) => {
12428
+ const label = readString(option.label);
12429
+ return {
12430
+ id: readString(option.id, `${questionID}-opt-${optionIndex}`),
12431
+ label,
12432
+ value: label
12433
+ };
12434
+ }) : [];
12435
+ return {
12436
+ id: questionID,
12437
+ text: readString(question.text),
12438
+ type: normalizeQuestionType(question.type),
12439
+ options
12440
+ };
12441
+ }) : [];
12442
+ return {
12443
+ id: checkpointID,
12444
+ turnId: readString(rawPendingQuestion.turnId),
12070
12445
  questions,
12071
12446
  status: "PENDING"
12072
12447
  };
@@ -12262,7 +12637,8 @@ var HttpDataSource = class {
12262
12637
  this.abortController = null;
12263
12638
  this.config = {
12264
12639
  streamEndpoint: "/stream",
12265
- timeout: 3e4,
12640
+ uploadEndpoint: "/api/uploads",
12641
+ timeout: 12e4,
12266
12642
  ...config
12267
12643
  };
12268
12644
  if (config.navigateToSession) {
@@ -12297,6 +12673,83 @@ var HttpDataSource = class {
12297
12673
  }
12298
12674
  return headers;
12299
12675
  }
12676
+ createUploadHeaders(additionalHeaders) {
12677
+ const headers = new Headers({
12678
+ ...this.config.headers,
12679
+ ...additionalHeaders
12680
+ });
12681
+ const csrfToken = this.getCSRFToken();
12682
+ if (csrfToken) {
12683
+ headers.set("X-CSRF-Token", csrfToken);
12684
+ }
12685
+ headers.delete("Content-Type");
12686
+ return headers;
12687
+ }
12688
+ async uploadFile(file) {
12689
+ const formData = new FormData();
12690
+ formData.append("file", file);
12691
+ const response = await fetch(`${this.config.baseUrl}${this.config.uploadEndpoint}`, {
12692
+ method: "POST",
12693
+ headers: this.createUploadHeaders(),
12694
+ body: formData
12695
+ });
12696
+ let payload = null;
12697
+ try {
12698
+ payload = await response.json();
12699
+ } catch {
12700
+ payload = null;
12701
+ }
12702
+ if (!response.ok) {
12703
+ const errorMessage = isRecord(payload) && typeof payload.error === "string" ? payload.error : `Upload failed: HTTP ${response.status}`;
12704
+ throw new Error(errorMessage);
12705
+ }
12706
+ if (!isRecord(payload) || typeof payload.id !== "number" || payload.id <= 0) {
12707
+ throw new Error("Upload failed: invalid response payload");
12708
+ }
12709
+ return {
12710
+ id: payload.id,
12711
+ url: typeof payload.url === "string" ? payload.url : "",
12712
+ path: typeof payload.path === "string" ? payload.path : "",
12713
+ name: typeof payload.name === "string" ? payload.name : file.name,
12714
+ mimetype: typeof payload.mimetype === "string" ? payload.mimetype : file.type,
12715
+ size: typeof payload.size === "number" && Number.isFinite(payload.size) ? payload.size : file.size
12716
+ };
12717
+ }
12718
+ async attachmentToFile(attachment) {
12719
+ if (attachment.base64Data && attachment.base64Data.trim().length > 0) {
12720
+ const base64Data = attachment.base64Data.trim();
12721
+ const dataUrl = base64Data.startsWith("data:") ? base64Data : `data:${attachment.mimeType || "application/octet-stream"};base64,${base64Data}`;
12722
+ const blob = await fetch(dataUrl).then((response) => response.blob());
12723
+ return new File([blob], attachment.filename, {
12724
+ type: attachment.mimeType || blob.type || "application/octet-stream"
12725
+ });
12726
+ }
12727
+ if (attachment.url) {
12728
+ const response = await fetch(attachment.url);
12729
+ if (!response.ok) {
12730
+ throw new Error(`Failed to read attachment source: HTTP ${response.status}`);
12731
+ }
12732
+ const blob = await response.blob();
12733
+ return new File([blob], attachment.filename, {
12734
+ type: attachment.mimeType || blob.type || "application/octet-stream"
12735
+ });
12736
+ }
12737
+ throw new Error(`Attachment "${attachment.filename}" has no uploadable data`);
12738
+ }
12739
+ async ensureAttachmentUpload(attachment) {
12740
+ if (typeof attachment.uploadId === "number" && attachment.uploadId > 0) {
12741
+ return {
12742
+ id: attachment.uploadId,
12743
+ url: attachment.url || "",
12744
+ path: "",
12745
+ name: attachment.filename,
12746
+ mimetype: attachment.mimeType,
12747
+ size: attachment.sizeBytes
12748
+ };
12749
+ }
12750
+ const file = await this.attachmentToFile(attachment);
12751
+ return this.uploadFile(file);
12752
+ }
12300
12753
  async callRPC(method, params) {
12301
12754
  return this.rpc.callTyped(method, params);
12302
12755
  }
@@ -12319,11 +12772,22 @@ var HttpDataSource = class {
12319
12772
  return { artifacts: [], hasMore: false, nextOffset: 0 };
12320
12773
  })
12321
12774
  ]);
12322
- const turns = attachArtifactsToTurns(normalizeTurns(data.turns), artifactsData.artifacts || []);
12775
+ const sanitizedTurns = sanitizeConversationTurns(data.turns, id);
12776
+ const turns = attachArtifactsToTurns(
12777
+ normalizeTurns(sanitizedTurns),
12778
+ artifactsData.artifacts || []
12779
+ );
12780
+ const pendingQuestion = sanitizePendingQuestion(data.pendingQuestion, id);
12781
+ if (data.pendingQuestion && pendingQuestion && pendingQuestion.questions.length === 0) {
12782
+ warnMalformedSessionPayload("Pending question normalized to zero renderable questions", {
12783
+ sessionID: id,
12784
+ checkpointID: pendingQuestion.id
12785
+ });
12786
+ }
12323
12787
  return {
12324
12788
  session: toSession(data.session),
12325
12789
  turns,
12326
- pendingQuestion: toPendingQuestion(data.pendingQuestion)
12790
+ pendingQuestion
12327
12791
  };
12328
12792
  } catch (err) {
12329
12793
  if (isSessionNotFoundError(err)) {
@@ -12355,27 +12819,12 @@ var HttpDataSource = class {
12355
12819
  return { artifacts: [] };
12356
12820
  }
12357
12821
  validateFileCount(0, files.length, 10);
12358
- const attachments = [];
12359
- for (const file of files) {
12360
- validateAttachmentFile(file);
12361
- const base64Data = await convertToBase64(file);
12362
- attachments.push({
12363
- clientKey: crypto.randomUUID(),
12364
- filename: file.name,
12365
- mimeType: file.type,
12366
- sizeBytes: file.size,
12367
- base64Data
12368
- });
12369
- }
12822
+ files.forEach((file) => validateAttachmentFile(file));
12823
+ const uploads = await Promise.all(files.map((file) => this.uploadFile(file)));
12370
12824
  const data = await this.callRPC("bichat.session.uploadArtifacts", {
12371
12825
  sessionId,
12372
- attachments: attachments.map((a) => ({
12373
- id: "",
12374
- // Backend will assign ID
12375
- filename: a.filename,
12376
- mimeType: a.mimeType,
12377
- sizeBytes: a.sizeBytes,
12378
- base64Data: a.base64Data
12826
+ attachments: uploads.map((upload) => ({
12827
+ uploadId: upload.id
12379
12828
  }))
12380
12829
  });
12381
12830
  return {
@@ -12406,22 +12855,21 @@ var HttpDataSource = class {
12406
12855
  signal.addEventListener("abort", onExternalAbort);
12407
12856
  }
12408
12857
  const url = `${this.config.baseUrl}${this.config.streamEndpoint}`;
12409
- const payload = {
12410
- sessionId,
12411
- content,
12412
- debugMode: options?.debugMode ?? false,
12413
- replaceFromMessageId: options?.replaceFromMessageID,
12414
- attachments: attachments.map((a) => ({
12415
- filename: a.filename,
12416
- mimeType: a.mimeType,
12417
- sizeBytes: a.sizeBytes,
12418
- base64Data: a.base64Data,
12419
- url: a.url
12420
- }))
12421
- };
12422
12858
  let connectionTimeoutID;
12423
12859
  let connectionTimedOut = false;
12424
12860
  try {
12861
+ const uploads = await Promise.all(
12862
+ attachments.map((attachment) => this.ensureAttachmentUpload(attachment))
12863
+ );
12864
+ const payload = {
12865
+ sessionId,
12866
+ content,
12867
+ debugMode: options?.debugMode ?? false,
12868
+ replaceFromMessageId: options?.replaceFromMessageID,
12869
+ attachments: uploads.map((upload) => ({
12870
+ uploadId: upload.id
12871
+ }))
12872
+ };
12425
12873
  const timeoutMs = this.config.timeout ?? 0;
12426
12874
  if (timeoutMs > 0) {
12427
12875
  connectionTimeoutID = setTimeout(() => {