@iota-uz/sdk 0.4.33 → 0.4.35

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.
@@ -3659,16 +3659,26 @@ function hashString(str) {
3659
3659
  return Math.abs(hash);
3660
3660
  }
3661
3661
  var colorPalette = [
3662
- { bg: "bg-blue-500", text: "text-white" },
3663
- { bg: "bg-green-500", text: "text-white" },
3664
- { bg: "bg-purple-500", text: "text-white" },
3665
- { bg: "bg-pink-500", text: "text-white" },
3666
- { bg: "bg-indigo-500", text: "text-white" },
3667
- { bg: "bg-teal-500", text: "text-white" },
3668
- { bg: "bg-orange-500", text: "text-white" },
3669
- { bg: "bg-cyan-500", text: "text-white" },
3670
- { bg: "bg-amber-500", text: "text-white" },
3671
- { bg: "bg-lime-500", text: "text-white" }
3662
+ { bg: "#3b82f6", text: "#ffffff" },
3663
+ // blue-500
3664
+ { bg: "#22c55e", text: "#111827" },
3665
+ // green-500 (light bg)
3666
+ { bg: "#a855f7", text: "#ffffff" },
3667
+ // purple-500
3668
+ { bg: "#ec4899", text: "#ffffff" },
3669
+ // pink-500
3670
+ { bg: "#6366f1", text: "#ffffff" },
3671
+ // indigo-500
3672
+ { bg: "#14b8a6", text: "#111827" },
3673
+ // teal-500 (light bg)
3674
+ { bg: "#f97316", text: "#ffffff" },
3675
+ // orange-500
3676
+ { bg: "#06b6d4", text: "#111827" },
3677
+ // cyan-500 (light bg)
3678
+ { bg: "#f59e0b", text: "#111827" },
3679
+ // amber-500 (light bg)
3680
+ { bg: "#84cc16", text: "#111827" }
3681
+ // lime-500 (light bg)
3672
3682
  ];
