@iota-uz/sdk 0.4.23 → 0.4.24

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, memo, useRef, useEffect, useCallback, useState, u
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 { CaretUp, CaretDown, DotsThreeVertical, Check, Copy, X, Columns, ArrowsIn, ArrowsOut, Warning, ArrowClockwise, Paperclip, PaperPlaneRight, ChartBar, FileText, Lightbulb, CircleNotch, ArrowUUpLeft, PencilSimple, Bookmark, ArrowsClockwise, Archive, Trash, DotsThree, Image, MagnifyingGlass, DownloadSimple, ArrowCounterClockwise, Bug, ArrowUp, ArrowDown, Stack, ImageBroken, CaretLeft, CaretRight, Info, CheckCircle, XCircle, Spinner, WarningCircle, FilePdf, FileXls, FileCsv, FileDoc, FileCode, File as File$1, MagnifyingGlassMinus, MagnifyingGlassPlus, Download, ChatCircleDots, PencilSimpleLine, ArrowLeft, PaperPlaneTilt, ArrowRight, Timer, Lightning, Database, ArrowSquareOut, Wrench, ClockCounterClockwise, Package, Plus, ArrowsCounterClockwise, ChatCircle, Gear, Users, List, CaretLineLeft, CaretLineRight, Code, Table, SpinnerGap, FloppyDisk, Sidebar } from '@phosphor-icons/react';
5
+ import { CaretUp, CaretDown, DotsThreeVertical, Check, Copy, X, Columns, ArrowsIn, ArrowsOut, Warning, ArrowClockwise, Paperclip, PaperPlaneRight, ChartBar, FileText, Lightbulb, CircleNotch, ArrowUUpLeft, PencilSimple, Bookmark, ArrowsClockwise, Archive, Trash, UsersThree, DotsThree, Image, MagnifyingGlass, DownloadSimple, ArrowCounterClockwise, Bug, ArrowUp, ArrowDown, Stack, ImageBroken, CaretLeft, CaretRight, Info, CheckCircle, XCircle, Spinner, WarningCircle, FilePdf, FileXls, FileCsv, FileDoc, FileCode, File as File$1, MagnifyingGlassMinus, MagnifyingGlassPlus, Download, ChatCircleDots, PencilSimpleLine, ArrowLeft, PaperPlaneTilt, ArrowRight, Timer, Lightning, Database, ArrowSquareOut, Wrench, ClockCounterClockwise, Package, Plus, Crown, UserPlus, ArrowsCounterClockwise, ChatCircle, Gear, Users, List, CaretLineLeft, CaretLineRight, Code, Table, SpinnerGap, FloppyDisk, ShareNetwork, 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';
@@ -12,7 +12,7 @@ import rehypeKatex from 'rehype-katex';
12
12
  import { motion, useMotionValue, useTransform, AnimatePresence, MotionConfig, useReducedMotion } from 'framer-motion';
13
13
  import 'react-dom/client';
14
14
  import { differenceInMinutes, differenceInHours, differenceInDays, format, isSameDay, startOfDay, isToday, isYesterday } from 'date-fns';
15
- import { Menu, MenuButton, MenuItems, MenuItem, Popover, PopoverButton, PopoverPanel, Dialog, DialogBackdrop, DialogPanel, DialogTitle, Description, Transition } from '@headlessui/react';
15
+ import { Menu, MenuButton, MenuItems, MenuItem, Popover, PopoverButton, PopoverPanel, Dialog, DialogBackdrop, DialogPanel, Transition } from '@headlessui/react';
16
16
  import { createPortal } from 'react-dom';
17
17
 
18
18
  var __defProp = Object.defineProperty;
@@ -3291,12 +3291,151 @@ function useBranding() {
3291
3291
  }, [context.extensions?.branding, t]);
3292
3292
  return branding;
3293
3293
  }
3294
- function ChatHeader({ session, onBack, readOnly, logoSlot, actionsSlot }) {
3294
+ function hashString(str) {
3295
+ let hash = 0;
3296
+ for (let i = 0; i < str.length; i++) {
3297
+ const char = str.charCodeAt(i);
3298
+ hash = (hash << 5) - hash + char;
3299
+ hash = hash & hash;
3300
+ }
3301
+ return Math.abs(hash);
3302
+ }
3303
+ var colorPalette = [
3304
+ { bg: "bg-blue-500", text: "text-white" },
3305
+ { bg: "bg-green-500", text: "text-white" },
3306
+ { bg: "bg-purple-500", text: "text-white" },
3307
+ { bg: "bg-pink-500", text: "text-white" },
3308
+ { bg: "bg-indigo-500", text: "text-white" },
3309
+ { bg: "bg-teal-500", text: "text-white" },
3310
+ { bg: "bg-orange-500", text: "text-white" },
3311
+ { bg: "bg-cyan-500", text: "text-white" },
3312
+ { bg: "bg-amber-500", text: "text-white" },
3313
+ { bg: "bg-lime-500", text: "text-white" }
3314
+ ];
3315
+ var sizeClasses = {
3316
+ xs: "w-6 h-6 text-[10px]",
3317
+ sm: "w-8 h-8 text-xs",
3318
+ md: "w-10 h-10 text-sm",
3319
+ lg: "w-12 h-12 text-base"
3320
+ };
3321
+ function UserAvatar({
3322
+ firstName,
3323
+ lastName,
3324
+ initials: providedInitials,
3325
+ size = "md",
3326
+ className = ""
3327
+ }) {
3328
+ const derivedInitials = (() => {
3329
+ const firstChar = firstName?.trim()?.charAt(0) || "";
3330
+ const lastChar = lastName?.trim()?.charAt(0) || "";
3331
+ const combined = `${firstChar}${lastChar}`.trim();
3332
+ return combined || "U";
3333
+ })();
3334
+ const initials = (providedInitials?.trim() || derivedInitials).toUpperCase();
3335
+ const fullName = `${firstName}${lastName}`;
3336
+ const colorIndex = hashString(fullName) % colorPalette.length;
3337
+ const colors = colorPalette[colorIndex];
3338
+ return /* @__PURE__ */ jsx(
3339
+ "div",
3340
+ {
3341
+ className: `
3342
+ ${sizeClasses[size]}
3343
+ ${colors.bg}
3344
+ ${colors.text}
3345
+ ${className}
3346
+ rounded-full
3347
+ flex
3348
+ items-center
3349
+ justify-center
3350
+ font-semibold
3351
+ flex-shrink-0
3352
+ select-none
3353
+ `,
3354
+ "aria-label": `${firstName} ${lastName}`,
3355
+ title: `${firstName} ${lastName}`,
3356
+ children: initials
3357
+ }
3358
+ );
3359
+ }
3360
+ var MemoizedUserAvatar = memo(UserAvatar);
3361
+ MemoizedUserAvatar.displayName = "UserAvatar";
3362
+ var overlapClasses = {
3363
+ xs: "-ml-1.5",
3364
+ sm: "-ml-2"
3365
+ };
3366
+ var badgeSizeClasses = {
3367
+ xs: "w-6 h-6 text-[10px]",
3368
+ sm: "w-8 h-8 text-xs"
3369
+ };
3370
+ function AvatarStackInner({
3371
+ users,
3372
+ max = 3,
3373
+ size = "sm",
3374
+ onClick,
3375
+ className = ""
3376
+ }) {
3377
+ const visible = users.slice(0, max);
3378
+ const overflow = users.length - max;
3379
+ const interactive = typeof onClick === "function";
3380
+ const overlap = overlapClasses[size];
3381
+ const badgeSize = badgeSizeClasses[size];
3382
+ const handleKeyDown = (e) => {
3383
+ if (interactive && (e.key === "Enter" || e.key === " ")) {
3384
+ e.preventDefault();
3385
+ onClick();
3386
+ }
3387
+ };
3388
+ return /* @__PURE__ */ jsxs(
3389
+ "div",
3390
+ {
3391
+ className: `inline-flex items-center ${interactive ? "cursor-pointer transition-opacity hover:opacity-80" : ""} ${className}`,
3392
+ onClick: interactive ? onClick : void 0,
3393
+ onKeyDown: interactive ? handleKeyDown : void 0,
3394
+ role: interactive ? "button" : void 0,
3395
+ tabIndex: interactive ? 0 : void 0,
3396
+ "aria-label": interactive ? `${users.length} members` : void 0,
3397
+ children: [
3398
+ visible.map((user, i) => /* @__PURE__ */ jsx(
3399
+ "div",
3400
+ {
3401
+ className: `${i > 0 ? overlap : ""} ring-2 ring-white dark:ring-gray-900 rounded-full`,
3402
+ style: { zIndex: visible.length - i },
3403
+ children: /* @__PURE__ */ jsx(
3404
+ MemoizedUserAvatar,
3405
+ {
3406
+ firstName: user.firstName,
3407
+ lastName: user.lastName,
3408
+ initials: user.initials,
3409
+ size
3410
+ }
3411
+ )
3412
+ },
3413
+ `${user.firstName}-${user.lastName}-${i}`
3414
+ )),
3415
+ overflow > 0 && /* @__PURE__ */ jsxs(
3416
+ "div",
3417
+ {
3418
+ className: `${overlap} ${badgeSize} rounded-full bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 font-medium flex items-center justify-center flex-shrink-0 ring-2 ring-white dark:ring-gray-900`,
3419
+ style: { zIndex: 0 },
3420
+ children: [
3421
+ "+",
3422
+ overflow
3423
+ ]
3424
+ }
3425
+ )
3426
+ ]
3427
+ }
3428
+ );
3429
+ }
3430
+ var AvatarStack = memo(AvatarStackInner);
3431
+ AvatarStack.displayName = "AvatarStack";
3432
+ function ChatHeader({ session, onBack, readOnly, logoSlot, actionsSlot, members, onMembersClick }) {
3295
3433
  const { t } = useTranslation();
3296
3434
  const branding = useBranding();
3297
3435
  const BackButton = onBack ? /* @__PURE__ */ jsx(
3298
3436
  "button",
3299
3437
  {
3438
+ type: "button",
3300
3439
  onClick: onBack,
3301
3440
  className: "cursor-pointer p-2 hover:bg-gray-100 dark:hover:bg-gray-700 active:bg-gray-200 dark:active:bg-gray-600 rounded-lg transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50",
3302
3441
  "aria-label": t("BiChat.Chat.GoBack"),
@@ -3315,23 +3454,42 @@ function ChatHeader({ session, onBack, readOnly, logoSlot, actionsSlot }) {
3315
3454
  ] }) });
3316
3455
  }
3317
3456
  const resolvedSessionTitle = session.title?.trim() || t("BiChat.Chat.NewChat");
3457
+ const isGroupSession = Boolean(session.isGroup || session.memberCount && session.memberCount > 1);
3458
+ const memberCount = session.memberCount ?? 0;
3459
+ const stackUsers = members && members.length > 0 ? members : [];
3318
3460
  return /* @__PURE__ */ jsx("header", { className: "bichat-header border-b border-gray-200 dark:border-gray-700 px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
3319
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3461
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
3320
3462
  BackButton,
3321
3463
  Logo,
3322
- /* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold text-[var(--bichat-text)]", children: resolvedSessionTitle }),
3323
- session.pinned && /* @__PURE__ */ jsx(
3324
- "svg",
3325
- {
3326
- className: "w-4 h-4 text-[var(--bichat-primary)]",
3327
- fill: "currentColor",
3328
- viewBox: "0 0 20 20",
3329
- "aria-label": t("BiChat.Chat.Pinned"),
3330
- children: /* @__PURE__ */ jsx("path", { d: "M10 2a1 1 0 011 1v1.323l3.954 1.582 1.599-.8a1 1 0 01.894 1.79l-1.233.616 1.738 5.42a1 1 0 01-.285 1.05A3.989 3.989 0 0115 15a3.989 3.989 0 01-2.667-1.019 1 1 0 01-.285-1.05l1.715-5.349L11 6.477V16h2a1 1 0 110 2H7a1 1 0 110-2h2V6.477L6.237 7.582l1.715 5.349a1 1 0 01-.285 1.05A3.989 3.989 0 015 15a3.989 3.989 0 01-2.667-1.019 1 1 0 01-.285-1.05l1.738-5.42-1.233-.617a1 1 0 01.894-1.788l1.599.799L9 4.323V3a1 1 0 011-1z" })
3331
- }
3332
- )
3464
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
3465
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
3466
+ /* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold text-[var(--bichat-text)] truncate", children: resolvedSessionTitle }),
3467
+ session.pinned && /* @__PURE__ */ jsx(
3468
+ "svg",
3469
+ {
3470
+ className: "w-4 h-4 text-[var(--bichat-primary)] flex-shrink-0",
3471
+ fill: "currentColor",
3472
+ viewBox: "0 0 20 20",
3473
+ role: "img",
3474
+ "aria-label": t("BiChat.Chat.Pinned"),
3475
+ children: /* @__PURE__ */ jsx("path", { d: "M10 2a1 1 0 011 1v1.323l3.954 1.582 1.599-.8a1 1 0 01.894 1.79l-1.233.616 1.738 5.42a1 1 0 01-.285 1.05A3.989 3.989 0 0115 15a3.989 3.989 0 01-2.667-1.019 1 1 0 01-.285-1.05l1.715-5.349L11 6.477V16h2a1 1 0 110 2H7a1 1 0 110-2h2V6.477L6.237 7.582l1.715 5.349a1 1 0 01-.285 1.05A3.989 3.989 0 015 15a3.989 3.989 0 01-2.667-1.019 1 1 0 01-.285-1.05l1.738-5.42-1.233-.617a1 1 0 01.894-1.788l1.599.799L9 4.323V3a1 1 0 011-1z" })
3476
+ }
3477
+ ),
3478
+ isGroupSession && stackUsers.length > 0 && /* @__PURE__ */ jsx(
3479
+ AvatarStack,
3480
+ {
3481
+ users: stackUsers,
3482
+ max: 3,
3483
+ size: "xs",
3484
+ onClick: onMembersClick,
3485
+ className: "flex-shrink-0"
3486
+ }
3487
+ )
3488
+ ] }),
3489
+ isGroupSession && memberCount > 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-600 dark:text-gray-400 mt-0.5", children: memberCount === 1 ? t("BiChat.Chat.OneMember") : t("BiChat.Chat.MemberCount").replace("{{count}}", String(memberCount)) })
3490
+ ] })
3333
3491
  ] }),