3673
3683
  var sizeClasses = {
3674
3684
  xs: "w-6 h-6 text-[10px]",
@@ -3698,8 +3708,6 @@ function UserAvatar({
3698
3708
  {
3699
3709
  className: `
3700
3710
  ${sizeClasses[size]}
3701
- ${colors.bg}
3702
- ${colors.text}
3703
3711
  ${className}
3704
3712
  rounded-full
3705
3713
  flex
@@ -3709,6 +3717,7 @@ function UserAvatar({
3709
3717
  flex-shrink-0
3710
3718
  select-none
3711
3719
  `,
3720
+ style: { backgroundColor: colors.bg, color: colors.text },
3712
3721
  "aria-label": `${firstName} ${lastName}`,
3713
3722
  title: `${firstName} ${lastName}`,
3714
3723
  children: initials
@@ -12680,7 +12689,9 @@ function ModelSelector() {
12680
12689
  }, [currentModel, models, setModel]);
12681
12690
  React.useEffect(() => {
12682
12691
  const handler = (e) => {
12683
- if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === "m") {
12692
+ const isModifierPressed = e.metaKey || e.ctrlKey;
12693
+ const isShortcutKey = e.code === "KeyM" || e.key.toLowerCase() === "m";
12694
+ if (isModifierPressed && e.shiftKey && !e.altKey && isShortcutKey) {
12684
12695
  e.preventDefault();
12685
12696
  rotateModel();
12686
12697
  }
@@ -14385,6 +14396,62 @@ function AllChatsList({ dataSource, onSessionSelect, activeSessionId }) {
14385
14396
  });
14386
14397
  return Array.from(userMap.values());
14387
14398
  }, [chats, users, dataSource.listUsers]);
14399
+ const [expandedGroups, setExpandedGroups] = React.useState(/* @__PURE__ */ new Set());
14400
+ const toggleGroup = React.useCallback((ownerId) => {
14401
+ setExpandedGroups((prev) => {
14402
+ const next = new Set(prev);
14403
+ if (next.has(ownerId)) {
14404
+ next.delete(ownerId);
14405
+ } else {
14406
+ next.add(ownerId);
14407
+ }
14408
+ return next;
14409
+ });
14410
+ }, []);
14411
+ React.useEffect(() => {
14412
+ if (!activeSessionId || selectedUser) {
14413
+ return;
14414
+ }
14415
+ const chat = chats.find((c) => c.id === activeSessionId);
14416
+ if (chat?.owner?.id) {
14417
+ setExpandedGroups((prev) => {
14418
+ if (prev.has(chat.owner.id)) {
14419
+ return prev;
14420
+ }
14421
+ return /* @__PURE__ */ new Set([...prev, chat.owner.id]);
14422
+ });
14423
+ }
14424
+ }, [activeSessionId, chats, selectedUser]);
14425
+ const groupedChats = React.useMemo(() => {
14426
+ if (selectedUser) {
14427
+ return null;
14428
+ }
14429
+ const groupMap = /* @__PURE__ */ new Map();
14430
+ chats.forEach((chat) => {
14431
+ const owner = chat.owner ?? {
14432
+ id: "__unknown__",
14433
+ firstName: t("BiChat.Common.Untitled"),
14434
+ lastName: "",
14435
+ initials: "?"
14436
+ };
14437
+ const ownerId = owner.id;
14438
+ if (!groupMap.has(ownerId)) {
14439
+ groupMap.set(ownerId, {
14440
+ owner,
14441
+ chats: [],
14442
+ latestUpdatedAt: chat.updatedAt
14443
+ });
14444
+ }
14445
+ const group = groupMap.get(ownerId);
14446
+ group.chats.push(chat);
14447
+ if (chat.updatedAt > group.latestUpdatedAt) {
14448
+ group.latestUpdatedAt = chat.updatedAt;
14449
+ }
14450
+ });
14451
+ return Array.from(groupMap.values()).sort(
14452
+ (a, b) => b.latestUpdatedAt.localeCompare(a.latestUpdatedAt)
14453
+ );
14454
+ }, [chats, selectedUser, t]);
14388
14455
  return /* @__PURE__ */ jsxRuntime.jsxs(
14389
14456
  "div",
14390
14457
  {
@@ -14430,61 +14497,159 @@ function AllChatsList({ dataSource, onSessionSelect, activeSessionId }) {
14430
14497
  role: "list",
14431
14498
  "aria-label": t("BiChat.AllChats.OrganizationChatSessions"),
14432
14499
  children: [
14433
- chats.map((chat) => {
14434
- const owner = chat.owner ?? {
14435
- firstName: "",
14436
- lastName: "",
14437
- initials: "U"
14438
- };
14439
- const ownerName = [owner.firstName, owner.lastName].filter(Boolean).join(" ");
14440
- return /* @__PURE__ */ jsxRuntime.jsx(
14441
- framerMotion.motion.div,
14442
- {
14443
- initial: { opacity: 0, y: -10 },
14444
- animate: { opacity: 1, y: 0 },
14445
- exit: { opacity: 0, y: -10 },
14446
- children: /* @__PURE__ */ jsxRuntime.jsx(
14500
+ groupedChats ? (
14501
+ /* ── Grouped view (no user selected) ── */
14502
+ groupedChats.map((group) => {
14503
+ const ownerId = group.owner.id;
14504
+ const ownerName = [group.owner.firstName, group.owner.lastName].filter(Boolean).join(" ");
14505
+ const isCollapsed = !expandedGroups.has(ownerId);
14506
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-1", children: [
14507
+ /* @__PURE__ */ jsxRuntime.jsxs(
14447
14508
  "div",
14448
14509
  {
14449
- role: "link",
14510
+ role: "button",
14450
14511
  tabIndex: 0,
14451
- onClick: () => onSessionSelect(chat.id),
14512
+ onClick: () => toggleGroup(ownerId),
14452
14513
  onKeyDown: (e) => {
14453
14514
  if (e.key === "Enter" || e.key === " ") {
14454
14515
  e.preventDefault();
14455
- onSessionSelect(chat.id);
14516
+ toggleGroup(ownerId);
14456
14517
  }
14457
14518
  },
14458
- className: `
14459
- block px-3 py-2 rounded-lg transition-smooth group cursor-pointer
14460
- ${chat.id === activeSessionId ? "bg-primary-50/50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400 border-l-4 border-primary-400 dark:border-primary-600" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 border-l-4 border-transparent"}
14461
- `,
14462
- "aria-current": chat.id === activeSessionId ? "page" : void 0,
14463
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
14519
+ className: "flex items-center gap-2 px-3 py-2 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg transition-smooth select-none",
14520
+ "aria-expanded": !isCollapsed,
14521
+ children: [
14522
+ /* @__PURE__ */ jsxRuntime.jsx(
14523
+ react.CaretRight,
14524
+ {
14525
+ size: 14,
14526
+ weight: "bold",
14527
+ className: `shrink-0 text-gray-500 dark:text-gray-400 transition-transform duration-150 ${isCollapsed ? "" : "rotate-90"}`
14528
+ }
14529
+ ),
14464
14530
  /* @__PURE__ */ jsxRuntime.jsx(
14465
14531
  MemoizedUserAvatar,
14466
14532
  {
14467
- firstName: owner.firstName,
14468
- lastName: owner.lastName,
14469
- initials: owner.initials,
14533
+ firstName: group.owner.firstName,
14534
+ lastName: group.owner.lastName,
14535
+ initials: group.owner.initials,
14470
14536
  size: "sm"
14471
14537
  }
14472
14538
  ),
14473
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
14474
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium truncate", children: chat.title || t("BiChat.Common.Untitled") }),
14475
- ownerName && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 truncate", children: ownerName }),
14476
- chat.status === "archived" && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 mt-1 px-2 py-0.5 bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 rounded-full text-xs", children: [
14477
- /* @__PURE__ */ jsxRuntime.jsx(react.Archive, { size: 12, className: "w-3 h-3" }),
14478
- t("BiChat.Chat.Archived")
14539
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-700 dark:text-gray-300 truncate flex-1 min-w-0", children: ownerName || t("BiChat.Common.Untitled") }),
14540
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded-full flex-shrink-0", children: group.chats.length })
14541
+ ]
14542
+ }
14543
+ ),
14544
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { initial: false, children: !isCollapsed && /* @__PURE__ */ jsxRuntime.jsx(
14545
+ framerMotion.motion.div,
14546
+ {
14547
+ initial: { height: 0, opacity: 0 },
14548
+ animate: { height: "auto", opacity: 1 },
14549
+ exit: { height: 0, opacity: 0 },
14550
+ transition: { duration: 0.2, ease: [0.4, 0, 0.2, 1] },
14551
+ className: "overflow-hidden",
14552
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-0.5 pl-6", children: group.chats.map((chat) => /* @__PURE__ */ jsxRuntime.jsx(
14553
+ framerMotion.motion.div,
14554
+ {
14555
+ initial: { opacity: 0, y: -10 },
14556
+ animate: { opacity: 1, y: 0 },
14557
+ exit: { opacity: 0, y: -10 },
14558
+ children: /* @__PURE__ */ jsxRuntime.jsx(
14559
+ "div",
14560
+ {
14561
+ role: "link",
14562
+ tabIndex: 0,
14563
+ onClick: () => onSessionSelect(chat.id),
14564
+ onKeyDown: (e) => {
14565
+ if (e.key === "Enter" || e.key === " ") {
14566
+ e.preventDefault();
14567
+ onSessionSelect(chat.id);
14568
+ }
14569
+ },
14570
+ className: `
14571
+ block px-3 py-2 rounded-lg transition-smooth group cursor-pointer
14572
+ ${chat.id === activeSessionId ? "bg-primary-50/50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400 border-l-4 border-primary-400 dark:border-primary-600" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 border-l-4 border-transparent"}
14573
+ `,
14574
+ "aria-current": chat.id === activeSessionId ? "page" : void 0,
14575
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
14576
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm truncate flex-1 min-w-0", children: chat.title || t("BiChat.Common.Untitled") }),
14577
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 flex-shrink-0", children: [
14578
+ chat.isGroup && chat.memberCount && chat.memberCount > 1 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400 dark:text-gray-500", children: chat.memberCount }),
14579
+ chat.status === "archived" && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 rounded-full text-xs", children: [
14580
+ /* @__PURE__ */ jsxRuntime.jsx(react.Archive, { size: 12, className: "w-3 h-3" }),
14581
+ t("BiChat.Chat.Archived")
14582
+ ] })
14583
+ ] })
14584
+ ] })
14585
+ }
14586
+ )
14587
+ },
14588
+ chat.id
14589
+ )) })
14590
+ },
14591
+ `group-${ownerId}`
14592
+ ) })
14593
+ ] }, ownerId);
14594
+ })
14595
+ ) : (
14596
+ /* ── Flat view (user selected) ── */
14597
+ chats.map((chat) => {
14598
+ const owner = chat.owner ?? {
14599
+ firstName: "",
14600
+ lastName: "",
14601
+ initials: "U"
14602
+ };
14603
+ const ownerName = [owner.firstName, owner.lastName].filter(Boolean).join(" ");
14604
+ return /* @__PURE__ */ jsxRuntime.jsx(
14605
+ framerMotion.motion.div,
14606
+ {
14607
+ initial: { opacity: 0, y: -10 },
14608
+ animate: { opacity: 1, y: 0 },
14609
+ exit: { opacity: 0, y: -10 },
14610
+ children: /* @__PURE__ */ jsxRuntime.jsx(
14611
+ "div",
14612
+ {
14613
+ role: "link",
14614
+ tabIndex: 0,
14615
+ onClick: () => onSessionSelect(chat.id),
14616
+ onKeyDown: (e) => {
14617
+ if (e.key === "Enter" || e.key === " ") {
14618
+ e.preventDefault();
14619
+ onSessionSelect(chat.id);
14620
+ }
14621
+ },
14622
+ className: `
14623
+ block px-3 py-2 rounded-lg transition-smooth group cursor-pointer
14624
+ ${chat.id === activeSessionId ? "bg-primary-50/50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400 border-l-4 border-primary-400 dark:border-primary-600" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 border-l-4 border-transparent"}
14625
+ `,
14626
+ "aria-current": chat.id === activeSessionId ? "page" : void 0,
14627
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
14628
+ /* @__PURE__ */ jsxRuntime.jsx(
14629
+ MemoizedUserAvatar,
14630
+ {
14631
+ firstName: owner.firstName,
14632
+ lastName: owner.lastName,
14633
+ initials: owner.initials,
14634
+ size: "sm"
14635
+ }
14636
+ ),
14637
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
14638
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium truncate", children: chat.title || t("BiChat.Common.Untitled") }),
14639
+ ownerName && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 truncate", children: ownerName }),
14640
+ chat.status === "archived" && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 mt-1 px-2 py-0.5 bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 rounded-full text-xs", children: [
14641
+ /* @__PURE__ */ jsxRuntime.jsx(react.Archive, { size: 12, className: "w-3 h-3" }),
14642
+ t("BiChat.Chat.Archived")
14643
+ ] })
14479
14644
  ] })
14480
14645
  ] })
14481
- ] })
14482
- }
14483
- )
14484
- },
14485
- chat.id
14486
- );
14487
- }),
14646
+ }
14647
+ )
14648
+ },
14649
+ chat.id
14650
+ );
14651
+ })
14652
+ ),
14488
14653
  hasMore && /* @__PURE__ */ jsxRuntime.jsx("div", { ref: loadMoreRef, className: "py-4 text-center", children: fetching ? /* @__PURE__ */ jsxRuntime.jsx(SessionSkeleton, { count: 2 }) : /* @__PURE__ */ jsxRuntime.jsx(
14489
14654
  "button",
14490
14655
  {
@@ -14707,7 +14872,9 @@ function Sidebar2({
14707
14872
  onClose,
14708
14873
  headerSlot,
14709
14874
  footerSlot,
14710
- className = ""
14875
+ className = "",
14876
+ activeTab: controlledActiveTab,
14877
+ onTabChange
14711
14878
  }) {
14712
14879
  const { t } = useTranslation();
14713
14880
  const toast = useToast();
@@ -14767,7 +14934,14 @@ function Sidebar2({
14767
14934
  const timer = setTimeout(() => setCollapsedOverflowVisible(true), 300);
14768
14935
  return () => clearTimeout(timer);
14769
14936
  }, [showCollapsed]);
14770
- const [activeTab, setActiveTab] = React.useState("my-chats");
14937
+ const [internalActiveTab, setInternalActiveTab] = React.useState("my-chats");
14938
+ const activeTab = controlledActiveTab ?? internalActiveTab;
14939
+ const handleTabChange = React.useCallback((tab) => {
14940
+ if (controlledActiveTab === void 0) {
14941
+ setInternalActiveTab(tab);
14942
+ }
14943
+ onTabChange?.(tab);
14944
+ }, [controlledActiveTab, onTabChange]);
14771
14945
  const [searchQuery, setSearchQuery] = React.useState("");
14772
14946
  const [sessions, setSessions] = React.useState([]);
14773
14947
  const [loading, setLoading] = React.useState(true);
@@ -15381,7 +15555,7 @@ function Sidebar2({
15381
15555
  onClick: (e) => {
15382
15556
  e.preventDefault();
15383
15557
  e.stopPropagation();
15384
- setActiveTab("all-chats");
15558
+ handleTabChange("all-chats");
15385
15559
  close();
15386
15560
  },
15387
15561
  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" : ""}`,
@@ -15398,7 +15572,7 @@ function Sidebar2({
15398
15572
  onClick: (e) => {
15399
15573
  e.preventDefault();
15400
15574
  e.stopPropagation();
15401
- setActiveTab("my-chats");
15575
+ handleTabChange("my-chats");
15402
15576
  close();
15403
15577
  },
15404
15578
  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" : ""}`,
@@ -16893,6 +17067,140 @@ function useStreaming2(options = {}) {
16893
17067
  reset
16894
17068
  };
16895
17069
  }
17070
+ var TERMINAL = /* @__PURE__ */ new Set([
17071
+ "completed",
17072
+ "cancelled",
17073
+ "failed"
17074
+ ]);
17075
+ function useActiveRuns(dataSource, options = {}) {
17076
+ const [runs, setRuns] = React.useState({});
17077
+ const [ready, setReady] = React.useState(false);
17078
+ const onErrorRef = React.useRef(options.onError);
17079
+ onErrorRef.current = options.onError;
17080
+ const enabled = options.enabled ?? true;
17081
+ const retainTerminalMs = options.retainTerminalMs ?? 0;
17082
+ const emptyStateTimeoutMs = options.emptyStateTimeoutMs ?? 250;
17083
+ React.useEffect(() => {
17084
+ if (!enabled) {
17085
+ return;
17086
+ }
17087
+ if (!dataSource.subscribeActiveRuns) {
17088
+ return;
17089
+ }
17090
+ const controller = new AbortController();
17091
+ let stagingTimer;
17092
+ const staging = {};
17093
+ let sawSnapshotRow = false;
17094
+ const pendingTerminalTimers = /* @__PURE__ */ new Map();
17095
+ const flushSnapshot = () => {
17096
+ setRuns((prev) => ({ ...prev, ...staging }));
17097
+ setReady(true);
17098
+ stagingTimer = void 0;
17099
+ };
17100
+ const subscription = dataSource.subscribeActiveRuns({
17101
+ signal: controller.signal,
17102
+ onError: (evt) => onErrorRef.current?.(evt),
17103
+ onEvent: (evt) => {
17104
+ if (evt.event === "snapshot") {
17105
+ sawSnapshotRow = true;
17106
+ const pending2 = pendingTerminalTimers.get(evt.sessionId);
17107
+ if (pending2 !== void 0) {
17108
+ clearTimeout(pending2);
17109
+ pendingTerminalTimers.delete(evt.sessionId);
17110
+ }
17111
+ staging[evt.sessionId] = {
17112
+ runId: evt.runId,
17113
+ status: evt.status,
17114
+ updatedAt: evt.updatedAt
17115
+ };
17116
+ if (stagingTimer === void 0) {
17117
+ stagingTimer = setTimeout(flushSnapshot, 16);
17118
+ }
17119
+ return;
17120
+ }
17121
+ if (TERMINAL.has(evt.status)) {
17122
+ if (retainTerminalMs <= 0) {
17123
+ setRuns((prev) => {
17124
+ if (!(evt.sessionId in prev)) {
17125
+ return prev;
17126
+ }
17127
+ const next = { ...prev };
17128
+ delete next[evt.sessionId];
17129
+ return next;
17130
+ });
17131
+ return;
17132
+ }
17133
+ setRuns((prev) => ({
17134
+ ...prev,
17135
+ [evt.sessionId]: {
17136
+ runId: evt.runId,
17137
+ status: evt.status,
17138
+ updatedAt: evt.updatedAt
17139
+ }
17140
+ }));
17141
+ const existing = pendingTerminalTimers.get(evt.sessionId);
17142
+ if (existing !== void 0) {
17143
+ clearTimeout(existing);
17144
+ }
17145
+ const handle = setTimeout(() => {
17146
+ pendingTerminalTimers.delete(evt.sessionId);
17147
+ setRuns((prev) => {
17148
+ if (!(evt.sessionId in prev)) {
17149
+ return prev;
17150
+ }
17151
+ const next = { ...prev };
17152
+ delete next[evt.sessionId];
17153
+ return next;
17154
+ });
17155
+ }, retainTerminalMs);
17156
+ pendingTerminalTimers.set(evt.sessionId, handle);
17157
+ return;
17158
+ }
17159
+ const pending = pendingTerminalTimers.get(evt.sessionId);
17160
+ if (pending !== void 0) {
17161
+ clearTimeout(pending);
17162
+ pendingTerminalTimers.delete(evt.sessionId);
17163
+ }
17164
+ setRuns((prev) => ({
17165
+ ...prev,
17166
+ [evt.sessionId]: {
17167
+ runId: evt.runId,
17168
+ status: evt.status,
17169
+ updatedAt: evt.updatedAt
17170
+ }
17171
+ }));
17172
+ }
17173
+ });
17174
+ subscription?.catch((err) => {
17175
+ if (controller.signal.aborted) {
17176
+ return;
17177
+ }
17178
+ const surface = err instanceof Event ? err : new Event("error");
17179
+ onErrorRef.current?.(surface);
17180
+ });
17181
+ const readyTimeout = setTimeout(() => {
17182
+ if (!sawSnapshotRow) {
17183
+ setReady(true);
17184
+ }
17185
+ }, emptyStateTimeoutMs);
17186
+ return () => {
17187
+ controller.abort();
17188
+ if (stagingTimer !== void 0) {
17189
+ clearTimeout(stagingTimer);
17190
+ }
17191
+ clearTimeout(readyTimeout);
17192
+ for (const handle of pendingTerminalTimers.values()) {
17193
+ clearTimeout(handle);
17194
+ }
17195
+ pendingTerminalTimers.clear();
17196
+ };
17197
+ }, [dataSource, enabled, retainTerminalMs, emptyStateTimeoutMs]);
17198
+ const status = React.useCallback(
17199
+ (sessionId) => runs[sessionId]?.status,
17200
+ [runs]
17201
+ );
17202
+ return { runs, ready, status };
17203
+ }
16896
17204
 
16897
17205
  // ui/src/bichat/index.ts
16898
17206
  init_useTranslation();
@@ -17505,42 +17813,66 @@ function useBichatRouter({
17505
17813
  pathname,
17506
17814
  onNavigate
17507
17815
  }) {
17816
+ const isAllChats = pathname.startsWith("/all-chats");
17508
17817
  const activeSessionId = React.useMemo(
17509
17818
  () => pathname.match(SESSION_PATH_REGEX)?.[1],
17510
17819
  [pathname]
17511
17820
  );
17821
+ const sidebarTab = React.useMemo(
17822
+ () => isAllChats ? "all-chats" : "my-chats",
17823
+ [isAllChats]
17824
+ );
17512
17825
  const maybeClose = React.useCallback(() => {
17513
17826
  onNavigate?.();
17514
17827
  }, [onNavigate]);
17515
17828
  const onSessionSelect = React.useCallback(
17516
17829
  (sessionId) => {
17517
17830
  if (sessionId) {
17518
- navigate(`/session/${sessionId}`);
17831
+ const prefix = isAllChats ? "/all-chats" : "";
17832
+ navigate(`${prefix}/session/${sessionId}`);
17519
17833
  } else {
17520
- navigate("/");
17834
+ navigate(isAllChats ? "/all-chats" : "/");
17521
17835
  }
17522
17836
  maybeClose();
17523
17837
  },
17524
- [navigate, maybeClose]
17838
+ [navigate, maybeClose, isAllChats]
17525
17839
  );
17526
17840
  const onNewChat = React.useCallback(() => {
17527
- navigate("/");
17841
+ navigate(isAllChats ? "/all-chats" : "/");
17528
17842
  maybeClose();
17529
- }, [navigate, maybeClose]);
17843
+ }, [navigate, maybeClose, isAllChats]);
17530
17844
  const onArchivedView = React.useCallback(() => {
17531
17845
  navigate("/archived");
17532
17846
  maybeClose();
17533
17847
  }, [navigate, maybeClose]);
17848
+ const onAllChatsView = React.useCallback(() => {
17849
+ navigate("/all-chats");
17850
+ maybeClose();
17851
+ }, [navigate, maybeClose]);
17534
17852
  const onBack = React.useCallback(() => {
17535
17853
  navigate("/");
17536
17854
  maybeClose();
17537
17855
  }, [navigate, maybeClose]);
17856
+ const onSidebarTabChange = React.useCallback(
17857
+ (tab) => {
17858
+ if (tab === "all-chats") {
17859
+ navigate("/all-chats");
17860
+ } else {
17861
+ navigate("/");
17862
+ }
17863
+ maybeClose();
17864
+ },
17865
+ [navigate, maybeClose]
17866
+ );
17538
17867
  return {
17539
17868
  activeSessionId,
17540
17869
  onSessionSelect,
17541
17870
  onNewChat,
17542
17871
  onArchivedView,
17543
- onBack
17872
+ onBack,
17873
+ onAllChatsView,
17874
+ sidebarTab,
17875
+ onSidebarTabChange
17544
17876
  };
17545
17877
  }
17546
17878
 
@@ -18938,6 +19270,8 @@ function toStreamEvent(chunk) {
18938
19270
  return chunk.sessionId ? { type: "user_message", sessionId: chunk.sessionId } : null;
18939
19271
  case "interrupt":
18940
19272
  return chunk.interrupt ? { type: "interrupt", interrupt: chunk.interrupt, sessionId: chunk.sessionId } : null;
19273
+ case "text_block_end":
19274
+ return { type: "text_block_end", seq: chunk.textBlockSeq ?? 0 };
18941
19275
  case "done":
18942
19276
  return { type: "done", sessionId: chunk.sessionId, generationMs: chunk.generationMs };
18943
19277
  case "error":
@@ -18947,6 +19281,90 @@ function toStreamEvent(chunk) {
18947
19281
  }
18948
19282
  }
18949
19283
 
19284
+ // ui/src/bichat/utils/eventNames.ts
19285
+ var STREAM_EVENT_TYPES = [
19286
+ "chunk",
19287
+ "content",
19288
+ "thinking",
19289
+ "tool_start",
19290
+ "tool_end",
19291
+ "text_block_end",
19292
+ "snapshot",
19293
+ "interrupt",
19294
+ "citation",
19295
+ "usage",
19296
+ "ping",
19297
+ "stream_started",
19298
+ "done",
19299
+ "cancelled",
19300
+ "error",
19301
+ "failed"
19302
+ ];
19303
+ var TERMINAL_STREAM_EVENT_TYPES = [
19304
+ "done",
19305
+ "cancelled",
19306
+ "error",
19307
+ "failed"
19308
+ ];
19309
+ function isTerminalEvent(name) {
19310
+ return TERMINAL_STREAM_EVENT_TYPES.includes(name);
19311
+ }
19312
+
19313
+ // ui/src/bichat/data/openManagedEventSource.ts
19314
+ function openManagedEventSource(opts) {
19315
+ const graceMs = opts.connectGraceMs ?? 500;
19316
+ return new Promise((resolve, reject) => {
19317
+ const startedAt = Date.now();
19318
+ const es = new EventSource(opts.url, {
19319
+ withCredentials: opts.withCredentials ?? true
19320
+ });
19321
+ let settled = false;
19322
+ let sawEvent = false;
19323
+ const settle = (err) => {
19324
+ if (settled) {
19325
+ return;
19326
+ }
19327
+ settled = true;
19328
+ es.close();
19329
+ if (err) {
19330
+ reject(err);
19331
+ } else {
19332
+ resolve();
19333
+ }
19334
+ };
19335
+ if (opts.signal) {
19336
+ if (opts.signal.aborted) {
19337
+ settle();
19338
+ return;
19339
+ }
19340
+ opts.signal.addEventListener("abort", () => settle(), { once: true });
19341
+ }
19342
+ const forward = (name) => (evt) => {
19343
+ sawEvent = true;
19344
+ let parsed;
19345
+ try {
19346
+ parsed = JSON.parse(evt.data);
19347
+ } catch {
19348
+ parsed = { __unparseable: true, raw: String(evt.data) };
19349
+ }
19350
+ try {
19351
+ opts.onMessage(name, parsed);
19352
+ } catch {
19353
+ }
19354
+ };
19355
+ for (const name of opts.events) {
19356
+ es.addEventListener(name, forward(name));
19357
+ }
19358
+ es.onerror = (evt) => {
19359
+ opts.onError?.(evt);
19360
+ if (graceMs > 0 && !sawEvent && Date.now() - startedAt < graceMs) {
19361
+ const err = opts.onConnectError ? opts.onConnectError(evt) : new Error("EventSource failed to connect before first event");
19362
+ settle(err);
19363
+ }
19364
+ };
19365
+ });
19366
+ }
19367
+
18950
19368
  // ui/src/bichat/data/AttachmentUploader.ts
18951
19369
  init_chartSpec();
18952
19370
  var MIME_TO_EXTENSION = {
@@ -19201,6 +19619,26 @@ async function ensureAttachmentUpload(attachment, context, uploadFileFn) {
19201
19619
  }
19202
19620
 
19203
19621
  // ui/src/bichat/data/MessageTransport.ts
19622
+ var RunEventsConnectError = class extends Error {
19623
+ constructor(message, cause) {
19624
+ super(message);
19625
+ this.name = "RunEventsConnectError";
19626
+ this.cause = cause;
19627
+ }
19628
+ };
19629
+ function generateRequestId() {
19630
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
19631
+ return crypto.randomUUID();
19632
+ }
19633
+ const bytes = new Uint8Array(16);
19634
+ for (let i = 0; i < bytes.length; i++) {
19635
+ bytes[i] = Math.floor(Math.random() * 256);
19636
+ }
19637
+ bytes[6] = bytes[6] & 15 | 64;
19638
+ bytes[8] = bytes[8] & 63 | 128;
19639
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
19640
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
19641
+ }
19204
19642
  async function* sendMessage(deps, sessionId, content, attachments = [], signal, options) {
19205
19643
  const abortController = new AbortController();
19206
19644
  let onExternalAbort;
@@ -19228,12 +19666,14 @@ async function* sendMessage(deps, sessionId, content, attachments = [], signal,
19228
19666
  sessionId,
19229
19667
  attachmentCount: streamAttachments.length
19230
19668
  });
19669
+ const requestId = options?.requestId ?? generateRequestId();
19231
19670
  const payload = {
19232
19671
  sessionId,
19233
19672
  content,
19234
19673
  debugMode: options?.debugMode ?? false,
19235
19674
  replaceFromMessageId: options?.replaceFromMessageID,
19236
- attachments: streamAttachments
19675
+ attachments: streamAttachments,
19676
+ requestId
19237
19677
  };
19238
19678
  if (options?.reasoningEffort) {
19239
19679
  payload.reasoningEffort = options.reasoningEffort;
@@ -19401,6 +19841,67 @@ async function resumeStream(deps, sessionId, runId, onChunk, signal) {
19401
19841
  }
19402
19842
  }
19403
19843
  }
19844
+ function subscribeRunEvents(deps, sessionId, runId, options) {
19845
+ const base = buildStreamUrl(deps, "/events");
19846
+ const qs = new URLSearchParams({ sessionId, runId });
19847
+ const url = `${base}?${qs.toString()}`;
19848
+ const withCursor = options.lastEventId ? `${url}&${new URLSearchParams({ lastEventId: options.lastEventId }).toString()}` : url;
19849
+ const settleController = new AbortController();
19850
+ if (options.signal) {
19851
+ if (options.signal.aborted) {
19852
+ settleController.abort();
19853
+ } else {
19854
+ options.signal.addEventListener("abort", () => settleController.abort(), {
19855
+ once: true
19856
+ });
19857
+ }
19858
+ }
19859
+ return openManagedEventSource({
19860
+ url: withCursor,
19861
+ events: STREAM_EVENT_TYPES,
19862
+ withCredentials: true,
19863
+ signal: settleController.signal,
19864
+ onError: options.onError,
19865
+ onConnectError: (evt) => new RunEventsConnectError(
19866
+ "EventSource failed to connect before first event",
19867
+ evt
19868
+ ),
19869
+ onMessage: (name, data) => {
19870
+ if (typeof data === "object" && data !== null && data.__unparseable) {
19871
+ options.onChunk({
19872
+ type: "error",
19873
+ error: `Failed to parse event: ${data.raw}`
19874
+ });
19875
+ return;
19876
+ }
19877
+ const parsed = data;
19878
+ if (!parsed.type) {
19879
+ parsed.type = name;
19880
+ }
19881
+ options.onChunk(parsed);
19882
+ if (isTerminalEvent(name) || isTerminalEvent(String(parsed.type))) {
19883
+ settleController.abort();
19884
+ }
19885
+ }
19886
+ });
19887
+ }
19888
+ function subscribeActiveRuns(deps, options) {
19889
+ const url = buildStreamUrl(deps, "/active-runs");
19890
+ return openManagedEventSource({
19891
+ url,
19892
+ events: ["snapshot", "update"],
19893
+ withCredentials: true,
19894
+ signal: options.signal,
19895
+ onError: options.onError,
19896
+ onMessage: (name, data) => {
19897
+ if (typeof data !== "object" || data === null || data.__unparseable) {
19898
+ return;
19899
+ }
19900
+ const body = data;
19901
+ options.onEvent({ event: name, ...body });
19902
+ }
19903
+ });
19904
+ }
19404
19905
  async function submitQuestionAnswers(callRPC, sessionId, questionId, answers) {
19405
19906
  try {
19406
19907
  const flatAnswers = {};
@@ -19669,6 +20170,39 @@ var HttpDataSource = class {
19669
20170
  signal
19670
20171
  );
19671
20172
  }
20173
+ /**
20174
+ * Open a native EventSource against GET /stream/events for the given
20175
+ * run. Used by components that want browser-native auto-reconnect
20176
+ * with Last-Event-ID (tab close, wifi drop, device switch). Prefer
20177
+ * over resumeStream when tailing an already-running generation that
20178
+ * another tab started.
20179
+ */
20180
+ subscribeRunEvents(sessionId, runId, options) {
20181
+ return subscribeRunEvents(
20182
+ {
20183
+ baseUrl: this.config.baseUrl,
20184
+ streamEndpoint: this.config.streamEndpoint
20185
+ },
20186
+ sessionId,
20187
+ runId,
20188
+ options
20189
+ );
20190
+ }
20191
+ /**
20192
+ * Subscribe to the per-tenant active-run fan-out
20193
+ * (GET /stream/active-runs). Never resolves until the caller aborts
20194
+ * via the signal; use from a top-level component (sidebar container)
20195
+ * that mounts for the lifetime of the chat app.
20196
+ */
20197
+ subscribeActiveRuns(options) {
20198
+ return subscribeActiveRuns(
20199
+ {
20200
+ baseUrl: this.config.baseUrl,
20201
+ streamEndpoint: this.config.streamEndpoint
20202
+ },
20203
+ options
20204
+ );
20205
+ }
19672
20206
  async *sendMessage(sessionId, content, attachments = [], signal, options) {
19673
20207
  this.abortController = new AbortController();
19674
20208
  let onExternalAbort;
@@ -19742,6 +20276,50 @@ function createHttpDataSource(config) {
19742
20276
  return new HttpDataSource(config);
19743
20277
  }
19744
20278
 
20279
+ // ui/src/bichat/utils/textBlocks.ts
20280
+ function splitIntoTextBlocks(content, offsets) {
20281
+ if (!content) {
20282
+ return [];
20283
+ }
20284
+ if (!offsets || offsets.length === 0) {
20285
+ return [{ seq: 0, content }];
20286
+ }
20287
+ const sanitized = [...offsets].map((n) => Math.max(0, Math.min(Math.floor(n), content.length))).sort((a, b) => a - b);
20288
+ const blocks = [];
20289
+ let cursor = 0;
20290
+ for (let i = 0; i < sanitized.length; i++) {
20291
+ const end = sanitized[i];
20292
+ if (end <= cursor) {
20293
+ continue;
20294
+ }
20295
+ const slice = content.slice(cursor, end);
20296
+ if (slice) {
20297
+ blocks.push({ seq: blocks.length, content: slice });
20298
+ }
20299
+ cursor = end;
20300
+ }
20301
+ if (cursor < content.length) {
20302
+ blocks.push({ seq: blocks.length, content: content.slice(cursor) });
20303
+ }
20304
+ return blocks;
20305
+ }
20306
+ function readTextBlockOffsets(partialMetadata) {
20307
+ if (!partialMetadata) {
20308
+ return [];
20309
+ }
20310
+ const raw = partialMetadata["text_block_offsets"];
20311
+ if (!Array.isArray(raw)) {
20312
+ return [];
20313
+ }
20314
+ const out = [];
20315
+ for (const entry of raw) {
20316
+ if (typeof entry === "number" && Number.isFinite(entry) && entry >= 0) {
20317
+ out.push(Math.floor(entry));
20318
+ }
20319
+ }
20320
+ return out;
20321
+ }
20322
+
19745
20323
  exports.ATTACHMENT_ACCEPT_ATTRIBUTE = ATTACHMENT_ACCEPT_ATTRIBUTE;
19746
20324
  exports.ActionButton = ActionButton;
19747
20325
  exports.ActivityTrace = ActivityTrace;
@@ -19793,6 +20371,8 @@ exports.QuestionForm = QuestionForm;
19793
20371
  exports.QuestionStep = QuestionStep;
19794
20372
  exports.RateLimiter = RateLimiter;
19795
20373
  exports.RetryActionArea = RetryActionArea;
20374
+ exports.RunEventsConnectError = RunEventsConnectError;
20375
+ exports.STREAM_EVENT_TYPES = STREAM_EVENT_TYPES;
19796
20376
  exports.ScreenReaderAnnouncer = ScreenReaderAnnouncer;
19797
20377
  exports.ScrollToBottomButton = ScrollToBottomButton;
19798
20378
  exports.SearchInput = MemoizedSearchInput;
@@ -19814,6 +20394,7 @@ exports.SourcesPanel = SourcesPanel;
19814
20394
  exports.StreamError = StreamError;
19815
20395
  exports.StreamingCursor = StreamingCursor;
19816
20396
  exports.SystemMessage = SystemMessage;
20397
+ exports.TERMINAL_STREAM_EVENT_TYPES = TERMINAL_STREAM_EVENT_TYPES;
19817
20398
  exports.TabbedChartGroup = TabbedChartGroup;
19818
20399
  exports.TabbedTableGroup = TabbedTableGroup;
19819
20400
  exports.ThemeProvider = ThemeProvider;
@@ -19851,6 +20432,7 @@ exports.groupSteps = groupSteps;
19851
20432
  exports.hasPermission = hasPermission;
19852
20433
  exports.isImageMimeType = isImageMimeType;
19853
20434
  exports.isPermissionDeniedError = isPermissionDeniedError;
20435
+ exports.isTerminalEvent = isTerminalEvent;
19854
20436
  exports.lightTheme = lightTheme;
19855
20437
  exports.listItemVariants = listItemVariants;
19856
20438
  exports.messageContainerVariants = messageContainerVariants;
@@ -19858,12 +20440,15 @@ exports.messageVariants = messageVariants;
19858
20440
  exports.parseBichatStream = parseBichatStream;
19859
20441
  exports.parseBichatStreamEvents = parseBichatStreamEvents;
19860
20442
  exports.parseSSEStream = parseSSEStream;
20443
+ exports.readTextBlockOffsets = readTextBlockOffsets;
19861
20444
  exports.scaleFadeVariants = scaleFadeVariants;
19862
20445
  exports.sessionItemVariants = sessionItemVariants;
20446
+ exports.splitIntoTextBlocks = splitIntoTextBlocks;
19863
20447
  exports.staggerContainerVariants = staggerContainerVariants;
19864
20448
  exports.toErrorDisplay = toErrorDisplay;
19865
20449
  exports.typingDotVariants = typingDotVariants;
19866
20450
  exports.useActionButtonContext = useActionButtonContext;
20451
+ exports.useActiveRuns = useActiveRuns;
19867
20452
  exports.useAttachments = useAttachments;
19868
20453
  exports.useAutoScroll = useAutoScroll;
19869
20454
  exports.useAvatarContext = useAvatarContext;