3334
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3492
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
3335
3493
  readOnly && /* @__PURE__ */ jsx("span", { className: "px-2 py-1 text-xs bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-200 rounded", children: t("BiChat.Chat.ReadOnly") }),
3336
3494
  session.status === "archived" && /* @__PURE__ */ jsx("span", { className: "px-2 py-1 text-xs bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded", children: t("BiChat.Chat.Archived") }),
3337
3495
  actionsSlot
@@ -4206,6 +4364,7 @@ function UserMessage({
4206
4364
  turn,
4207
4365
  turnId,
4208
4366
  initials = "U",
4367
+ authorName,
4209
4368
  slots,
4210
4369
  classNames: classNameOverrides,
4211
4370
  onCopy,
@@ -4413,6 +4572,16 @@ function UserMessage({
4413
4572
  };
4414
4573
  return /* @__PURE__ */ jsxs("div", { className: classes.root, children: [
4415
4574
  /* @__PURE__ */ jsxs("div", { className: classes.wrapper, children: [
4575
+ authorName && /* @__PURE__ */ jsx(
4576
+ "span",
4577
+ {
4578
+ id: `${turn.id}-author`,
4579
+ role: "note",
4580
+ "aria-label": authorName,
4581
+ className: "mb-1 px-1 text-[11px] text-right text-gray-500 dark:text-gray-400",
4582
+ children: authorName
4583
+ }
4584
+ ),
4416
4585
  normalizedAttachments.length > 0 && /* @__PURE__ */ jsx("div", { className: classes.attachments, children: renderSlot(
4417
4586
  slots?.attachments,
4418
4587
  attachmentsSlotProps,
@@ -4424,20 +4593,28 @@ function UserMessage({
4424
4593
  }
4425
4594
  )
4426
4595
  ) }),
4427
- turn.content && /* @__PURE__ */ jsx("div", { ref: bubbleRef, className: classes.bubble, children: /* @__PURE__ */ jsx("div", { className: classes.content, children: isEditing ? /* @__PURE__ */ jsx(
4428
- EditForm,
4596
+ turn.content && /* @__PURE__ */ jsx(
4597
+ "div",
4429
4598
  {
4430
- draftContent,
4431
- onDraftChange: handleDraftChange,
4432
- onSave: handleEditSave,
4433
- onCancel: handleEditCancel,
4434
- onKeyDown: handleEditKeyDown,
4435
- textareaRef: editTextareaRef,
4436
- disabled: false,
4437
- originalContent: turn.content,
4438
- t
4599
+ ref: bubbleRef,
4600
+ className: classes.bubble,
4601
+ "aria-describedby": authorName ? `${turn.id}-author` : void 0,
4602
+ children: /* @__PURE__ */ jsx("div", { className: classes.content, children: isEditing ? /* @__PURE__ */ jsx(
4603
+ EditForm,
4604
+ {
4605
+ draftContent,
4606
+ onDraftChange: handleDraftChange,
4607
+ onSave: handleEditSave,
4608
+ onCancel: handleEditCancel,
4609
+ onKeyDown: handleEditKeyDown,
4610
+ textareaRef: editTextareaRef,
4611
+ disabled: false,
4612
+ originalContent: turn.content,
4613
+ t
4614
+ }
4615
+ ) : renderSlot(slots?.content, contentSlotProps, turn.content) })
4439
4616
  }
4440
- ) : renderSlot(slots?.content, contentSlotProps, turn.content) }) }),
4617
+ ),
4441
4618
  !hideActions && /* @__PURE__ */ jsx("div", { className: `${classes.actions} ${isCopied ? "opacity-100" : ""}`, children: renderSlot(
4442
4619
  slots?.actions,
4443
4620
  actionsSlotProps,
@@ -4486,19 +4663,25 @@ function UserTurnView({
4486
4663
  turn,
4487
4664
  slots,
4488
4665
  classNames,
4489
- initials = "U",
4666
+ initials,
4490
4667
  hideAvatar,
4491
4668
  hideActions,
4492
4669
  hideTimestamp,
4493
- allowEdit
4670
+ allowEdit,
4671
+ showAuthorName = false
4494
4672
  }) {
4495
4673
  const { handleEdit, handleCopy } = useChatMessaging();
4674
+ const author = turn.userTurn.author;
4675
+ const fullName = [author?.firstName || "", author?.lastName || ""].join(" ").trim();
4676
+ const authorName = showAuthorName && fullName.length > 0 ? fullName : void 0;
4677
+ const resolvedInitials = initials ?? author?.initials ?? "U";
4496
4678
  return /* @__PURE__ */ jsx(
4497
4679
  UserMessage,
4498
4680
  {
4499
4681
  turn: turn.userTurn,
4500
4682
  turnId: turn.id,
4501
- initials,
4683
+ initials: resolvedInitials,
4684
+ authorName,
4502
4685
  slots,
4503
4686
  classNames,
4504
4687
  onCopy: handleCopy,
@@ -8493,7 +8676,7 @@ function StreamingBubble({ content, normalizedContent }) {
8493
8676
  }
8494
8677
  function MessageList({ renderUserTurn, renderAssistantTurn, thinkingVerbs, readOnly }) {
8495
8678
  const { t } = useTranslation();
8496
- const { currentSessionId, fetching } = useChatSession();
8679
+ const { session, currentSessionId, fetching } = useChatSession();
8497
8680
  const {
8498
8681
  turns,
8499
8682
  streamingContent,
@@ -8508,6 +8691,7 @@ function MessageList({ renderUserTurn, renderAssistantTurn, thinkingVerbs, readO
8508
8691
  () => streamingContent ? normalizeStreamingMarkdown(streamingContent) : "",
8509
8692
  [streamingContent]
8510
8693
  );
8694
+ const showAuthorNames = Boolean(session?.isGroup);
8511
8695
  const showEphemeral = showActivityTrace || showTypingIndicator;
8512
8696
  return /* @__PURE__ */ jsxs("div", { className: "relative flex-1 min-h-0", children: [
8513
8697
  /* @__PURE__ */ jsx("div", { ref: containerRef, className: "h-full overflow-y-auto overflow-x-hidden px-4 py-6", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto space-y-6", children: [
@@ -8517,6 +8701,10 @@ function MessageList({ renderUserTurn, renderAssistantTurn, thinkingVerbs, readO
8517
8701
  const prevDate = index > 0 ? new Date(turns[index - 1].createdAt) : null;
8518
8702
  const showDateSeparator = !!prevDate && !isSameDay(turnDate, prevDate);
8519
8703
  const isLast = index === turns.length - 1;
8704
+ const userTurnProps = {
8705
+ allowEdit: readOnly ? false : isLast,
8706
+ showAuthorName: showAuthorNames
8707
+ };
8520
8708
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
8521
8709
  showDateSeparator && /* @__PURE__ */ jsx(DateSeparator, { date: turnDate }),
8522
8710
  /* @__PURE__ */ jsx(
@@ -8526,7 +8714,7 @@ function MessageList({ renderUserTurn, renderAssistantTurn, thinkingVerbs, readO
8526
8714
  isLastTurn: isLast,
8527
8715
  renderUserTurn,
8528
8716
  renderAssistantTurn,
8529
- userTurnProps: readOnly ? { allowEdit: false } : { allowEdit: isLast },
8717
+ userTurnProps,
8530
8718
  assistantTurnProps: readOnly ? { allowRegenerate: false } : void 0
8531
8719
  }
8532
8720
  )
@@ -10732,71 +10920,689 @@ function SessionArtifactsPanel({
10732
10920
  }
10733
10921
  );
10734
10922
  }
10923
+ var DialogContext = createContext(null);
10924
+ var FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
10925
+ function InlineDialog({ open, onClose, className, children }) {
10926
+ const containerRef = useRef(null);
10927
+ const previousFocusRef = useRef(null);
10928
+ useEffect(() => {
10929
+ if (!open) {
10930
+ return;
10931
+ }
10932
+ previousFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
10933
+ const container = containerRef.current;
10934
+ if (!container) {
10935
+ return;
10936
+ }
10937
+ const getFocusable = () => Array.from(
10938
+ container.querySelectorAll(FOCUSABLE_SELECTOR)
10939
+ );
10940
+ const handler = (e) => {
10941
+ if (e.key === "Escape") {
10942
+ onClose();
10943
+ return;
10944
+ }
10945
+ if (e.key !== "Tab") {
10946
+ return;
10947
+ }
10948
+ const focusable = getFocusable();
10949
+ if (focusable.length === 0) {
10950
+ e.preventDefault();
10951
+ container.focus();
10952
+ return;
10953
+ }
10954
+ const first = focusable[0];
10955
+ const last = focusable[focusable.length - 1];
10956
+ if (e.shiftKey && document.activeElement === first) {
10957
+ e.preventDefault();
10958
+ last.focus();
10959
+ } else if (!e.shiftKey && document.activeElement === last) {
10960
+ e.preventDefault();
10961
+ first.focus();
10962
+ }
10963
+ };
10964
+ (getFocusable()[0] ?? container)?.focus();
10965
+ container.addEventListener("keydown", handler);
10966
+ return () => {
10967
+ container.removeEventListener("keydown", handler);
10968
+ previousFocusRef.current?.focus();
10969
+ };
10970
+ }, [open, onClose]);
10971
+ if (!open) {
10972
+ return null;
10973
+ }
10974
+ return /* @__PURE__ */ jsx(DialogContext.Provider, { value: onClose, children: /* @__PURE__ */ jsx(
10975
+ "div",
10976
+ {
10977
+ ref: containerRef,
10978
+ className,
10979
+ onClick: onClose,
10980
+ tabIndex: -1,
10981
+ children
10982
+ }
10983
+ ) });
10984
+ }
10985
+ function InlineDialogBackdrop(props) {
10986
+ return /* @__PURE__ */ jsx("div", { "aria-hidden": "true", ...props });
10987
+ }
10988
+ function InlineDialogPanel({
10989
+ children,
10990
+ onClick,
10991
+ ...rest
10992
+ }) {
10993
+ const ref = useRef(null);
10994
+ useEffect(() => {
10995
+ if (!ref.current) {
10996
+ return;
10997
+ }
10998
+ const target = ref.current.querySelector("[data-autofocus]") ?? ref.current.querySelector(
10999
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
11000
+ );
11001
+ target?.focus();
11002
+ }, []);
11003
+ return /* @__PURE__ */ jsx(
11004
+ "div",
11005
+ {
11006
+ ref,
11007
+ role: "dialog",
11008
+ "aria-modal": "true",
11009
+ onClick: (e) => {
11010
+ e.stopPropagation();
11011
+ onClick?.(e);
11012
+ },
11013
+ ...rest,
11014
+ children
11015
+ }
11016
+ );
11017
+ }
11018
+ function InlineDialogTitle(props) {
11019
+ return /* @__PURE__ */ jsx("h2", { ...props });
11020
+ }
11021
+ function InlineDialogDescription(props) {
11022
+ return /* @__PURE__ */ jsx("p", { ...props });
11023
+ }
10735
11024
 
10736
- // ui/src/bichat/components/StreamError.tsx
11025
+ // ui/src/bichat/components/SessionMembersModal.tsx
10737
11026
  init_useTranslation();
10738
- function StreamError({
10739
- error,
10740
- onRetry,
10741
- onRegenerate,
10742
- onDismiss,
10743
- compact = false
11027
+ init_useTranslation();
11028
+ function ConfirmModalBase({
11029
+ isOpen,
11030
+ title,
11031
+ message,
11032
+ onConfirm,
11033
+ onCancel,
11034
+ confirmText,
11035
+ cancelText,
11036
+ isDanger = false
10744
11037
  }) {
10745
11038
  const { t } = useTranslation();
10746
- return /* @__PURE__ */ jsxs(
10747
- motion.div,
10748
- {
10749
- initial: { opacity: 0, y: 10 },
10750
- animate: { opacity: 1, y: 0 },
10751
- exit: { opacity: 0, y: -10 },
10752
- className: `flex items-start gap-3 ${compact ? "px-3 py-2.5" : "px-4 py-3"} bg-red-50 dark:bg-red-950/40 border border-red-200/80 dark:border-red-900/60 rounded-xl shadow-sm`,
10753
- role: "alert",
10754
- children: [
10755
- /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 mt-0.5 flex items-center justify-center w-7 h-7 rounded-full bg-red-100 dark:bg-red-900/40", children: /* @__PURE__ */ jsx(
10756
- Warning,
10757
- {
10758
- className: "w-4 h-4 text-red-600 dark:text-red-400",
10759
- weight: "fill"
10760
- }
10761
- ) }),
11039
+ const resolvedConfirmText = confirmText?.trim() ? confirmText : t("BiChat.Common.Confirm");
11040
+ const resolvedCancelText = cancelText?.trim() ? cancelText : t("BiChat.Common.Cancel");
11041
+ return /* @__PURE__ */ jsxs(InlineDialog, { open: isOpen, onClose: onCancel, className: "relative z-40", children: [
11042
+ /* @__PURE__ */ jsx(InlineDialogBackdrop, { className: "fixed inset-0 bg-black/40 dark:bg-black/60 backdrop-blur-sm transition-opacity duration-200" }),
11043
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 flex items-center justify-center z-50 p-4", children: /* @__PURE__ */ jsxs(InlineDialogPanel, { className: "bg-white dark:bg-gray-800 rounded-2xl shadow-xl dark:shadow-2xl dark:shadow-black/30 max-w-sm w-full overflow-hidden", children: [
11044
+ /* @__PURE__ */ jsx("div", { className: "px-6 pt-6 pb-5", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-4", children: [
11045
+ isDanger && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 flex items-center justify-center w-10 h-10 rounded-xl bg-red-50 dark:bg-red-950/40 border border-red-200/60 dark:border-red-800/40", children: /* @__PURE__ */ jsx(WarningCircle, { size: 22, weight: "duotone", className: "text-red-600 dark:text-red-400" }) }),
10762
11046
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
10763
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-red-800 dark:text-red-200 leading-snug", children: t("BiChat.Error.Generic") }),
10764
- /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-red-600/80 dark:text-red-400/70 break-words leading-relaxed", children: error }),
10765
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
10766
- onRetry && /* @__PURE__ */ jsxs(
10767
- "button",
10768
- {
10769
- onClick: onRetry,
10770
- className: "cursor-pointer inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-white bg-red-600 hover:bg-red-700 active:bg-red-800 dark:bg-red-700 dark:hover:bg-red-600 rounded-lg transition-colors shadow-sm",
10771
- type: "button",
10772
- children: [
10773
- /* @__PURE__ */ jsx(ArrowClockwise, { className: "w-3.5 h-3.5" }),
10774
- t("BiChat.StreamError.Retry")
10775
- ]
10776
- }
10777
- ),
10778
- onRegenerate && /* @__PURE__ */ jsxs(
10779
- "button",
10780
- {
10781
- onClick: onRegenerate,
10782
- className: "cursor-pointer inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-lg transition-colors shadow-sm",
10783
- type: "button",
10784
- children: [
10785
- /* @__PURE__ */ jsx(ArrowsCounterClockwise, { className: "w-3.5 h-3.5" }),
10786
- t("BiChat.StreamError.Regenerate")
10787
- ]
10788
- }
10789
- )
10790
- ] })
10791
- ] }),
10792
- onDismiss && /* @__PURE__ */ jsx(
11047
+ /* @__PURE__ */ jsx(InlineDialogTitle, { className: "text-base font-semibold text-gray-900 dark:text-gray-100 leading-snug", children: title }),
11048
+ /* @__PURE__ */ jsx(InlineDialogDescription, { className: "mt-2 text-sm text-gray-600 dark:text-gray-400 leading-relaxed", children: message })
11049
+ ] })
11050
+ ] }) }),
11051
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2.5 px-6 pb-5", children: [
11052
+ /* @__PURE__ */ jsx(
10793
11053
  "button",
10794
11054
  {
10795
- onClick: onDismiss,
10796
- className: "cursor-pointer flex-shrink-0 mt-0.5 inline-flex items-center justify-center w-6 h-6 text-red-400 dark:text-red-500 hover:text-red-600 dark:hover:text-red-300 hover:bg-red-100 dark:hover:bg-red-900/40 rounded-md transition-colors",
10797
11055
  type: "button",
10798
- "aria-label": t("BiChat.Chat.DismissNotification"),
10799
- children: /* @__PURE__ */ jsx(X, { className: "w-3.5 h-3.5" })
11056
+ onClick: onCancel,
11057
+ ...isDanger ? { "data-autofocus": true } : {},
11058
+ className: "cursor-pointer px-4 py-2 text-sm font-medium rounded-xl text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700/60 hover:bg-gray-200 dark:hover:bg-gray-700 active:bg-gray-250 dark:active:bg-gray-600 transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-800",
11059
+ "data-testid": "confirm-modal-cancel",
11060
+ children: resolvedCancelText
11061
+ }
11062
+ ),
11063
+ /* @__PURE__ */ jsx(
11064
+ "button",
11065
+ {
11066
+ type: "button",
11067
+ ...!isDanger ? { "data-autofocus": true } : {},
11068
+ onClick: onConfirm,
11069
+ className: [
11070
+ "cursor-pointer px-4 py-2 text-sm font-medium rounded-xl text-white",
11071
+ "transition-all duration-150 shadow-sm hover:shadow",
11072
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-800",
11073
+ isDanger ? "bg-red-600 hover:bg-red-700 active:bg-red-800 focus-visible:ring-red-500/50" : "bg-primary-600 hover:bg-primary-700 active:bg-primary-800 focus-visible:ring-primary-500/50"
11074
+ ].join(" "),
11075
+ "data-testid": "confirm-modal-confirm",
11076
+ children: resolvedConfirmText
11077
+ }
11078
+ )
11079
+ ] })
11080
+ ] }) })
11081
+ ] });
11082
+ }
11083
+ var ConfirmModal = memo(ConfirmModalBase);
11084
+ ConfirmModal.displayName = "ConfirmModal";
11085
+ var ConfirmModal_default = ConfirmModal;
11086
+ var ROLES = ["editor", "viewer"];
11087
+ function RoleSegmentedControl({
11088
+ value,
11089
+ onChange,
11090
+ disabled,
11091
+ size = "md",
11092
+ t
11093
+ }) {
11094
+ const btnBase = size === "sm" ? "px-2 py-0.5 text-[11px]" : "px-3 py-1 text-xs";
11095
+ const currentIndex = ROLES.indexOf(value);
11096
+ const handleKeyDown = (e) => {
11097
+ if (disabled) {
11098
+ return;
11099
+ }
11100
+ let nextIndex = null;
11101
+ if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
11102
+ e.preventDefault();
11103
+ nextIndex = currentIndex <= 0 ? ROLES.length - 1 : currentIndex - 1;
11104
+ } else if (e.key === "ArrowRight" || e.key === "ArrowDown") {
11105
+ e.preventDefault();
11106
+ nextIndex = currentIndex >= ROLES.length - 1 ? 0 : currentIndex + 1;
11107
+ }
11108
+ if (nextIndex !== null) {
11109
+ const nextRole = ROLES[nextIndex];
11110
+ onChange(nextRole);
11111
+ const target = e.currentTarget.querySelector(`[data-role="${nextRole}"]`);
11112
+ target?.focus();
11113
+ }
11114
+ };
11115
+ return /* @__PURE__ */ jsx(
11116
+ "div",
11117
+ {
11118
+ role: "radiogroup",
11119
+ "aria-label": t("BiChat.Share.RoleLabel"),
11120
+ className: "inline-flex rounded-lg border border-gray-200 dark:border-gray-700 p-0.5 bg-gray-50 dark:bg-gray-800/50",
11121
+ onKeyDown: handleKeyDown,
11122
+ children: ROLES.map((role) => /* @__PURE__ */ jsx(
11123
+ "button",
11124
+ {
11125
+ type: "button",
11126
+ role: "radio",
11127
+ "aria-checked": value === role,
11128
+ tabIndex: value === role ? 0 : -1,
11129
+ "data-role": role,
11130
+ disabled,
11131
+ onClick: () => onChange(role),
11132
+ className: `${btnBase} cursor-pointer rounded-md font-medium transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 disabled:opacity-50 disabled:cursor-not-allowed ${value === role ? "bg-primary-600 text-white shadow-sm" : "text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100"}`,
11133
+ children: role === "editor" ? t("BiChat.Share.RoleEditor") : t("BiChat.Share.RoleViewer")
11134
+ },
11135
+ role
11136
+ ))
11137
+ }
11138
+ );
11139
+ }
11140
+ function MemberSkeleton() {
11141
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-xl px-3 py-2.5", "aria-hidden": "true", children: [
11142
+ /* @__PURE__ */ jsx("div", { className: "w-8 h-8 rounded-full bg-gray-200 dark:bg-gray-700 animate-pulse flex-shrink-0" }),
11143
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1.5", children: [
11144
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-28 rounded bg-gray-200 dark:bg-gray-700 animate-pulse" }),
11145
+ /* @__PURE__ */ jsx("div", { className: "h-2.5 w-16 rounded bg-gray-100 dark:bg-gray-800 animate-pulse" })
11146
+ ] }),
11147
+ /* @__PURE__ */ jsx("div", { className: "h-6 w-16 rounded-lg bg-gray-200 dark:bg-gray-700 animate-pulse" })
11148
+ ] });
11149
+ }
11150
+ function SessionMembersModal({ isOpen, sessionId, dataSource, onClose }) {
11151
+ const headingId = useId();
11152
+ const { t } = useTranslation();
11153
+ const statusTimerRef = useRef();
11154
+ const [loading, setLoading] = useState(false);
11155
+ const [saving, setSaving] = useState(false);
11156
+ const [error, setError] = useState(null);
11157
+ const [users, setUsers] = useState([]);
11158
+ const [members, setMembers] = useState([]);
11159
+ const [selectedUser, setSelectedUser] = useState(null);
11160
+ const [selectedRole, setSelectedRole] = useState("editor");
11161
+ const [query, setQuery] = useState("");
11162
+ const [confirmRemove, setConfirmRemove] = useState(null);
11163
+ const [statusMessage, setStatusMessage] = useState(null);
11164
+ const [dropdownOpen, setDropdownOpen] = useState(false);
11165
+ const [dropdownHighlightIndex, setDropdownHighlightIndex] = useState(0);
11166
+ const dropdownOptionRefs = useRef([]);
11167
+ const canManageMembers = Boolean(
11168
+ dataSource.listUsers && dataSource.listSessionMembers && dataSource.addSessionMember && dataSource.updateSessionMemberRole && dataSource.removeSessionMember
11169
+ );
11170
+ const refresh = useCallback(async () => {
11171
+ if (!sessionId || !canManageMembers) {
11172
+ return;
11173
+ }
11174
+ setLoading(true);
11175
+ setError(null);
11176
+ try {
11177
+ const [usersData, membersData] = await Promise.all([
11178
+ dataSource.listUsers(),
11179
+ dataSource.listSessionMembers(sessionId)
11180
+ ]);
11181
+ setUsers(usersData);
11182
+ setMembers(membersData);
11183
+ } catch {
11184
+ setError(t("BiChat.Share.LoadFailed"));
11185
+ } finally {
11186
+ setLoading(false);
11187
+ }
11188
+ }, [canManageMembers, dataSource, sessionId, t]);
11189
+ useEffect(() => {
11190
+ if (!isOpen) {
11191
+ return;
11192
+ }
11193
+ void refresh();
11194
+ }, [isOpen, refresh]);
11195
+ useEffect(() => {
11196
+ if (!isOpen) {
11197
+ setQuery("");
11198
+ setSelectedUser(null);
11199
+ setSelectedRole("editor");
11200
+ setError(null);
11201
+ setConfirmRemove(null);
11202
+ setStatusMessage(null);
11203
+ setDropdownOpen(false);
11204
+ setDropdownHighlightIndex(0);
11205
+ }
11206
+ }, [isOpen]);
11207
+ const memberIDs = useMemo(() => new Set(members.map((m) => m.user.id)), [members]);
11208
+ const availableUsers = useMemo(
11209
+ () => users.filter((user) => !memberIDs.has(user.id)),
11210
+ [users, memberIDs]
11211
+ );
11212
+ const filteredUsers = useMemo(() => {
11213
+ if (!query.trim()) {
11214
+ return availableUsers;
11215
+ }
11216
+ const q = query.toLowerCase();
11217
+ return availableUsers.filter(
11218
+ (u) => u.firstName.toLowerCase().includes(q) || u.lastName.toLowerCase().includes(q) || `${u.firstName} ${u.lastName}`.toLowerCase().includes(q)
11219
+ );
11220
+ }, [availableUsers, query]);
11221
+ useEffect(() => {
11222
+ setDropdownHighlightIndex(
11223
+ (i) => Math.min(Math.max(0, i), Math.max(0, filteredUsers.length - 1))
11224
+ );
11225
+ }, [filteredUsers.length]);
11226
+ useEffect(() => () => clearTimeout(statusTimerRef.current), []);
11227
+ const flashStatus = useCallback((msg) => {
11228
+ clearTimeout(statusTimerRef.current);
11229
+ setStatusMessage(msg);
11230
+ statusTimerRef.current = setTimeout(() => setStatusMessage(null), 3e3);
11231
+ }, []);
11232
+ const handleAdd = useCallback(async () => {
11233
+ if (!sessionId || !selectedUser || !dataSource.addSessionMember) {
11234
+ return;
11235
+ }
11236
+ setSaving(true);
11237
+ setError(null);
11238
+ try {
11239
+ await dataSource.addSessionMember(sessionId, selectedUser.id, selectedRole);
11240
+ setSelectedUser(null);
11241
+ setQuery("");
11242
+ flashStatus(t("BiChat.Share.MemberAdded"));
11243
+ await refresh();
11244
+ } catch {
11245
+ setError(t("BiChat.Share.AddFailed"));
11246
+ } finally {
11247
+ setSaving(false);
11248
+ }
11249
+ }, [sessionId, selectedUser, selectedRole, dataSource.addSessionMember, refresh, t, flashStatus]);
11250
+ const handleUpdateRole = useCallback(async (userId, role) => {
11251
+ if (!sessionId || !dataSource.updateSessionMemberRole) {
11252
+ return;
11253
+ }
11254
+ setSaving(true);
11255
+ setError(null);
11256
+ try {
11257
+ await dataSource.updateSessionMemberRole(sessionId, userId, role);
11258
+ await refresh();
11259
+ } catch {
11260
+ setError(t("BiChat.Share.UpdateFailed"));
11261
+ } finally {
11262
+ setSaving(false);
11263
+ }
11264
+ }, [sessionId, dataSource.updateSessionMemberRole, refresh]);
11265
+ const handleRemove = useCallback(async (userId) => {
11266
+ if (!sessionId || !dataSource.removeSessionMember) {
11267
+ return;
11268
+ }
11269
+ setSaving(true);
11270
+ setError(null);
11271
+ try {
11272
+ await dataSource.removeSessionMember(sessionId, userId);
11273
+ flashStatus(t("BiChat.Share.MemberRemoved"));
11274
+ await refresh();
11275
+ } catch {
11276
+ setError(t("BiChat.Share.RemoveFailed"));
11277
+ } finally {
11278
+ setSaving(false);
11279
+ }
11280
+ }, [sessionId, dataSource.removeSessionMember, refresh, flashStatus]);
11281
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
11282
+ /* @__PURE__ */ jsxs(InlineDialog, { open: isOpen, onClose, className: "relative z-40", children: [
11283
+ /* @__PURE__ */ jsx(InlineDialogBackdrop, { className: "fixed inset-0 bg-black/40 dark:bg-black/60 backdrop-blur-sm transition-opacity duration-200" }),
11284
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 flex items-center justify-center z-50 p-4", children: /* @__PURE__ */ jsxs(
11285
+ InlineDialogPanel,
11286
+ {
11287
+ "aria-labelledby": headingId,
11288
+ className: "bg-white dark:bg-gray-800 rounded-2xl shadow-xl dark:shadow-2xl dark:shadow-black/30 max-w-md w-full",
11289
+ children: [
11290
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 pt-5 pb-4", children: [
11291
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
11292
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center w-9 h-9 rounded-xl bg-primary-50 dark:bg-primary-950/40 border border-primary-200/60 dark:border-primary-800/40", children: /* @__PURE__ */ jsx(UsersThree, { size: 18, weight: "duotone", className: "text-primary-600 dark:text-primary-400" }) }),
11293
+ /* @__PURE__ */ jsx(InlineDialogTitle, { id: headingId, className: "text-base font-semibold text-gray-900 dark:text-gray-100", children: t("BiChat.Share.Title") })
11294
+ ] }),
11295
+ /* @__PURE__ */ jsx(
11296
+ "button",
11297
+ {
11298
+ type: "button",
11299
+ onClick: onClose,
11300
+ className: "rounded-lg p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 dark:hover:text-gray-300 dark:hover:bg-gray-700 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50",
11301
+ "aria-label": t("BiChat.Common.Close"),
11302
+ children: /* @__PURE__ */ jsx(X, { size: 18 })
11303
+ }
11304
+ )
11305
+ ] }),
11306
+ /* @__PURE__ */ jsxs("div", { className: "px-6 pb-5 space-y-4", children: [
11307
+ !canManageMembers && /* @__PURE__ */ jsx("div", { className: "rounded-xl border border-amber-200 bg-amber-50 px-3 py-2.5 text-xs text-amber-700 dark:border-amber-800/60 dark:bg-amber-900/20 dark:text-amber-300", children: t("BiChat.Share.Unsupported") }),
11308
+ error && /* @__PURE__ */ jsx("div", { className: "rounded-xl border border-red-200 bg-red-50 px-3 py-2.5 text-xs text-red-700 dark:border-red-800/60 dark:bg-red-900/20 dark:text-red-300", children: error }),
11309
+ /* @__PURE__ */ jsx("div", { "aria-live": "polite", "aria-atomic": "true", className: "sr-only", children: statusMessage }),
11310
+ /* @__PURE__ */ jsxs("div", { children: [
11311
+ /* @__PURE__ */ jsxs("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400", children: [
11312
+ t("BiChat.Share.Members"),
11313
+ !loading && members.length > 0 ? ` (${members.length})` : ""
11314
+ ] }),
11315
+ /* @__PURE__ */ jsx("div", { className: "max-h-64 overflow-y-auto -mx-1 px-1 space-y-1", children: loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
11316
+ /* @__PURE__ */ jsx(MemberSkeleton, {}),
11317
+ /* @__PURE__ */ jsx(MemberSkeleton, {}),
11318
+ /* @__PURE__ */ jsx(MemberSkeleton, {})
11319
+ ] }) : members.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-gray-400 dark:text-gray-500", children: [
11320
+ /* @__PURE__ */ jsx(UsersThree, { size: 32, weight: "thin", className: "mb-2" }),
11321
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: t("BiChat.Share.Empty") })
11322
+ ] }) : members.map((member) => /* @__PURE__ */ jsxs(
11323
+ "div",
11324
+ {
11325
+ className: "flex items-center gap-3 rounded-xl px-3 py-2 transition-colors hover:bg-gray-50 dark:hover:bg-gray-700/40",
11326
+ children: [
11327
+ /* @__PURE__ */ jsx(
11328
+ MemoizedUserAvatar,
11329
+ {
11330
+ firstName: member.user.firstName,
11331
+ lastName: member.user.lastName,
11332
+ initials: member.user.initials,
11333
+ size: "sm"
11334
+ }
11335
+ ),
11336
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxs("div", { className: "truncate text-sm font-medium text-gray-900 dark:text-gray-100", children: [
11337
+ member.user.firstName,
11338
+ " ",
11339
+ member.user.lastName
11340
+ ] }) }),
11341
+ member.role === "owner" ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-50 dark:bg-amber-900/20 border border-amber-200/60 dark:border-amber-800/40 px-2.5 py-0.5 text-[11px] font-medium text-amber-700 dark:text-amber-300", children: [
11342
+ /* @__PURE__ */ jsx(Crown, { size: 12, weight: "duotone" }),
11343
+ t("BiChat.Share.RoleOwner")
11344
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
11345
+ /* @__PURE__ */ jsx(
11346
+ RoleSegmentedControl,
11347
+ {
11348
+ value: member.role,
11349
+ onChange: (role) => handleUpdateRole(member.user.id, role),
11350
+ disabled: saving,
11351
+ size: "sm",
11352
+ t
11353
+ }
11354
+ ),
11355
+ /* @__PURE__ */ jsx(
11356
+ "button",
11357
+ {
11358
+ type: "button",
11359
+ disabled: saving,
11360
+ onClick: () => setConfirmRemove(member),
11361
+ className: "cursor-pointer rounded-lg p-1.5 text-gray-400 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500/50",
11362
+ "aria-label": `${t("BiChat.Share.Remove")} ${member.user.firstName} ${member.user.lastName}`,
11363
+ children: /* @__PURE__ */ jsx(Trash, { size: 14 })
11364
+ }
11365
+ )
11366
+ ] })
11367
+ ]
11368
+ },
11369
+ member.user.id
11370
+ )) })
11371
+ ] }),
11372
+ canManageMembers && /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-gray-200 dark:border-gray-700 p-3 space-y-3", children: [
11373
+ /* @__PURE__ */ jsx("h3", { className: "text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400", children: t("BiChat.Share.AddMember") }),
11374
+ /* @__PURE__ */ jsxs(
11375
+ "div",
11376
+ {
11377
+ className: "relative",
11378
+ onBlur: (e) => {
11379
+ if (!e.currentTarget.contains(e.relatedTarget)) {
11380
+ setDropdownOpen(false);
11381
+ }
11382
+ },
11383
+ children: [
11384
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
11385
+ /* @__PURE__ */ jsx(
11386
+ MagnifyingGlass,
11387
+ {
11388
+ size: 14,
11389
+ className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500"
11390
+ }
11391
+ ),
11392
+ /* @__PURE__ */ jsx(
11393
+ "input",
11394
+ {
11395
+ type: "text",
11396
+ className: "w-full rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800/60 pl-8 pr-3 py-2 text-sm text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 transition-colors focus:border-primary-400 dark:focus:border-primary-600 focus:outline-none focus:ring-2 focus:ring-primary-500/20",
11397
+ placeholder: t("BiChat.Share.SearchUsers"),
11398
+ value: selectedUser ? `${selectedUser.firstName} ${selectedUser.lastName}` : query,
11399
+ onFocus: () => {
11400
+ setDropdownOpen(true);
11401
+ setDropdownHighlightIndex(0);
11402
+ if (selectedUser) {
11403
+ setSelectedUser(null);
11404
+ setQuery("");
11405
+ }
11406
+ },
11407
+ onChange: (e) => {
11408
+ setQuery(e.target.value);
11409
+ setSelectedUser(null);
11410
+ setDropdownOpen(true);
11411
+ setDropdownHighlightIndex(0);
11412
+ },
11413
+ onKeyDown: (e) => {
11414
+ if (!dropdownOpen || filteredUsers.length === 0) {
11415
+ if (e.key === "Escape") {
11416
+ setDropdownOpen(false);
11417
+ }
11418
+ return;
11419
+ }
11420
+ if (e.key === "Escape") {
11421
+ e.preventDefault();
11422
+ setDropdownOpen(false);
11423
+ setDropdownHighlightIndex(0);
11424
+ return;
11425
+ }
11426
+ if (e.key === "ArrowDown") {
11427
+ e.preventDefault();
11428
+ const next = (dropdownHighlightIndex + 1) % filteredUsers.length;
11429
+ setDropdownHighlightIndex(next);
11430
+ setTimeout(() => dropdownOptionRefs.current[next]?.focus(), 0);
11431
+ return;
11432
+ }
11433
+ if (e.key === "ArrowUp") {
11434
+ e.preventDefault();
11435
+ const next = dropdownHighlightIndex <= 0 ? filteredUsers.length - 1 : dropdownHighlightIndex - 1;
11436
+ setDropdownHighlightIndex(next);
11437
+ setTimeout(() => dropdownOptionRefs.current[next]?.focus(), 0);
11438
+ return;
11439
+ }
11440
+ if (e.key === "Enter") {
11441
+ e.preventDefault();
11442
+ const user = filteredUsers[dropdownHighlightIndex];
11443
+ if (user) {
11444
+ setSelectedUser(user);
11445
+ setQuery("");
11446
+ setDropdownOpen(false);
11447
+ }
11448
+ }
11449
+ }
11450
+ }
11451
+ )
11452
+ ] }),
11453
+ dropdownOpen && !selectedUser && /* @__PURE__ */ jsx("div", { className: "absolute z-10 mt-1 max-h-48 w-full overflow-auto rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-lg py-1", children: filteredUsers.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-gray-500 dark:text-gray-400", children: availableUsers.length === 0 ? t("BiChat.Share.NoUsersAvailable") : t("BiChat.Share.NoSearchResults") }) : filteredUsers.map((user, index) => /* @__PURE__ */ jsxs(
11454
+ "button",
11455
+ {
11456
+ type: "button",
11457
+ ref: (el) => {
11458
+ dropdownOptionRefs.current[index] = el;
11459
+ },
11460
+ className: `flex w-full items-center gap-2.5 px-3 py-2 cursor-pointer transition-colors hover:bg-primary-50 dark:hover:bg-primary-900/20 ${index === dropdownHighlightIndex ? "bg-primary-50 dark:bg-primary-900/20" : ""}`,
11461
+ onMouseDown: (e) => e.preventDefault(),
11462
+ onClick: () => {
11463
+ setSelectedUser(user);
11464
+ setQuery("");
11465
+ setDropdownOpen(false);
11466
+ },
11467
+ children: [
11468
+ /* @__PURE__ */ jsx(
11469
+ MemoizedUserAvatar,
11470
+ {
11471
+ firstName: user.firstName,
11472
+ lastName: user.lastName,
11473
+ initials: user.initials,
11474
+ size: "xs"
11475
+ }
11476
+ ),
11477
+ /* @__PURE__ */ jsxs("span", { className: "text-sm text-gray-900 dark:text-gray-100", children: [
11478
+ user.firstName,
11479
+ " ",
11480
+ user.lastName
11481
+ ] })
11482
+ ]
11483
+ },
11484
+ user.id
11485
+ )) })
11486
+ ]
11487
+ }
11488
+ ),
11489
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
11490
+ /* @__PURE__ */ jsx(
11491
+ RoleSegmentedControl,
11492
+ {
11493
+ value: selectedRole,
11494
+ onChange: setSelectedRole,
11495
+ disabled: saving,
11496
+ t
11497
+ }
11498
+ ),
11499
+ /* @__PURE__ */ jsxs(
11500
+ "button",
11501
+ {
11502
+ type: "button",
11503
+ onClick: handleAdd,
11504
+ disabled: saving || !selectedUser,
11505
+ className: "inline-flex cursor-pointer items-center gap-1.5 rounded-xl bg-primary-600 px-3.5 py-2 text-sm font-medium text-white shadow-sm transition-all duration-150 hover:bg-primary-700 hover:shadow active:bg-primary-800 disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-800",
11506
+ children: [
11507
+ /* @__PURE__ */ jsx(UserPlus, { size: 14 }),
11508
+ t("BiChat.Share.Add")
11509
+ ]
11510
+ }
11511
+ )
11512
+ ] })
11513
+ ] })
11514
+ ] })
11515
+ ]
11516
+ }
11517
+ ) })
11518
+ ] }),
11519
+ /* @__PURE__ */ jsx(
11520
+ ConfirmModal,
11521
+ {
11522
+ isOpen: !!confirmRemove,
11523
+ isDanger: true,
11524
+ title: t("BiChat.Share.RemoveConfirmTitle"),
11525
+ message: confirmRemove ? t("BiChat.Share.RemoveConfirmMessage").replace(
11526
+ "{{name}}",
11527
+ `${confirmRemove.user.firstName} ${confirmRemove.user.lastName}`
11528
+ ) : "",
11529
+ confirmText: t("BiChat.Share.Remove"),
11530
+ onConfirm: () => {
11531
+ if (confirmRemove) {
11532
+ void handleRemove(confirmRemove.user.id);
11533
+ }
11534
+ setConfirmRemove(null);
11535
+ },
11536
+ onCancel: () => setConfirmRemove(null)
11537
+ }
11538
+ )
11539
+ ] });
11540
+ }
11541
+
11542
+ // ui/src/bichat/components/StreamError.tsx
11543
+ init_useTranslation();
11544
+ function StreamError({
11545
+ error,
11546
+ onRetry,
11547
+ onRegenerate,
11548
+ onDismiss,
11549
+ compact = false
11550
+ }) {
11551
+ const { t } = useTranslation();
11552
+ return /* @__PURE__ */ jsxs(
11553
+ motion.div,
11554
+ {
11555
+ initial: { opacity: 0, y: 10 },
11556
+ animate: { opacity: 1, y: 0 },
11557
+ exit: { opacity: 0, y: -10 },
11558
+ className: `flex items-start gap-3 ${compact ? "px-3 py-2.5" : "px-4 py-3"} bg-red-50 dark:bg-red-950/40 border border-red-200/80 dark:border-red-900/60 rounded-xl shadow-sm`,
11559
+ role: "alert",
11560
+ children: [
11561
+ /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 mt-0.5 flex items-center justify-center w-7 h-7 rounded-full bg-red-100 dark:bg-red-900/40", children: /* @__PURE__ */ jsx(
11562
+ Warning,
11563
+ {
11564
+ className: "w-4 h-4 text-red-600 dark:text-red-400",
11565
+ weight: "fill"
11566
+ }
11567
+ ) }),
11568
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
11569
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-red-800 dark:text-red-200 leading-snug", children: t("BiChat.Error.Generic") }),
11570
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-red-600/80 dark:text-red-400/70 break-words leading-relaxed", children: error }),
11571
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
11572
+ onRetry && /* @__PURE__ */ jsxs(
11573
+ "button",
11574
+ {
11575
+ onClick: onRetry,
11576
+ className: "cursor-pointer inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-white bg-red-600 hover:bg-red-700 active:bg-red-800 dark:bg-red-700 dark:hover:bg-red-600 rounded-lg transition-colors shadow-sm",
11577
+ type: "button",
11578
+ children: [
11579
+ /* @__PURE__ */ jsx(ArrowClockwise, { className: "w-3.5 h-3.5" }),
11580
+ t("BiChat.StreamError.Retry")
11581
+ ]
11582
+ }
11583
+ ),
11584
+ onRegenerate && /* @__PURE__ */ jsxs(
11585
+ "button",
11586
+ {
11587
+ onClick: onRegenerate,
11588
+ className: "cursor-pointer inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-lg transition-colors shadow-sm",
11589
+ type: "button",
11590
+ children: [
11591
+ /* @__PURE__ */ jsx(ArrowsCounterClockwise, { className: "w-3.5 h-3.5" }),
11592
+ t("BiChat.StreamError.Regenerate")
11593
+ ]
11594
+ }
11595
+ )
11596
+ ] })
11597
+ ] }),
11598
+ onDismiss && /* @__PURE__ */ jsx(
11599
+ "button",
11600
+ {
11601
+ onClick: onDismiss,
11602
+ className: "cursor-pointer flex-shrink-0 mt-0.5 inline-flex items-center justify-center w-6 h-6 text-red-400 dark:text-red-500 hover:text-red-600 dark:hover:text-red-300 hover:bg-red-100 dark:hover:bg-red-900/40 rounded-md transition-colors",
11603
+ type: "button",
11604
+ "aria-label": t("BiChat.Chat.DismissNotification"),
11605
+ children: /* @__PURE__ */ jsx(X, { className: "w-3.5 h-3.5" })
10800
11606
  }
10801
11607
  )
10802
11608
  ]
@@ -10860,7 +11666,8 @@ function ChatSessionCore({
10860
11666
  updateQueueItem
10861
11667
  } = useChatInput();
10862
11668
  const isArchived = session?.status === "archived";
10863
- const effectiveReadOnly = Boolean(readOnly ?? isReadOnly) || isArchived;
11669
+ const accessReadOnly = session?.access ? !session.access.canWrite : false;
11670
+ const effectiveReadOnly = Boolean(readOnly ?? isReadOnly) || isArchived || accessReadOnly;
10864
11671
  const [restoring, setRestoring] = useState(false);
10865
11672
  const handleRestore = useCallback(async () => {
10866
11673
  if (!session?.id) {
@@ -10881,6 +11688,8 @@ function ChatSessionCore({
10881
11688
  const [artifactsPanelExpanded, setArtifactsPanelExpanded] = useState(
10882
11689
  artifactsPanelDefaultExpanded
10883
11690
  );
11691
+ const [membersModalOpen, setMembersModalOpen] = useState(false);
11692
+ const [headerMembers, setHeaderMembers] = useState(null);
10884
11693
  const [artifactsPanelWidth, setArtifactsPanelWidth] = useState(ARTIFACTS_PANEL_WIDTH_DEFAULT);
10885
11694
  const [isResizingArtifactsPanel, setIsResizingArtifactsPanel] = useState(false);
10886
11695
  const layoutContainerRef = useRef(null);
@@ -10915,6 +11724,25 @@ function ChatSessionCore({
10915
11724
  } catch {
10916
11725
  }
10917
11726
  }, [artifactsPanelStorageKey, showArtifactsPanel]);
11727
+ useEffect(() => {
11728
+ if (!session?.id || !dataSource.listSessionMembers) {
11729
+ setHeaderMembers(null);
11730
+ return;
11731
+ }
11732
+ let cancelled = false;
11733
+ dataSource.listSessionMembers(session.id).then((members) => {
11734
+ if (!cancelled) {
11735
+ setHeaderMembers(members.map((m) => m.user));
11736
+ }
11737
+ }).catch(() => {
11738
+ if (!cancelled) {
11739
+ setHeaderMembers(null);
11740
+ }
11741
+ });
11742
+ return () => {
11743
+ cancelled = true;
11744
+ };
11745
+ }, [session?.id, dataSource.listSessionMembers]);
10918
11746
  const handleArtifactsResizeStart = useCallback(() => {
10919
11747
  setIsResizingArtifactsPanel(true);
10920
11748
  }, []);
@@ -11004,8 +11832,26 @@ function ChatSessionCore({
11004
11832
  }
11005
11833
  }
11006
11834
  };
11007
- const headerActions = showArtifactsControls ? /* @__PURE__ */ jsxs(Fragment, { children: [
11008
- /* @__PURE__ */ jsxs(
11835
+ const canShowShareButton = Boolean(
11836
+ session?.access?.canManageMembers && dataSource.listUsers && dataSource.listSessionMembers && dataSource.addSessionMember && dataSource.updateSessionMemberRole && dataSource.removeSessionMember
11837
+ );
11838
+ const shareButton = canShowShareButton ? /* @__PURE__ */ jsxs(
11839
+ "button",
11840
+ {
11841
+ type: "button",
11842
+ onClick: () => setMembersModalOpen(true),
11843
+ className: "inline-flex cursor-pointer items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium text-gray-500 transition-all duration-150 hover:bg-gray-100 hover:text-gray-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200",
11844
+ "aria-label": t("BiChat.Share.Title"),
11845
+ title: t("BiChat.Share.Title"),
11846
+ children: [
11847
+ /* @__PURE__ */ jsx(ShareNetwork, { className: "h-4 w-4" }),
11848
+ t("BiChat.Share.Button")
11849
+ ]
11850
+ }
11851
+ ) : null;
11852
+ const headerActions = /* @__PURE__ */ jsxs(Fragment, { children: [
11853
+ shareButton,
11854
+ showArtifactsControls && /* @__PURE__ */ jsxs(
11009
11855
  "button",
11010
11856
  {
11011
11857
  type: "button",
@@ -11025,7 +11871,7 @@ function ChatSessionCore({
11025
11871
  }
11026
11872
  ),
11027
11873
  actionsSlot
11028
- ] }) : actionsSlot;
11874
+ ] });
11029
11875
  return /* @__PURE__ */ jsxs(
11030
11876
  "main",
11031
11877
  {
@@ -11038,7 +11884,9 @@ function ChatSessionCore({
11038
11884
  onBack,
11039
11885
  readOnly: effectiveReadOnly,
11040
11886
  logoSlot,
11041
- actionsSlot: headerActions
11887
+ actionsSlot: headerActions,
11888
+ members: headerMembers ?? (session?.owner ? [session.owner] : void 0),
11889
+ onMembersClick: canShowShareButton ? () => setMembersModalOpen(true) : void 0
11042
11890
  }
11043
11891
  ),
11044
11892
  error && /* @__PURE__ */ jsx(
@@ -11235,6 +12083,15 @@ function ChatSessionCore({
11235
12083
  ) })
11236
12084
  ]
11237
12085
  }
12086
+ ),
12087
+ canShowShareButton && /* @__PURE__ */ jsx(
12088
+ SessionMembersModal,
12089
+ {
12090
+ isOpen: membersModalOpen,
12091
+ sessionId: session?.id,
12092
+ dataSource,
12093
+ onClose: () => setMembersModalOpen(false)
12094
+ }
11238
12095
  )
11239
12096
  ]
11240
12097
  }
@@ -11257,7 +12114,7 @@ function ChatSession(props) {
11257
12114
  // ui/src/bichat/index.ts
11258
12115
  init_MarkdownRenderer();
11259
12116
  init_ChartCard();
11260
- var sizeClasses = {
12117
+ var sizeClasses2 = {
11261
12118
  sm: {
11262
12119
  container: "py-6 px-3",
11263
12120
  title: "text-sm",
@@ -11282,7 +12139,7 @@ function EmptyState({
11282
12139
  className = "",
11283
12140
  size = "md"
11284
12141
  }) {
11285
- const sizes = sizeClasses[size];
12142
+ const sizes = sizeClasses2[size];
11286
12143
  const prefersReducedMotion2 = useReducedMotion();
11287
12144
  const duration = prefersReducedMotion2 ? 0 : 0.4;
11288
12145
  return /* @__PURE__ */ jsx(
@@ -11342,7 +12199,7 @@ MemoizedEmptyState.displayName = "EmptyState";
11342
12199
 
11343
12200
  // ui/src/bichat/components/EditableText.tsx
11344
12201
  init_useTranslation();
11345
- var sizeClasses2 = {
12202
+ var sizeClasses3 = {
11346
12203
  sm: "text-sm",
11347
12204
  md: "text-base",
11348
12205
  lg: "text-lg"
@@ -11413,7 +12270,7 @@ var EditableText = forwardRef(
11413
12270
  const handleBlur = () => {
11414
12271
  handleSave();
11415
12272
  };
11416
- const sizeClass = sizeClasses2[size];
12273
+ const sizeClass = sizeClasses3[size];
11417
12274
  if (isEditing) {
11418
12275
  return /* @__PURE__ */ jsx(
11419
12276
  "div",
@@ -11473,7 +12330,7 @@ var MemoizedEditableText = memo(EditableText);
11473
12330
 
11474
12331
  // ui/src/bichat/components/SearchInput.tsx
11475
12332
  init_useTranslation();
11476
- var sizeClasses3 = {
12333
+ var sizeClasses4 = {
11477
12334
  sm: {
11478
12335
  container: "py-1.5 pl-8 pr-8 text-xs",
11479
12336
  icon: 14,
@@ -11506,7 +12363,7 @@ function SearchInput({
11506
12363
  const resolvedPlaceholder = placeholder ?? t("BiChat.Common.Search");
11507
12364
  const resolvedAriaLabel = ariaLabel ?? t("BiChat.Common.Search");
11508
12365
  const inputRef = useRef(null);
11509
- const sizes = sizeClasses3[size];
12366
+ const sizes = sizeClasses4[size];
11510
12367
  useEffect(() => {
11511
12368
  if (autoFocus && inputRef.current) {
11512
12369
  inputRef.current.focus();
@@ -11933,134 +12790,6 @@ function ToastContainer({ toasts, onDismiss, dismissLabel }) {
11933
12790
  }
11934
12791
  );
11935
12792
  }
11936
-
11937
- // ui/src/bichat/components/ConfirmModal.tsx
11938
- init_useTranslation();
11939
- function ConfirmModalBase({
11940
- isOpen,
11941
- title,
11942
- message,
11943
- onConfirm,
11944
- onCancel,
11945
- confirmText,
11946
- cancelText,
11947
- isDanger = false
11948
- }) {
11949
- const { t } = useTranslation();
11950
- const resolvedConfirmText = confirmText?.trim() ? confirmText : t("BiChat.Common.Confirm");
11951
- const resolvedCancelText = cancelText?.trim() ? cancelText : t("BiChat.Common.Cancel");
11952
- return /* @__PURE__ */ jsxs(Dialog, { open: isOpen, onClose: onCancel, className: "relative z-40", children: [
11953
- /* @__PURE__ */ jsx(DialogBackdrop, { className: "fixed inset-0 bg-black/40 dark:bg-black/60 backdrop-blur-sm transition-opacity duration-200" }),
11954
- /* @__PURE__ */ jsx("div", { className: "fixed inset-0 flex items-center justify-center z-50 p-4", children: /* @__PURE__ */ jsxs(DialogPanel, { className: "bg-white dark:bg-gray-800 rounded-2xl shadow-xl dark:shadow-2xl dark:shadow-black/30 max-w-sm w-full overflow-hidden", children: [
11955
- /* @__PURE__ */ jsx("div", { className: "px-6 pt-6 pb-5", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3.5", children: [
11956
- isDanger && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 flex items-center justify-center w-10 h-10 rounded-xl bg-red-50 dark:bg-red-950/40 border border-red-200/60 dark:border-red-800/40", children: /* @__PURE__ */ jsx(WarningCircle, { size: 22, weight: "duotone", className: "text-red-600 dark:text-red-400" }) }),
11957
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
11958
- /* @__PURE__ */ jsx(DialogTitle, { className: "text-base font-semibold text-gray-900 dark:text-gray-100 leading-snug", children: title }),
11959
- /* @__PURE__ */ jsx(Description, { className: "mt-2 text-sm text-gray-600 dark:text-gray-400 leading-relaxed", children: message })
11960
- ] })
11961
- ] }) }),
11962
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2.5 px-6 pb-5", children: [
11963
- /* @__PURE__ */ jsx(
11964
- "button",
11965
- {
11966
- type: "button",
11967
- onClick: onCancel,
11968
- ...isDanger ? { "data-autofocus": true } : {},
11969
- className: "cursor-pointer px-4 py-2 text-sm font-medium rounded-xl text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700/60 hover:bg-gray-200 dark:hover:bg-gray-700 active:bg-gray-250 dark:active:bg-gray-600 transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-800",
11970
- "data-testid": "confirm-modal-cancel",
11971
- children: resolvedCancelText
11972
- }
11973
- ),
11974
- /* @__PURE__ */ jsx(
11975
- "button",
11976
- {
11977
- type: "button",
11978
- ...!isDanger ? { "data-autofocus": true } : {},
11979
- onClick: onConfirm,
11980
- className: [
11981
- "cursor-pointer px-4 py-2 text-sm font-medium rounded-xl text-white",
11982
- "transition-all duration-150 shadow-sm hover:shadow",
11983
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-800",
11984
- isDanger ? "bg-red-600 hover:bg-red-700 active:bg-red-800 focus-visible:ring-red-500/50" : "bg-primary-600 hover:bg-primary-700 active:bg-primary-800 focus-visible:ring-primary-500/50"
11985
- ].join(" "),
11986
- "data-testid": "confirm-modal-confirm",
11987
- children: resolvedConfirmText
11988
- }
11989
- )
11990
- ] })
11991
- ] }) })
11992
- ] });
11993
- }
11994
- var ConfirmModal = memo(ConfirmModalBase);
11995
- ConfirmModal.displayName = "ConfirmModal";
11996
- var ConfirmModal_default = ConfirmModal;
11997
- function hashString(str) {
11998
- let hash = 0;
11999
- for (let i = 0; i < str.length; i++) {
12000
- const char = str.charCodeAt(i);
12001
- hash = (hash << 5) - hash + char;
12002
- hash = hash & hash;
12003
- }
12004
- return Math.abs(hash);
12005
- }
12006
- var colorPalette = [
12007
- { bg: "bg-blue-500", text: "text-white" },
12008
- { bg: "bg-green-500", text: "text-white" },
12009
- { bg: "bg-purple-500", text: "text-white" },
12010
- { bg: "bg-pink-500", text: "text-white" },
12011
- { bg: "bg-indigo-500", text: "text-white" },
12012
- { bg: "bg-teal-500", text: "text-white" },
12013
- { bg: "bg-orange-500", text: "text-white" },
12014
- { bg: "bg-cyan-500", text: "text-white" },
12015
- { bg: "bg-amber-500", text: "text-white" },
12016
- { bg: "bg-lime-500", text: "text-white" }
12017
- ];
12018
- var sizeClasses4 = {
12019
- sm: "w-8 h-8 text-xs",
12020
- md: "w-10 h-10 text-sm",
12021
- lg: "w-12 h-12 text-base"
12022
- };
12023
- function UserAvatar({
12024
- firstName,
12025
- lastName,
12026
- initials: providedInitials,
12027
- size = "md",
12028
- className = ""
12029
- }) {
12030
- const derivedInitials = (() => {
12031
- const firstChar = firstName?.trim()?.charAt(0) || "";
12032
- const lastChar = lastName?.trim()?.charAt(0) || "";
12033
- const combined = `${firstChar}${lastChar}`.trim();
12034
- return combined || "U";
12035
- })();
12036
- const initials = (providedInitials?.trim() || derivedInitials).toUpperCase();
12037
- const fullName = `${firstName}${lastName}`;
12038
- const colorIndex = hashString(fullName) % colorPalette.length;
12039
- const colors = colorPalette[colorIndex];
12040
- return /* @__PURE__ */ jsx(
12041
- "div",
12042
- {
12043
- className: `
12044
- ${sizeClasses4[size]}
12045
- ${colors.bg}
12046
- ${colors.text}
12047
- ${className}
12048
- rounded-full
12049
- flex
12050
- items-center
12051
- justify-center
12052
- font-semibold
12053
- flex-shrink-0
12054
- select-none
12055
- `,
12056
- "aria-label": `${firstName} ${lastName}`,
12057
- title: `${firstName} ${lastName}`,
12058
- children: initials
12059
- }
12060
- );
12061
- }
12062
- var MemoizedUserAvatar = memo(UserAvatar);
12063
- MemoizedUserAvatar.displayName = "UserAvatar";
12064
12793
  function PermissionGuard({
12065
12794
  permissions,
12066
12795
  mode = "all",
@@ -12502,6 +13231,11 @@ var SessionItem = memo(
12502
13231
  const isTitleGenerating = !session.title?.trim();
12503
13232
  const displayTitle = isTitleGenerating ? t("BiChat.Common.Generating") : session.title ?? t("BiChat.Common.Untitled");
12504
13233
  const lastActivity = formatRelativeTime(session.updatedAt, t);
13234
+ const accessRole = session.access?.role ?? "owner";
13235
+ const roleLabel = accessRole === "editor" ? t("BiChat.Share.RoleEditor") : accessRole === "viewer" ? t("BiChat.Share.RoleViewer") : accessRole === "read_all" ? t("BiChat.Share.RoleReadOnly") : "";
13236
+ const visibilityLabel = session.isGroup ? t("BiChat.Sidebar.GroupChat") : accessRole === "editor" || accessRole === "viewer" ? t("BiChat.Sidebar.SharedWithYou") : "";
13237
+ const isGroupOrShared = Boolean(session.isGroup || session.memberCount && session.memberCount > 1);
13238
+ const metaParts = [lastActivity, visibilityLabel, roleLabel].filter(Boolean);
12505
13239
  const { handlers: longPressHandlers } = useLongPress({
12506
13240
  delay: 500,
12507
13241
  onLongPress: (e) => {
@@ -12643,17 +13377,23 @@ var SessionItem = memo(
12643
13377
  "data-testid": `${testIdPrefix}-session-${session.id}`,
12644
13378
  ...longPressHandlers,
12645
13379
  children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
12646
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [
12647
- /* @__PURE__ */ jsx(
12648
- MemoizedEditableText,
12649
- {
12650
- ref: editableTitleRef,
12651
- value: displayTitle,
12652
- onSave: (newTitle) => onRename?.(newTitle),
12653
- isLoading: isTitleGenerating
12654
- }
12655
- ),
12656
- /* @__PURE__ */ jsx("span", { className: "text-[11px] text-gray-400 dark:text-gray-500 truncate mt-0.5", children: lastActivity })
13380
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 min-w-0 flex-1", children: [
13381
+ isGroupOrShared && /* @__PURE__ */ jsx(UsersThree, { size: 14, weight: "duotone", className: "text-primary-500 dark:text-primary-400 mt-1 flex-shrink-0" }),
13382
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [
13383
+ /* @__PURE__ */ jsx(
13384
+ MemoizedEditableText,
13385
+ {
13386
+ ref: editableTitleRef,
13387
+ value: displayTitle,
13388
+ onSave: (newTitle) => onRename?.(newTitle),
13389
+ isLoading: isTitleGenerating
13390
+ }
13391
+ ),
13392
+ /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-gray-400 dark:text-gray-500 truncate mt-0.5", children: [
13393
+ metaParts.join(" \u2022 "),
13394
+ isGroupOrShared && session.memberCount && session.memberCount > 0 && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center ml-1 rounded-full bg-primary-50 dark:bg-primary-900/30 px-1.5 text-[10px] font-medium text-primary-600 dark:text-primary-400", children: session.memberCount })
13395
+ ] })
13396
+ ] })
12657
13397
  ] }),
12658
13398
  !isTouch && hasContextMenu && /* @__PURE__ */ jsxs(Menu, { children: [
12659
13399
  /* @__PURE__ */ jsx(
@@ -13007,24 +13747,23 @@ function AllChatsList({ dataSource, onSessionSelect, activeSessionId }) {
13007
13747
  setOffset((prev) => prev + limit);
13008
13748
  }
13009
13749
  }, [fetching, hasMore]);
13010
- const loadMoreRef = useCallback(
13011
- (node) => {
13012
- if (!node || fetching || !hasMore) {
13013
- return;
13750
+ const loadMoreNodeRef = useRef(null);
13751
+ const loadMoreRef = useCallback((node) => {
13752
+ loadMoreNodeRef.current = node;
13753
+ }, []);
13754
+ useEffect(() => {
13755
+ const node = loadMoreNodeRef.current;
13756
+ if (!node || fetching || !hasMore) {
13757
+ return;
13758
+ }
13759
+ const observer = new IntersectionObserver((entries) => {
13760
+ if (entries[0].isIntersecting) {
13761
+ handleLoadMore();
13014
13762
  }
13015
- const observer = new IntersectionObserver(
13016
- (entries) => {
13017
- if (entries[0].isIntersecting) {
13018
- handleLoadMore();
13019
- }
13020
- },
13021
- { threshold: 0.1 }
13022
- );
13023
- observer.observe(node);
13024
- return () => observer.disconnect();
13025
- },
13026
- [fetching, hasMore, handleLoadMore]
13027
- );
13763
+ }, { threshold: 0.1 });
13764
+ observer.observe(node);
13765
+ return () => observer.disconnect();
13766
+ }, [fetching, hasMore, handleLoadMore]);
13028
13767
  const derivedUsers = useMemo(() => {
13029
13768
  if (dataSource.listUsers) {
13030
13769
  return users;
@@ -13082,57 +13821,61 @@ function AllChatsList({ dataSource, onSessionSelect, activeSessionId }) {
13082
13821
  role: "list",
13083
13822
  "aria-label": t("BiChat.AllChats.OrganizationChatSessions"),
13084
13823
  children: [
13085
- chats.map((chat) => /* @__PURE__ */ jsx(
13086
- motion.div,
13087
- {
13088
- initial: { opacity: 0, y: -10 },
13089
- animate: { opacity: 1, y: 0 },
13090
- exit: { opacity: 0, y: -10 },
13091
- children: /* @__PURE__ */ jsx(
13092
- "div",
13093
- {
13094
- role: "link",
13095
- tabIndex: 0,
13096
- onClick: () => onSessionSelect(chat.id),
13097
- onKeyDown: (e) => {
13098
- if (e.key === "Enter" || e.key === " ") {
13099
- e.preventDefault();
13100
- onSessionSelect(chat.id);
13101
- }
13102
- },
13103
- className: `
13824
+ chats.map((chat) => {
13825
+ const owner = chat.owner ?? {
13826
+ firstName: "",
13827
+ lastName: "",
13828
+ initials: "U"
13829
+ };
13830
+ const ownerName = [owner.firstName, owner.lastName].filter(Boolean).join(" ");
13831
+ return /* @__PURE__ */ jsx(
13832
+ motion.div,
13833
+ {
13834
+ initial: { opacity: 0, y: -10 },
13835
+ animate: { opacity: 1, y: 0 },
13836
+ exit: { opacity: 0, y: -10 },
13837
+ children: /* @__PURE__ */ jsx(
13838
+ "div",
13839
+ {
13840
+ role: "link",
13841
+ tabIndex: 0,
13842
+ onClick: () => onSessionSelect(chat.id),
13843
+ onKeyDown: (e) => {
13844
+ if (e.key === "Enter" || e.key === " ") {
13845
+ e.preventDefault();
13846
+ onSessionSelect(chat.id);
13847
+ }
13848
+ },
13849
+ className: `
13104
13850
  block px-3 py-2 rounded-lg transition-smooth group cursor-pointer
13105
13851
  ${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"}
13106
13852
  `,
13107
- "aria-current": chat.id === activeSessionId ? "page" : void 0,
13108
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
13109
- /* @__PURE__ */ jsx(
13110
- MemoizedUserAvatar,
13111
- {
13112
- firstName: chat.owner.firstName,
13113
- lastName: chat.owner.lastName,
13114
- initials: chat.owner.initials,
13115
- size: "sm"
13116
- }
13117
- ),
13118
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
13119
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: chat.title || t("BiChat.Common.Untitled") }),
13120
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400 truncate", children: [
13121
- chat.owner.firstName,
13122
- " ",
13123
- chat.owner.lastName
13124
- ] }),
13125
- chat.status === "archived" && /* @__PURE__ */ 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: [
13126
- /* @__PURE__ */ jsx(Archive, { size: 12, className: "w-3 h-3" }),
13127
- t("BiChat.Chat.Archived")
13853
+ "aria-current": chat.id === activeSessionId ? "page" : void 0,
13854
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
13855
+ /* @__PURE__ */ jsx(
13856
+ MemoizedUserAvatar,
13857
+ {
13858
+ firstName: owner.firstName,
13859
+ lastName: owner.lastName,
13860
+ initials: owner.initials,
13861
+ size: "sm"
13862
+ }
13863
+ ),
13864
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
13865
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: chat.title || t("BiChat.Common.Untitled") }),
13866
+ ownerName && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 truncate", children: ownerName }),
13867
+ chat.status === "archived" && /* @__PURE__ */ 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: [
13868
+ /* @__PURE__ */ jsx(Archive, { size: 12, className: "w-3 h-3" }),
13869
+ t("BiChat.Chat.Archived")
13870
+ ] })
13128
13871
  ] })
13129
13872
  ] })
13130
- ] })
13131
- }
13132
- )
13133
- },
13134
- chat.id
13135
- )),
13873
+ }
13874
+ )
13875
+ },
13876
+ chat.id
13877
+ );
13878
+ }),
13136
13879
  hasMore && /* @__PURE__ */ jsx("div", { ref: loadMoreRef, className: "py-4 text-center", children: fetching ? /* @__PURE__ */ jsx(SessionSkeleton, { count: 2 }) : /* @__PURE__ */ jsx(
13137
13880
  "button",
13138
13881
  {
@@ -13902,19 +14645,22 @@ function Sidebar2({
13902
14645
  animate: "visible",
13903
14646
  role: "list",
13904
14647
  "aria-label": t("BiChat.Sidebar.PinnedChats"),
13905
- children: pinnedSessions.map((session) => /* @__PURE__ */ jsx(
13906
- SessionItem_default,
13907
- {
13908
- session,
13909
- isActive: session.id === activeSessionId,
13910
- onSelect: () => handleSessionSelect(session.id),
13911
- onArchive: () => handleSessionArchive(session.id),
13912
- onPin: () => handleSessionPin(session.id, session.pinned),
13913
- onRename: (newTitle) => handleSessionRename(session.id, newTitle),
13914
- onRegenerateTitle: () => handleSessionRegenerateTitle(session.id)
13915
- },
13916
- session.id
13917
- ))
14648
+ children: pinnedSessions.map((session) => {
14649
+ const canWrite = session.access?.canWrite ?? true;
14650
+ return /* @__PURE__ */ jsx(
14651
+ SessionItem_default,
14652
+ {
14653
+ session,
14654
+ isActive: session.id === activeSessionId,
14655
+ onSelect: () => handleSessionSelect(session.id),
14656
+ onArchive: canWrite ? () => handleSessionArchive(session.id) : void 0,
14657
+ onPin: canWrite ? () => handleSessionPin(session.id, session.pinned) : void 0,
14658
+ onRename: canWrite ? (newTitle) => handleSessionRename(session.id, newTitle) : void 0,
14659
+ onRegenerateTitle: canWrite ? () => handleSessionRegenerateTitle(session.id) : void 0
14660
+ },
14661
+ session.id
14662
+ );
14663
+ })
13918
14664
  }
13919
14665
  ),
13920
14666
  /* @__PURE__ */ jsx("div", { className: "border-b border-gray-200 dark:border-gray-700 my-3" })
@@ -13936,19 +14682,22 @@ function Sidebar2({
13936
14682
  animate: "visible",
13937
14683
  role: "list",
13938
14684
  "aria-label": `${group.name} chats`,
13939
- children: group.sessions.map((session) => /* @__PURE__ */ jsx(
13940
- SessionItem_default,
13941
- {
13942
- session,
13943
- isActive: session.id === activeSessionId,
13944
- onSelect: () => handleSessionSelect(session.id),
13945
- onArchive: () => handleSessionArchive(session.id),
13946
- onPin: () => handleSessionPin(session.id, session.pinned),
13947
- onRename: (newTitle) => handleSessionRename(session.id, newTitle),
13948
- onRegenerateTitle: () => handleSessionRegenerateTitle(session.id)
13949
- },
13950
- session.id
13951
- ))
14685
+ children: group.sessions.map((session) => {
14686
+ const canWrite = session.access?.canWrite ?? true;
14687
+ return /* @__PURE__ */ jsx(
14688
+ SessionItem_default,
14689
+ {
14690
+ session,
14691
+ isActive: session.id === activeSessionId,
14692
+ onSelect: () => handleSessionSelect(session.id),
14693
+ onArchive: canWrite ? () => handleSessionArchive(session.id) : void 0,
14694
+ onPin: canWrite ? () => handleSessionPin(session.id, session.pinned) : void 0,
14695
+ onRename: canWrite ? (newTitle) => handleSessionRename(session.id, newTitle) : void 0,
14696
+ onRegenerateTitle: canWrite ? () => handleSessionRegenerateTitle(session.id) : void 0
14697
+ },
14698
+ session.id
14699
+ );
14700
+ })
13952
14701
  }
13953
14702
  )
13954
14703
  ] }, group.name)),
@@ -16109,6 +16858,76 @@ function useScrollToBottom(items) {
16109
16858
  scrollToBottom
16110
16859
  };
16111
16860
  }
16861
+ function useHttpDataSourceConfigFromApplet(options) {
16862
+ return useMemo(() => {
16863
+ const ctx = typeof window !== "undefined" ? window.__APPLET_CONTEXT__ : void 0;
16864
+ if (!ctx) {
16865
+ throw new Error(
16866
+ "Applet context not found. Ensure window.__APPLET_CONTEXT__ is injected by the backend."
16867
+ );
16868
+ }
16869
+ const rpcEndpoint = ctx.config?.rpcUIEndpoint ?? "/rpc";
16870
+ const streamEndpoint = ctx.config?.streamEndpoint ?? "/stream";
16871
+ const csrfToken = ctx.session?.csrfToken ?? (typeof window !== "undefined" ? window.__CSRF_TOKEN__ : void 0) ?? "";
16872
+ const isDev = typeof import.meta.env?.DEV === "boolean" && import.meta.env?.DEV;
16873
+ if (!csrfToken && isDev) {
16874
+ console.warn(
16875
+ "[useHttpDataSourceConfigFromApplet] CSRF token is empty \u2014 requests may be rejected by the server."
16876
+ );
16877
+ }
16878
+ return {
16879
+ baseUrl: "",
16880
+ rpcEndpoint,
16881
+ streamEndpoint,
16882
+ csrfToken,
16883
+ timeout: options?.timeout ?? 12e4
16884
+ };
16885
+ }, [options?.timeout]);
16886
+ }
16887
+ var SESSION_PATH_REGEX = /\/session\/([^/]+)/;
16888
+ function useBichatRouter({
16889
+ navigate,
16890
+ pathname,
16891
+ onNavigate
16892
+ }) {
16893
+ const activeSessionId = useMemo(
16894
+ () => pathname.match(SESSION_PATH_REGEX)?.[1],
16895
+ [pathname]
16896
+ );
16897
+ const maybeClose = useCallback(() => {
16898
+ onNavigate?.();
16899
+ }, [onNavigate]);
16900
+ const onSessionSelect = useCallback(
16901
+ (sessionId) => {
16902
+ if (sessionId) {
16903
+ navigate(`/session/${sessionId}`);
16904
+ } else {
16905
+ navigate("/");
16906
+ }
16907
+ maybeClose();
16908
+ },
16909
+ [navigate, maybeClose]
16910
+ );
16911
+ const onNewChat = useCallback(() => {
16912
+ navigate("/");
16913
+ maybeClose();
16914
+ }, [navigate, maybeClose]);
16915
+ const onArchivedView = useCallback(() => {
16916
+ navigate("/archived");
16917
+ maybeClose();
16918
+ }, [navigate, maybeClose]);
16919
+ const onBack = useCallback(() => {
16920
+ navigate("/");
16921
+ maybeClose();
16922
+ }, [navigate, maybeClose]);
16923
+ return {
16924
+ activeSessionId,
16925
+ onSessionSelect,
16926
+ onNewChat,
16927
+ onArchivedView,
16928
+ onBack
16929
+ };
16930
+ }
16112
16931
 
16113
16932
  // ui/src/bichat/index.ts
16114
16933
  init_IotaContext();
@@ -16412,10 +17231,59 @@ function resolveArtifactName(artifact) {
16412
17231
  }
16413
17232
  return `${label} artifact`;
16414
17233
  }
17234
+ function mapSessionUser(rawUser) {
17235
+ if (!isRecord2(rawUser)) {
17236
+ return void 0;
17237
+ }
17238
+ const rawId = rawUser.id;
17239
+ const id = readNonEmptyString(rawId) ?? (typeof rawId === "number" && Number.isFinite(rawId) ? String(rawId) : null);
17240
+ if (!id) {
17241
+ return void 0;
17242
+ }
17243
+ const firstName = readString2(rawUser.firstName);
17244
+ const lastName = readString2(rawUser.lastName);
17245
+ const initials = readNonEmptyString(rawUser.initials) || `${firstName.charAt(0)}${lastName.charAt(0)}`.trim().toUpperCase() || "U";
17246
+ return {
17247
+ id,
17248
+ firstName,
17249
+ lastName,
17250
+ initials
17251
+ };
17252
+ }
17253
+ function mapSessionAccess(rawAccess) {
17254
+ if (!isRecord2(rawAccess)) {
17255
+ return void 0;
17256
+ }
17257
+ const role = readString2(rawAccess.role).toLowerCase();
17258
+ const source = readString2(rawAccess.source).toLowerCase();
17259
+ const normalizedRole = role === "owner" || role === "editor" || role === "viewer" || role === "read_all" ? role : "none";
17260
+ const normalizedSource = source === "owner" || source === "member" || source === "permission" ? source : "none";
17261
+ const canRead = rawAccess.canRead === true || rawAccess.canRead === "true";
17262
+ const canWrite = rawAccess.canWrite === true || rawAccess.canWrite === "true";
17263
+ const canManageMembers = rawAccess.canManageMembers === true || rawAccess.canManageMembers === "true";
17264
+ if (normalizedRole === "none" && normalizedSource === "none" && !canRead && !canWrite && !canManageMembers) {
17265
+ return void 0;
17266
+ }
17267
+ return {
17268
+ role: normalizedRole,
17269
+ source: normalizedSource,
17270
+ canRead,
17271
+ canWrite,
17272
+ canManageMembers
17273
+ };
17274
+ }
16415
17275
  function toSession(session) {
16416
17276
  return {
16417
- ...session,
16418
- status: session.status === "archived" ? "archived" : "active"
17277
+ id: readString2(session.id),
17278
+ title: readString2(session.title),
17279
+ status: session.status === "archived" ? "archived" : "active",
17280
+ pinned: Boolean(session.pinned),
17281
+ createdAt: readString2(session.createdAt),
17282
+ updatedAt: readString2(session.updatedAt),
17283
+ owner: mapSessionUser(session.owner),
17284
+ isGroup: Boolean(session.isGroup),
17285
+ memberCount: typeof session.memberCount === "number" ? session.memberCount : void 0,
17286
+ access: mapSessionAccess(session.access)
16419
17287
  };
16420
17288
  }
16421
17289
  function toSessionArtifact(artifact) {
@@ -16689,6 +17557,7 @@ function sanitizeConversationTurn(rawTurn, index, fallbackSessionID) {
16689
17557
  id: userTurnID,
16690
17558
  content: readString2(rawTurn.userTurn.content),
16691
17559
  attachments: sanitizeUserAttachments(rawTurn.userTurn.attachments, turnID),
17560
+ author: mapSessionUser(rawTurn.userTurn.author),
16692
17561
  createdAt: readString2(rawTurn.userTurn.createdAt, createdAt)
16693
17562
  },
16694
17563
  assistantTurn: sanitizeAssistantTurn(rawTurn.assistantTurn, createdAt, turnID),
@@ -17181,6 +18050,68 @@ async function clearSessionHistory(callRPC, sessionId) {
17181
18050
  async function compactSessionHistory(callRPC, sessionId) {
17182
18051
  return callRPC("bichat.session.compact", { id: sessionId });
17183
18052
  }
18053
+ async function listUsers(callRPC) {
18054
+ const data = await callRPC("bichat.user.list", {});
18055
+ return data.users.map((user) => ({
18056
+ id: String(user.id),
18057
+ firstName: user.firstName || "",
18058
+ lastName: user.lastName || "",
18059
+ initials: user.initials || ""
18060
+ }));
18061
+ }
18062
+ async function listAllSessions(callRPC, options) {
18063
+ const data = await callRPC("bichat.session.listAll", {
18064
+ limit: options?.limit ?? 50,
18065
+ offset: options?.offset ?? 0,
18066
+ includeArchived: options?.includeArchived ?? false,
18067
+ userId: options?.userId ?? null
18068
+ });
18069
+ return {
18070
+ sessions: data.sessions.map(toSession),
18071
+ total: typeof data.total === "number" ? data.total : data.sessions.length,
18072
+ hasMore: Boolean(data.hasMore)
18073
+ };
18074
+ }
18075
+ async function listSessionMembers(callRPC, sessionId) {
18076
+ const data = await callRPC("bichat.session.members.list", { sessionId });
18077
+ return data.members.map((member) => ({
18078
+ user: {
18079
+ id: String(member.user.id),
18080
+ firstName: member.user.firstName,
18081
+ lastName: member.user.lastName,
18082
+ initials: member.user.initials
18083
+ },
18084
+ role: (() => {
18085
+ const normalizedRole = (member.role || "").toLowerCase();
18086
+ if (normalizedRole === "owner") {
18087
+ return "owner";
18088
+ }
18089
+ if (normalizedRole === "editor") {
18090
+ return "editor";
18091
+ }
18092
+ return "viewer";
18093
+ })(),
18094
+ createdAt: member.createdAt,
18095
+ updatedAt: member.updatedAt
18096
+ }));
18097
+ }
18098
+ async function addSessionMember(callRPC, sessionId, userId, role) {
18099
+ await callRPC("bichat.session.members.add", {
18100
+ sessionId,
18101
+ userId,
18102
+ role: role.toUpperCase()
18103
+ });
18104
+ }
18105
+ async function updateSessionMemberRole(callRPC, sessionId, userId, role) {
18106
+ await callRPC("bichat.session.members.updateRole", {
18107
+ sessionId,
18108
+ userId,
18109
+ role: role.toUpperCase()
18110
+ });
18111
+ }
18112
+ async function removeSessionMember(callRPC, sessionId, userId) {
18113
+ await callRPC("bichat.session.members.remove", { sessionId, userId });
18114
+ }
17184
18115
 
17185
18116
  // ui/src/bichat/utils/sseParser.ts
17186
18117
  function* processDataLines(lines) {
@@ -17793,7 +18724,12 @@ async function uploadSessionArtifacts(callRPC, sessionId, files, uploadFileFn) {
17793
18724
  const data = await callRPC("bichat.session.uploadArtifacts", {
17794
18725
  sessionId,
17795
18726
  attachments: uploads.map((upload) => ({
17796
- uploadId: upload.id
18727
+ id: String(upload.id),
18728
+ filename: upload.name,
18729
+ uploadId: upload.id,
18730
+ mimeType: upload.mimetype || "application/octet-stream",
18731
+ sizeBytes: upload.size,
18732
+ url: upload.url
17797
18733
  }))
17798
18734
  });
17799
18735
  return {
@@ -17920,6 +18856,24 @@ var HttpDataSource = class {
17920
18856
  async compactSessionHistory(sessionId) {
17921
18857
  return compactSessionHistory(this.boundCallRPC, sessionId);
17922
18858
  }
18859
+ async listUsers() {
18860
+ return listUsers(this.boundCallRPC);
18861
+ }
18862
+ async listAllSessions(options) {
18863
+ return listAllSessions(this.boundCallRPC, options);
18864
+ }
18865
+ async listSessionMembers(sessionId) {
18866
+ return listSessionMembers(this.boundCallRPC, sessionId);
18867
+ }
18868
+ async addSessionMember(sessionId, userId, role) {
18869
+ return addSessionMember(this.boundCallRPC, sessionId, userId, role);
18870
+ }
18871
+ async updateSessionMemberRole(sessionId, userId, role) {
18872
+ return updateSessionMemberRole(this.boundCallRPC, sessionId, userId, role);
18873
+ }
18874
+ async removeSessionMember(sessionId, userId) {
18875
+ return removeSessionMember(this.boundCallRPC, sessionId, userId);
18876
+ }
17923
18877
  // -------------------------------------------------------------------------
17924
18878
  // Message transport (delegates to MessageTransport)
17925
18879
  // -------------------------------------------------------------------------
@@ -18042,6 +18996,6 @@ function createHttpDataSource(config) {
18042
18996
  return new HttpDataSource(config);
18043
18997
  }
18044
18998
 
18045
- export { ATTACHMENT_ACCEPT_ATTRIBUTE, ActionButton, ActivityTrace, Alert_default as Alert, AllChatsList, ArchiveBanner_default as ArchiveBanner, ArchivedChatList, AssistantMessage, AssistantTurnView, MemoizedAttachmentGrid as AttachmentGrid, AttachmentPreview_default as AttachmentPreview, AttachmentUpload_default as AttachmentUpload, Avatar, BiChatLayout, Bubble, CHART_VISUAL, ChartCard, ChatHeader, ChatMachine, ChatSession, ChatSessionProvider, MemoizedCodeBlock as CodeBlock, CodeOutputsPanel, CompactionDoodle, ConfigProvider, ConfirmModal, ConfirmationStep, DateGroupHeader, DebugPanel, DefaultErrorContent, DownloadCard, MemoizedEditableText as EditableText, MemoizedEmptyState as EmptyState, ErrorBoundary, HttpDataSource, ImageModal, InlineQuestionForm, InteractiveTableCard, IotaContextProvider, ListItemSkeleton, MemoizedLoadingSpinner as LoadingSpinner, MemoizedMarkdownRenderer as MarkdownRenderer, MessageActions, MessageInput, MessageList, MessageRole, PermissionGuard, QuestionForm, QuestionStep, RateLimiter, RetryActionArea, ScreenReaderAnnouncer, ScrollToBottomButton, MemoizedSearchInput as SearchInput, SessionArtifactList, SessionArtifactPreview, SessionArtifactsPanel, SessionItem_default as SessionItem, SessionSkeleton, Sidebar2 as Sidebar, MemoizedSkeleton as Skeleton, SkeletonAvatar, SkeletonCard, SkeletonGroup, SkeletonText, SkipLink, Slot, SourcesPanel, StreamError, StreamingCursor, SystemMessage, TabbedChartGroup, TabbedTableGroup, TableExportButton, TableWithExport, ThemeProvider, Toast, ToastContainer, TouchContextMenu, Turn, TurnBubble, MemoizedTypingIndicator as TypingIndicator, MemoizedUserAvatar as UserAvatar, MemoizedUserFilter as UserFilter, UserMessage, UserTurnView, WelcomeContent, addCSRFHeader, backdropVariants, buttonVariants, convertToBase64, createDataUrl, createHeadersWithCSRF, createHttpDataSource, darkTheme, dropdownVariants, errorMessageVariants, fadeInUpVariants, fadeInVariants, floatingButtonVariants, formatFileSize, getCSRFToken, getFileVisual, getToolLabel, getValidChildren, groupSessionsByDate, groupSteps, hasPermission, isImageMimeType, isPermissionDeniedError, lightTheme, listItemVariants, messageContainerVariants, messageVariants, parseBichatStream, parseBichatStreamEvents, parseSSEStream, scaleFadeVariants, sessionItemVariants, staggerContainerVariants, toErrorDisplay, typingDotVariants, useActionButtonContext, useAttachments, useAutoScroll, useAvatarContext, useBubbleContext, useChatInput, useChatMessaging, useChatSession, useConfig, useDataTable, useFocusTrap, useImageGallery, useIotaContext, useKeyboardShortcuts, useLongPress, useMarkdownCopy, useMessageActions, useModalLock, useOptionalChatMessaging, useRequiredConfig, useScrollToBottom, useSidebarState, useStreaming2 as useStreaming, useTheme, useToast, useTranslation, useTurnContext, validateAttachmentFile, validateFileCount, validateImageFile, verbTransitionVariants };
18999
+ export { ATTACHMENT_ACCEPT_ATTRIBUTE, ActionButton, ActivityTrace, Alert_default as Alert, AllChatsList, ArchiveBanner_default as ArchiveBanner, ArchivedChatList, AssistantMessage, AssistantTurnView, MemoizedAttachmentGrid as AttachmentGrid, AttachmentPreview_default as AttachmentPreview, AttachmentUpload_default as AttachmentUpload, Avatar, AvatarStack, BiChatLayout, Bubble, CHART_VISUAL, ChartCard, ChatHeader, ChatMachine, ChatSession, ChatSessionProvider, MemoizedCodeBlock as CodeBlock, CodeOutputsPanel, CompactionDoodle, ConfigProvider, ConfirmModal, ConfirmationStep, DateGroupHeader, DebugPanel, DefaultErrorContent, DownloadCard, MemoizedEditableText as EditableText, MemoizedEmptyState as EmptyState, ErrorBoundary, HttpDataSource, ImageModal, InlineQuestionForm, InteractiveTableCard, IotaContextProvider, ListItemSkeleton, MemoizedLoadingSpinner as LoadingSpinner, MemoizedMarkdownRenderer as MarkdownRenderer, MessageActions, MessageInput, MessageList, MessageRole, PermissionGuard, QuestionForm, QuestionStep, RateLimiter, RetryActionArea, ScreenReaderAnnouncer, ScrollToBottomButton, MemoizedSearchInput as SearchInput, SessionArtifactList, SessionArtifactPreview, SessionArtifactsPanel, SessionItem_default as SessionItem, SessionMembersModal, SessionSkeleton, Sidebar2 as Sidebar, MemoizedSkeleton as Skeleton, SkeletonAvatar, SkeletonCard, SkeletonGroup, SkeletonText, SkipLink, Slot, SourcesPanel, StreamError, StreamingCursor, SystemMessage, TabbedChartGroup, TabbedTableGroup, TableExportButton, TableWithExport, ThemeProvider, Toast, ToastContainer, TouchContextMenu, Turn, TurnBubble, MemoizedTypingIndicator as TypingIndicator, MemoizedUserAvatar as UserAvatar, MemoizedUserFilter as UserFilter, UserMessage, UserTurnView, WelcomeContent, addCSRFHeader, backdropVariants, buttonVariants, convertToBase64, createDataUrl, createHeadersWithCSRF, createHttpDataSource, darkTheme, dropdownVariants, errorMessageVariants, fadeInUpVariants, fadeInVariants, floatingButtonVariants, formatFileSize, getCSRFToken, getFileVisual, getToolLabel, getValidChildren, groupSessionsByDate, groupSteps, hasPermission, isImageMimeType, isPermissionDeniedError, lightTheme, listItemVariants, messageContainerVariants, messageVariants, parseBichatStream, parseBichatStreamEvents, parseSSEStream, scaleFadeVariants, sessionItemVariants, staggerContainerVariants, toErrorDisplay, typingDotVariants, useActionButtonContext, useAttachments, useAutoScroll, useAvatarContext, useBichatRouter, useBubbleContext, useChatInput, useChatMessaging, useChatSession, useConfig, useDataTable, useFocusTrap, useHttpDataSourceConfigFromApplet, useImageGallery, useIotaContext, useKeyboardShortcuts, useLongPress, useMarkdownCopy, useMessageActions, useModalLock, useOptionalChatMessaging, useRequiredConfig, useScrollToBottom, useSidebarState, useStreaming2 as useStreaming, useTheme, useToast, useTranslation, useTurnContext, validateAttachmentFile, validateFileCount, validateImageFile, verbTransitionVariants };
18046
19000
  //# sourceMappingURL=index.mjs.map
18047
19001
  //# sourceMappingURL=index.mjs.map