@iota-uz/sdk 0.4.22 → 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.
@@ -1880,7 +1880,7 @@ function pendingQuestionFromInterrupt(interrupt, fallbackTurnId) {
1880
1880
  options: Array.isArray(question.options) ? question.options.filter((option) => !!option && typeof option.id === "string").map((option) => ({
1881
1881
  id: option.id,
1882
1882
  label: typeof option.label === "string" ? option.label : "",
1883
- value: typeof option.label === "string" ? option.label : ""
1883
+ value: option.id
1884
1884
  })) : []
1885
1885
  })) : [];
1886
1886
  return {
@@ -3024,14 +3024,9 @@ var ChatMachine = class {
3024
3024
  if (!curSessionId || !curPendingQuestion) {
3025
3025
  return;
3026
3026
  }
3027
- const previousTurns = this.state.messaging.turns;
3028
3027
  this._updateMessaging({ loading: true });
3029
3028
  this._updateSession({ error: null, errorRetryable: false });
3030
3029
  const previousPendingQuestion = curPendingQuestion;
3031
- this._updateMessaging({
3032
- pendingQuestion: null,
3033
- turns: applyTurnLifecycleForPendingQuestion(previousTurns, null)
3034
- });
3035
3030
  try {
3036
3031
  const result = await this.dataSource.submitQuestionAnswers(
3037
3032
  curSessionId,
@@ -3042,60 +3037,28 @@ var ChatMachine = class {
3042
3037
  return;
3043
3038
  }
3044
3039
  if (result.success) {
3045
- if (curSessionId !== "new") {
3046
- try {
3047
- const fetchResult = await this.dataSource.fetchSession(curSessionId);
3048
- if (this.disposed) {
3049
- return;
3050
- }
3051
- if (fetchResult) {
3052
- this._updateSession({ session: fetchResult.session });
3053
- const hasMalformedRefresh = previousTurns.length > 0 && Array.isArray(fetchResult.turns) && fetchResult.turns.length === 0;
3054
- if (hasMalformedRefresh) {
3055
- console.warn("[ChatMachine] Preserving previous turns due to empty post-HITL refetch payload", {
3056
- sessionId: curSessionId,
3057
- previousTurnCount: previousTurns.length
3058
- });
3059
- this._updateSession({
3060
- error: "Failed to fully refresh session. Showing last known messages.",
3061
- errorRetryable: true
3062
- });
3063
- this._updateMessaging({
3064
- pendingQuestion: fetchResult.pendingQuestion || null,
3065
- turns: applyTurnLifecycleForPendingQuestion(
3066
- previousTurns,
3067
- fetchResult.pendingQuestion || null
3068
- )
3069
- });
3070
- } else {
3071
- this._setTurnsFromFetch(fetchResult.turns, fetchResult.pendingQuestion || null);
3072
- }
3073
- } else {
3074
- this._updateSession({ error: "Failed to load updated session", errorRetryable: true });
3075
- }
3076
- } catch (fetchErr) {
3077
- if (this.disposed) {
3078
- return;
3079
- }
3080
- const normalized = normalizeRPCError(fetchErr, "Failed to load updated session");
3081
- this._updateSession({ error: normalized.userMessage, errorRetryable: true });
3040
+ if (result.data) {
3041
+ this._updateSession({ session: result.data.session });
3042
+ this._setTurnsFromFetch(result.data.turns, result.data.pendingQuestion || null);
3043
+ } else if (curSessionId !== "new") {
3044
+ const fetchResult = await this.dataSource.fetchSession(curSessionId);
3045
+ if (this.disposed) {
3046
+ return;
3047
+ }
3048
+ if (fetchResult) {
3049
+ this._updateSession({ session: fetchResult.session });
3050
+ this._setTurnsFromFetch(fetchResult.turns, fetchResult.pendingQuestion || null);
3051
+ } else {
3052
+ this._updateSession({ error: "Failed to load updated session", errorRetryable: true });
3082
3053
  }
3083
3054
  }
3084
3055
  } else {
3085
- this._updateMessaging({
3086
- pendingQuestion: previousPendingQuestion,
3087
- turns: applyTurnLifecycleForPendingQuestion(previousTurns, previousPendingQuestion)
3088
- });
3089
3056
  this._updateSession({ error: result.error || "Failed to submit answers", errorRetryable: false });
3090
3057
  }
3091
3058
  } catch (err) {
3092
3059
  if (this.disposed) {
3093
3060
  return;
3094
3061
  }
3095
- this._updateMessaging({
3096
- pendingQuestion: previousPendingQuestion,
3097
- turns: applyTurnLifecycleForPendingQuestion(previousTurns, previousPendingQuestion)
3098
- });
3099
3062
  const normalized = normalizeRPCError(err, "Failed to submit answers");
3100
3063
  this._updateSession({ error: normalized.userMessage, errorRetryable: normalized.retryable });
3101
3064
  } finally {
@@ -3340,12 +3303,151 @@ function useBranding() {
3340
3303
  }, [context.extensions?.branding, t]);
3341
3304
  return branding;
3342
3305
  }
3343
- function ChatHeader({ session, onBack, readOnly, logoSlot, actionsSlot }) {
3306
+ function hashString(str) {
3307
+ let hash = 0;
3308
+ for (let i = 0; i < str.length; i++) {
3309
+ const char = str.charCodeAt(i);
3310
+ hash = (hash << 5) - hash + char;
3311
+ hash = hash & hash;
3312
+ }
3313
+ return Math.abs(hash);
3314
+ }
3315
+ var colorPalette = [
3316
+ { bg: "bg-blue-500", text: "text-white" },
3317
+ { bg: "bg-green-500", text: "text-white" },
3318
+ { bg: "bg-purple-500", text: "text-white" },
3319
+ { bg: "bg-pink-500", text: "text-white" },
3320
+ { bg: "bg-indigo-500", text: "text-white" },
3321
+ { bg: "bg-teal-500", text: "text-white" },
3322
+ { bg: "bg-orange-500", text: "text-white" },
3323
+ { bg: "bg-cyan-500", text: "text-white" },
3324
+ { bg: "bg-amber-500", text: "text-white" },
3325
+ { bg: "bg-lime-500", text: "text-white" }
3326
+ ];
3327
+ var sizeClasses = {
3328
+ xs: "w-6 h-6 text-[10px]",
3329
+ sm: "w-8 h-8 text-xs",
3330
+ md: "w-10 h-10 text-sm",
3331
+ lg: "w-12 h-12 text-base"
3332
+ };
3333
+ function UserAvatar({
3334
+ firstName,
3335
+ lastName,
3336
+ initials: providedInitials,
3337
+ size = "md",
3338
+ className = ""
3339
+ }) {
3340
+ const derivedInitials = (() => {
3341
+ const firstChar = firstName?.trim()?.charAt(0) || "";
3342
+ const lastChar = lastName?.trim()?.charAt(0) || "";
3343
+ const combined = `${firstChar}${lastChar}`.trim();
3344
+ return combined || "U";
3345
+ })();
3346
+ const initials = (providedInitials?.trim() || derivedInitials).toUpperCase();
3347
+ const fullName = `${firstName}${lastName}`;
3348
+ const colorIndex = hashString(fullName) % colorPalette.length;
3349
+ const colors = colorPalette[colorIndex];
3350
+ return /* @__PURE__ */ jsxRuntime.jsx(
3351
+ "div",
3352
+ {
3353
+ className: `
3354
+ ${sizeClasses[size]}
3355
+ ${colors.bg}
3356
+ ${colors.text}
3357
+ ${className}
3358
+ rounded-full
3359
+ flex
3360
+ items-center
3361
+ justify-center
3362
+ font-semibold
3363
+ flex-shrink-0
3364
+ select-none
3365
+ `,
3366
+ "aria-label": `${firstName} ${lastName}`,
3367
+ title: `${firstName} ${lastName}`,
3368
+ children: initials
3369
+ }
3370
+ );
3371
+ }
3372
+ var MemoizedUserAvatar = React.memo(UserAvatar);
3373
+ MemoizedUserAvatar.displayName = "UserAvatar";
3374
+ var overlapClasses = {
3375
+ xs: "-ml-1.5",
3376
+ sm: "-ml-2"
3377
+ };
3378
+ var badgeSizeClasses = {
3379
+ xs: "w-6 h-6 text-[10px]",
3380
+ sm: "w-8 h-8 text-xs"
3381
+ };
3382
+ function AvatarStackInner({
3383
+ users,
3384
+ max = 3,
3385
+ size = "sm",
3386
+ onClick,
3387
+ className = ""
3388
+ }) {
3389
+ const visible = users.slice(0, max);
3390
+ const overflow = users.length - max;
3391
+ const interactive = typeof onClick === "function";
3392
+ const overlap = overlapClasses[size];
3393
+ const badgeSize = badgeSizeClasses[size];
3394
+ const handleKeyDown = (e) => {
3395
+ if (interactive && (e.key === "Enter" || e.key === " ")) {
3396
+ e.preventDefault();
3397
+ onClick();
3398
+ }
3399
+ };
3400
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3401
+ "div",
3402
+ {
3403
+ className: `inline-flex items-center ${interactive ? "cursor-pointer transition-opacity hover:opacity-80" : ""} ${className}`,
3404
+ onClick: interactive ? onClick : void 0,
3405
+ onKeyDown: interactive ? handleKeyDown : void 0,
3406
+ role: interactive ? "button" : void 0,
3407
+ tabIndex: interactive ? 0 : void 0,
3408
+ "aria-label": interactive ? `${users.length} members` : void 0,
3409
+ children: [
3410
+ visible.map((user, i) => /* @__PURE__ */ jsxRuntime.jsx(
3411
+ "div",
3412
+ {
3413
+ className: `${i > 0 ? overlap : ""} ring-2 ring-white dark:ring-gray-900 rounded-full`,
3414
+ style: { zIndex: visible.length - i },
3415
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3416
+ MemoizedUserAvatar,
3417
+ {
3418
+ firstName: user.firstName,
3419
+ lastName: user.lastName,
3420
+ initials: user.initials,
3421
+ size
3422
+ }
3423
+ )
3424
+ },
3425
+ `${user.firstName}-${user.lastName}-${i}`
3426
+ )),
3427
+ overflow > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
3428
+ "div",
3429
+ {
3430
+ 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`,
3431
+ style: { zIndex: 0 },
3432
+ children: [
3433
+ "+",
3434
+ overflow
3435
+ ]
3436
+ }
3437
+ )
3438
+ ]
3439
+ }
3440
+ );
3441
+ }
3442
+ var AvatarStack = React.memo(AvatarStackInner);
3443
+ AvatarStack.displayName = "AvatarStack";
3444
+ function ChatHeader({ session, onBack, readOnly, logoSlot, actionsSlot, members, onMembersClick }) {
3344
3445
  const { t } = useTranslation();
3345
3446
  const branding = useBranding();
3346
3447
  const BackButton = onBack ? /* @__PURE__ */ jsxRuntime.jsx(
3347
3448
  "button",
3348
3449
  {
3450
+ type: "button",
3349
3451
  onClick: onBack,
3350
3452
  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",
3351
3453
  "aria-label": t("BiChat.Chat.GoBack"),
@@ -3364,23 +3466,42 @@ function ChatHeader({ session, onBack, readOnly, logoSlot, actionsSlot }) {
3364
3466
  ] }) });
3365
3467
  }
3366
3468
  const resolvedSessionTitle = session.title?.trim() || t("BiChat.Chat.NewChat");
3469
+ const isGroupSession = Boolean(session.isGroup || session.memberCount && session.memberCount > 1);
3470
+ const memberCount = session.memberCount ?? 0;
3471
+ const stackUsers = members && members.length > 0 ? members : [];
3367
3472
  return /* @__PURE__ */ jsxRuntime.jsx("header", { className: "bichat-header border-b border-gray-200 dark:border-gray-700 px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
3368
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
3473
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
3369
3474
  BackButton,
3370
3475
  Logo,
3371
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg font-semibold text-[var(--bichat-text)]", children: resolvedSessionTitle }),
3372
- session.pinned && /* @__PURE__ */ jsxRuntime.jsx(
3373
- "svg",
3374
- {
3375
- className: "w-4 h-4 text-[var(--bichat-primary)]",
3376
- fill: "currentColor",
3377
- viewBox: "0 0 20 20",
3378
- "aria-label": t("BiChat.Chat.Pinned"),
3379
- children: /* @__PURE__ */ jsxRuntime.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" })
3380
- }
3381
- )
3476
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
3477
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5", children: [
3478
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg font-semibold text-[var(--bichat-text)] truncate", children: resolvedSessionTitle }),
3479
+ session.pinned && /* @__PURE__ */ jsxRuntime.jsx(
3480
+ "svg",
3481
+ {
3482
+ className: "w-4 h-4 text-[var(--bichat-primary)] flex-shrink-0",
3483
+ fill: "currentColor",
3484
+ viewBox: "0 0 20 20",
3485
+ role: "img",
3486
+ "aria-label": t("BiChat.Chat.Pinned"),
3487
+ children: /* @__PURE__ */ jsxRuntime.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" })
3488
+ }
3489
+ ),
3490
+ isGroupSession && stackUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
3491
+ AvatarStack,
3492
+ {
3493
+ users: stackUsers,
3494
+ max: 3,
3495
+ size: "xs",
3496
+ onClick: onMembersClick,
3497
+ className: "flex-shrink-0"
3498
+ }
3499
+ )
3500
+ ] }),
3501
+ isGroupSession && memberCount > 0 && /* @__PURE__ */ jsxRuntime.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)) })
3502
+ ] })
3382
3503
  ] }),
3383
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
3504
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
3384
3505
  readOnly && /* @__PURE__ */ jsxRuntime.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") }),
3385
3506
  session.status === "archived" && /* @__PURE__ */ jsxRuntime.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") }),
3386
3507
  actionsSlot
@@ -4255,6 +4376,7 @@ function UserMessage({
4255
4376
  turn,
4256
4377
  turnId,
4257
4378
  initials = "U",
4379
+ authorName,
4258
4380
  slots,
4259
4381
  classNames: classNameOverrides,
4260
4382
  onCopy,
@@ -4462,6 +4584,16 @@ function UserMessage({
4462
4584
  };
4463
4585
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classes.root, children: [
4464
4586
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classes.wrapper, children: [
4587
+ authorName && /* @__PURE__ */ jsxRuntime.jsx(
4588
+ "span",
4589
+ {
4590
+ id: `${turn.id}-author`,
4591
+ role: "note",
4592
+ "aria-label": authorName,
4593
+ className: "mb-1 px-1 text-[11px] text-right text-gray-500 dark:text-gray-400",
4594
+ children: authorName
4595
+ }
4596
+ ),
4465
4597
  normalizedAttachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes.attachments, children: renderSlot(
4466
4598
  slots?.attachments,
4467
4599
  attachmentsSlotProps,
@@ -4473,20 +4605,28 @@ function UserMessage({
4473
4605
  }
4474
4606
  )
4475
4607
  ) }),
4476
- turn.content && /* @__PURE__ */ jsxRuntime.jsx("div", { ref: bubbleRef, className: classes.bubble, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes.content, children: isEditing ? /* @__PURE__ */ jsxRuntime.jsx(
4477
- EditForm,
4608
+ turn.content && /* @__PURE__ */ jsxRuntime.jsx(
4609
+ "div",
4478
4610
  {
4479
- draftContent,
4480
- onDraftChange: handleDraftChange,
4481
- onSave: handleEditSave,
4482
- onCancel: handleEditCancel,
4483
- onKeyDown: handleEditKeyDown,
4484
- textareaRef: editTextareaRef,
4485
- disabled: false,
4486
- originalContent: turn.content,
4487
- t
4611
+ ref: bubbleRef,
4612
+ className: classes.bubble,
4613
+ "aria-describedby": authorName ? `${turn.id}-author` : void 0,
4614
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes.content, children: isEditing ? /* @__PURE__ */ jsxRuntime.jsx(
4615
+ EditForm,
4616
+ {
4617
+ draftContent,
4618
+ onDraftChange: handleDraftChange,
4619
+ onSave: handleEditSave,
4620
+ onCancel: handleEditCancel,
4621
+ onKeyDown: handleEditKeyDown,
4622
+ textareaRef: editTextareaRef,
4623
+ disabled: false,
4624
+ originalContent: turn.content,
4625
+ t
4626
+ }
4627
+ ) : renderSlot(slots?.content, contentSlotProps, turn.content) })
4488
4628
  }
4489
- ) : renderSlot(slots?.content, contentSlotProps, turn.content) }) }),
4629
+ ),
4490
4630
  !hideActions && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${classes.actions} ${isCopied ? "opacity-100" : ""}`, children: renderSlot(
4491
4631
  slots?.actions,
4492
4632
  actionsSlotProps,
@@ -4535,19 +4675,25 @@ function UserTurnView({
4535
4675
  turn,
4536
4676
  slots,
4537
4677
  classNames,
4538
- initials = "U",
4678
+ initials,
4539
4679
  hideAvatar,
4540
4680
  hideActions,
4541
4681
  hideTimestamp,
4542
- allowEdit
4682
+ allowEdit,
4683
+ showAuthorName = false
4543
4684
  }) {
4544
4685
  const { handleEdit, handleCopy } = useChatMessaging();
4686
+ const author = turn.userTurn.author;
4687
+ const fullName = [author?.firstName || "", author?.lastName || ""].join(" ").trim();
4688
+ const authorName = showAuthorName && fullName.length > 0 ? fullName : void 0;
4689
+ const resolvedInitials = initials ?? author?.initials ?? "U";
4545
4690
  return /* @__PURE__ */ jsxRuntime.jsx(
4546
4691
  UserMessage,
4547
4692
  {
4548
4693
  turn: turn.userTurn,
4549
4694
  turnId: turn.id,
4550
- initials,
4695
+ initials: resolvedInitials,
4696
+ authorName,
4551
4697
  slots,
4552
4698
  classNames,
4553
4699
  onCopy: handleCopy,
@@ -6529,13 +6675,13 @@ function InlineQuestionForm({ pendingQuestion }) {
6529
6675
  const currentAnswer = answers[currentQuestion?.id];
6530
6676
  const currentOtherText = otherTexts[currentQuestion?.id] || "";
6531
6677
  const handleOptionChange = React.useCallback(
6532
- (optionLabel, checked) => {
6678
+ (optionID, checked) => {
6533
6679
  if (!currentQuestion) {
6534
6680
  return;
6535
6681
  }
6536
6682
  const questionId = currentQuestion.id;
6537
6683
  const existingAnswer = answers[questionId] || { options: [] };
6538
- const isOtherOption = optionLabel === "__other__";
6684
+ const isOtherOption = optionID === "__other__";
6539
6685
  const isMultiSelect2 = currentQuestion.type === "MULTIPLE_CHOICE";
6540
6686
  if (isOtherOption) {
6541
6687
  setAnswers({
@@ -6550,14 +6696,14 @@ function InlineQuestionForm({ pendingQuestion }) {
6550
6696
  let newOptions;
6551
6697
  if (isMultiSelect2) {
6552
6698
  if (!checked) {
6553
- newOptions = existingAnswer.options.filter((o) => o !== optionLabel);
6554
- } else if (existingAnswer.options.includes(optionLabel)) {
6699
+ newOptions = existingAnswer.options.filter((o) => o !== optionID);
6700
+ } else if (existingAnswer.options.includes(optionID)) {
6555
6701
  newOptions = existingAnswer.options;
6556
6702
  } else {
6557
- newOptions = [...existingAnswer.options, optionLabel];
6703
+ newOptions = [...existingAnswer.options, optionID];
6558
6704
  }
6559
6705
  } else {
6560
- newOptions = checked ? [optionLabel] : [];
6706
+ newOptions = checked ? [optionID] : [];
6561
6707
  }
6562
6708
  setAnswers({
6563
6709
  ...answers,
@@ -6648,7 +6794,7 @@ function InlineQuestionForm({ pendingQuestion }) {
6648
6794
  const options = (currentQuestion.options || []).filter((option) => Boolean(option && typeof option.label === "string")).map((option, index) => ({
6649
6795
  id: option.id || `${currentQuestion.id}-option-${index}`,
6650
6796
  label: option.label,
6651
- value: option.value || option.label
6797
+ value: option.value || option.id || `${currentQuestion.id}-option-${index}`
6652
6798
  }));
6653
6799
  const isOtherSelected = currentAnswer?.customText !== void 0;
6654
6800
  const canProceed = isCurrentAnswerValid();
@@ -6696,7 +6842,7 @@ function InlineQuestionForm({ pendingQuestion }) {
6696
6842
  ] }),
6697
6843
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 pb-2 space-y-1.5", children: [
6698
6844
  options.map((option) => {
6699
- const isSelected = currentAnswer?.options.includes(option.label) || false;
6845
+ const isSelected = currentAnswer?.options.includes(option.id) || false;
6700
6846
  return /* @__PURE__ */ jsxRuntime.jsxs(
6701
6847
  "label",
6702
6848
  {
@@ -6724,7 +6870,7 @@ function InlineQuestionForm({ pendingQuestion }) {
6724
6870
  name: `question-${currentQuestion.id}`,
6725
6871
  value: option.value,
6726
6872
  checked: isSelected,
6727
- onChange: (e) => handleOptionChange(option.label, e.target.checked),
6873
+ onChange: (e) => handleOptionChange(option.id, e.target.checked),
6728
6874
  className: "sr-only"
6729
6875
  }
6730
6876
  ),
@@ -8542,7 +8688,7 @@ function StreamingBubble({ content, normalizedContent }) {
8542
8688
  }
8543
8689
  function MessageList({ renderUserTurn, renderAssistantTurn, thinkingVerbs, readOnly }) {
8544
8690
  const { t } = useTranslation();
8545
- const { currentSessionId, fetching } = useChatSession();
8691
+ const { session, currentSessionId, fetching } = useChatSession();
8546
8692
  const {
8547
8693
  turns,
8548
8694
  streamingContent,
@@ -8557,6 +8703,7 @@ function MessageList({ renderUserTurn, renderAssistantTurn, thinkingVerbs, readO
8557
8703
  () => streamingContent ? normalizeStreamingMarkdown(streamingContent) : "",
8558
8704
  [streamingContent]
8559
8705
  );
8706
+ const showAuthorNames = Boolean(session?.isGroup);
8560
8707
  const showEphemeral = showActivityTrace || showTypingIndicator;
8561
8708
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-1 min-h-0", children: [
8562
8709
  /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "h-full overflow-y-auto overflow-x-hidden px-4 py-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto space-y-6", children: [
@@ -8566,6 +8713,10 @@ function MessageList({ renderUserTurn, renderAssistantTurn, thinkingVerbs, readO
8566
8713
  const prevDate = index > 0 ? new Date(turns[index - 1].createdAt) : null;
8567
8714
  const showDateSeparator = !!prevDate && !dateFns.isSameDay(turnDate, prevDate);
8568
8715
  const isLast = index === turns.length - 1;
8716
+ const userTurnProps = {
8717
+ allowEdit: readOnly ? false : isLast,
8718
+ showAuthorName: showAuthorNames
8719
+ };
8569
8720
  return /* @__PURE__ */ jsxRuntime.jsxs(React.Fragment, { children: [
8570
8721
  showDateSeparator && /* @__PURE__ */ jsxRuntime.jsx(DateSeparator, { date: turnDate }),
8571
8722
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -8575,7 +8726,7 @@ function MessageList({ renderUserTurn, renderAssistantTurn, thinkingVerbs, readO
8575
8726
  isLastTurn: isLast,
8576
8727
  renderUserTurn,
8577
8728
  renderAssistantTurn,
8578
- userTurnProps: readOnly ? { allowEdit: false } : { allowEdit: isLast },
8729
+ userTurnProps,
8579
8730
  assistantTurnProps: readOnly ? { allowRegenerate: false } : void 0
8580
8731
  }
8581
8732
  )
@@ -10781,47 +10932,665 @@ function SessionArtifactsPanel({
10781
10932
  }
10782
10933
  );
10783
10934
  }
10935
+ var DialogContext = React.createContext(null);
10936
+ var FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
10937
+ function InlineDialog({ open, onClose, className, children }) {
10938
+ const containerRef = React.useRef(null);
10939
+ const previousFocusRef = React.useRef(null);
10940
+ React.useEffect(() => {
10941
+ if (!open) {
10942
+ return;
10943
+ }
10944
+ previousFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
10945
+ const container = containerRef.current;
10946
+ if (!container) {
10947
+ return;
10948
+ }
10949
+ const getFocusable = () => Array.from(
10950
+ container.querySelectorAll(FOCUSABLE_SELECTOR)
10951
+ );
10952
+ const handler = (e) => {
10953
+ if (e.key === "Escape") {
10954
+ onClose();
10955
+ return;
10956
+ }
10957
+ if (e.key !== "Tab") {
10958
+ return;
10959
+ }
10960
+ const focusable = getFocusable();
10961
+ if (focusable.length === 0) {
10962
+ e.preventDefault();
10963
+ container.focus();
10964
+ return;
10965
+ }
10966
+ const first = focusable[0];
10967
+ const last = focusable[focusable.length - 1];
10968
+ if (e.shiftKey && document.activeElement === first) {
10969
+ e.preventDefault();
10970
+ last.focus();
10971
+ } else if (!e.shiftKey && document.activeElement === last) {
10972
+ e.preventDefault();
10973
+ first.focus();
10974
+ }
10975
+ };
10976
+ (getFocusable()[0] ?? container)?.focus();
10977
+ container.addEventListener("keydown", handler);
10978
+ return () => {
10979
+ container.removeEventListener("keydown", handler);
10980
+ previousFocusRef.current?.focus();
10981
+ };
10982
+ }, [open, onClose]);
10983
+ if (!open) {
10984
+ return null;
10985
+ }
10986
+ return /* @__PURE__ */ jsxRuntime.jsx(DialogContext.Provider, { value: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(
10987
+ "div",
10988
+ {
10989
+ ref: containerRef,
10990
+ className,
10991
+ onClick: onClose,
10992
+ tabIndex: -1,
10993
+ children
10994
+ }
10995
+ ) });
10996
+ }
10997
+ function InlineDialogBackdrop(props) {
10998
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", ...props });
10999
+ }
11000
+ function InlineDialogPanel({
11001
+ children,
11002
+ onClick,
11003
+ ...rest
11004
+ }) {
11005
+ const ref = React.useRef(null);
11006
+ React.useEffect(() => {
11007
+ if (!ref.current) {
11008
+ return;
11009
+ }
11010
+ const target = ref.current.querySelector("[data-autofocus]") ?? ref.current.querySelector(
11011
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
11012
+ );
11013
+ target?.focus();
11014
+ }, []);
11015
+ return /* @__PURE__ */ jsxRuntime.jsx(
11016
+ "div",
11017
+ {
11018
+ ref,
11019
+ role: "dialog",
11020
+ "aria-modal": "true",
11021
+ onClick: (e) => {
11022
+ e.stopPropagation();
11023
+ onClick?.(e);
11024
+ },
11025
+ ...rest,
11026
+ children
11027
+ }
11028
+ );
11029
+ }
11030
+ function InlineDialogTitle(props) {
11031
+ return /* @__PURE__ */ jsxRuntime.jsx("h2", { ...props });
11032
+ }
11033
+ function InlineDialogDescription(props) {
11034
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { ...props });
11035
+ }
10784
11036
 
10785
- // ui/src/bichat/components/StreamError.tsx
11037
+ // ui/src/bichat/components/SessionMembersModal.tsx
10786
11038
  init_useTranslation();
10787
- function StreamError({
10788
- error,
10789
- onRetry,
10790
- onRegenerate,
10791
- onDismiss,
10792
- compact = false
11039
+ init_useTranslation();
11040
+ function ConfirmModalBase({
11041
+ isOpen,
11042
+ title,
11043
+ message,
11044
+ onConfirm,
11045
+ onCancel,
11046
+ confirmText,
11047
+ cancelText,
11048
+ isDanger = false
10793
11049
  }) {
10794
11050
  const { t } = useTranslation();
10795
- return /* @__PURE__ */ jsxRuntime.jsxs(
10796
- framerMotion.motion.div,
10797
- {
10798
- initial: { opacity: 0, y: 10 },
10799
- animate: { opacity: 1, y: 0 },
10800
- exit: { opacity: 0, y: -10 },
10801
- 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`,
10802
- role: "alert",
10803
- children: [
10804
- /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(
10805
- react.Warning,
11051
+ const resolvedConfirmText = confirmText?.trim() ? confirmText : t("BiChat.Common.Confirm");
11052
+ const resolvedCancelText = cancelText?.trim() ? cancelText : t("BiChat.Common.Cancel");
11053
+ return /* @__PURE__ */ jsxRuntime.jsxs(InlineDialog, { open: isOpen, onClose: onCancel, className: "relative z-40", children: [
11054
+ /* @__PURE__ */ jsxRuntime.jsx(InlineDialogBackdrop, { className: "fixed inset-0 bg-black/40 dark:bg-black/60 backdrop-blur-sm transition-opacity duration-200" }),
11055
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 flex items-center justify-center z-50 p-4", children: /* @__PURE__ */ jsxRuntime.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: [
11056
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 pt-6 pb-5", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-4", children: [
11057
+ isDanger && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(react.WarningCircle, { size: 22, weight: "duotone", className: "text-red-600 dark:text-red-400" }) }),
11058
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
11059
+ /* @__PURE__ */ jsxRuntime.jsx(InlineDialogTitle, { className: "text-base font-semibold text-gray-900 dark:text-gray-100 leading-snug", children: title }),
11060
+ /* @__PURE__ */ jsxRuntime.jsx(InlineDialogDescription, { className: "mt-2 text-sm text-gray-600 dark:text-gray-400 leading-relaxed", children: message })
11061
+ ] })
11062
+ ] }) }),
11063
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-2.5 px-6 pb-5", children: [
11064
+ /* @__PURE__ */ jsxRuntime.jsx(
11065
+ "button",
10806
11066
  {
10807
- className: "w-4 h-4 text-red-600 dark:text-red-400",
10808
- weight: "fill"
11067
+ type: "button",
11068
+ onClick: onCancel,
11069
+ ...isDanger ? { "data-autofocus": true } : {},
11070
+ 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",
11071
+ "data-testid": "confirm-modal-cancel",
11072
+ children: resolvedCancelText
10809
11073
  }
10810
- ) }),
10811
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
10812
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-red-800 dark:text-red-200 leading-snug", children: t("BiChat.Error.Generic") }),
10813
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-xs text-red-600/80 dark:text-red-400/70 break-words leading-relaxed", children: error }),
10814
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
10815
- onRetry && /* @__PURE__ */ jsxRuntime.jsxs(
10816
- "button",
10817
- {
10818
- onClick: onRetry,
10819
- 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",
10820
- type: "button",
10821
- children: [
10822
- /* @__PURE__ */ jsxRuntime.jsx(react.ArrowClockwise, { className: "w-3.5 h-3.5" }),
10823
- t("BiChat.StreamError.Retry")
10824
- ]
11074
+ ),
11075
+ /* @__PURE__ */ jsxRuntime.jsx(
11076
+ "button",
11077
+ {
11078
+ type: "button",
11079
+ ...!isDanger ? { "data-autofocus": true } : {},
11080
+ onClick: onConfirm,
11081
+ className: [
11082
+ "cursor-pointer px-4 py-2 text-sm font-medium rounded-xl text-white",
11083
+ "transition-all duration-150 shadow-sm hover:shadow",
11084
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-800",
11085
+ 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"
11086
+ ].join(" "),
11087
+ "data-testid": "confirm-modal-confirm",
11088
+ children: resolvedConfirmText
11089
+ }
11090
+ )
11091
+ ] })
11092
+ ] }) })
11093
+ ] });
11094
+ }
11095
+ var ConfirmModal = React.memo(ConfirmModalBase);
11096
+ ConfirmModal.displayName = "ConfirmModal";
11097
+ var ConfirmModal_default = ConfirmModal;
11098
+ var ROLES = ["editor", "viewer"];
11099
+ function RoleSegmentedControl({
11100
+ value,
11101
+ onChange,
11102
+ disabled,
11103
+ size = "md",
11104
+ t
11105
+ }) {
11106
+ const btnBase = size === "sm" ? "px-2 py-0.5 text-[11px]" : "px-3 py-1 text-xs";
11107
+ const currentIndex = ROLES.indexOf(value);
11108
+ const handleKeyDown = (e) => {
11109
+ if (disabled) {
11110
+ return;
11111
+ }
11112
+ let nextIndex = null;
11113
+ if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
11114
+ e.preventDefault();
11115
+ nextIndex = currentIndex <= 0 ? ROLES.length - 1 : currentIndex - 1;
11116
+ } else if (e.key === "ArrowRight" || e.key === "ArrowDown") {
11117
+ e.preventDefault();
11118
+ nextIndex = currentIndex >= ROLES.length - 1 ? 0 : currentIndex + 1;
11119
+ }
11120
+ if (nextIndex !== null) {
11121
+ const nextRole = ROLES[nextIndex];
11122
+ onChange(nextRole);
11123
+ const target = e.currentTarget.querySelector(`[data-role="${nextRole}"]`);
11124
+ target?.focus();
11125
+ }
11126
+ };
11127
+ return /* @__PURE__ */ jsxRuntime.jsx(
11128
+ "div",
11129
+ {
11130
+ role: "radiogroup",
11131
+ "aria-label": t("BiChat.Share.RoleLabel"),
11132
+ className: "inline-flex rounded-lg border border-gray-200 dark:border-gray-700 p-0.5 bg-gray-50 dark:bg-gray-800/50",
11133
+ onKeyDown: handleKeyDown,
11134
+ children: ROLES.map((role) => /* @__PURE__ */ jsxRuntime.jsx(
11135
+ "button",
11136
+ {
11137
+ type: "button",
11138
+ role: "radio",
11139
+ "aria-checked": value === role,
11140
+ tabIndex: value === role ? 0 : -1,
11141
+ "data-role": role,
11142
+ disabled,
11143
+ onClick: () => onChange(role),
11144
+ 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"}`,
11145
+ children: role === "editor" ? t("BiChat.Share.RoleEditor") : t("BiChat.Share.RoleViewer")
11146
+ },
11147
+ role
11148
+ ))
11149
+ }
11150
+ );
11151
+ }
11152
+ function MemberSkeleton() {
11153
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 rounded-xl px-3 py-2.5", "aria-hidden": "true", children: [
11154
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 h-8 rounded-full bg-gray-200 dark:bg-gray-700 animate-pulse flex-shrink-0" }),
11155
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-1.5", children: [
11156
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-28 rounded bg-gray-200 dark:bg-gray-700 animate-pulse" }),
11157
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2.5 w-16 rounded bg-gray-100 dark:bg-gray-800 animate-pulse" })
11158
+ ] }),
11159
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-6 w-16 rounded-lg bg-gray-200 dark:bg-gray-700 animate-pulse" })
11160
+ ] });
11161
+ }
11162
+ function SessionMembersModal({ isOpen, sessionId, dataSource, onClose }) {
11163
+ const headingId = React.useId();
11164
+ const { t } = useTranslation();
11165
+ const statusTimerRef = React.useRef();
11166
+ const [loading, setLoading] = React.useState(false);
11167
+ const [saving, setSaving] = React.useState(false);
11168
+ const [error, setError] = React.useState(null);
11169
+ const [users, setUsers] = React.useState([]);
11170
+ const [members, setMembers] = React.useState([]);
11171
+ const [selectedUser, setSelectedUser] = React.useState(null);
11172
+ const [selectedRole, setSelectedRole] = React.useState("editor");
11173
+ const [query, setQuery] = React.useState("");
11174
+ const [confirmRemove, setConfirmRemove] = React.useState(null);
11175
+ const [statusMessage, setStatusMessage] = React.useState(null);
11176
+ const [dropdownOpen, setDropdownOpen] = React.useState(false);
11177
+ const [dropdownHighlightIndex, setDropdownHighlightIndex] = React.useState(0);
11178
+ const dropdownOptionRefs = React.useRef([]);
11179
+ const canManageMembers = Boolean(
11180
+ dataSource.listUsers && dataSource.listSessionMembers && dataSource.addSessionMember && dataSource.updateSessionMemberRole && dataSource.removeSessionMember
11181
+ );
11182
+ const refresh = React.useCallback(async () => {
11183
+ if (!sessionId || !canManageMembers) {
11184
+ return;
11185
+ }
11186
+ setLoading(true);
11187
+ setError(null);
11188
+ try {
11189
+ const [usersData, membersData] = await Promise.all([
11190
+ dataSource.listUsers(),
11191
+ dataSource.listSessionMembers(sessionId)
11192
+ ]);
11193
+ setUsers(usersData);
11194
+ setMembers(membersData);
11195
+ } catch {
11196
+ setError(t("BiChat.Share.LoadFailed"));
11197
+ } finally {
11198
+ setLoading(false);
11199
+ }
11200
+ }, [canManageMembers, dataSource, sessionId, t]);
11201
+ React.useEffect(() => {
11202
+ if (!isOpen) {
11203
+ return;
11204
+ }
11205
+ void refresh();
11206
+ }, [isOpen, refresh]);
11207
+ React.useEffect(() => {
11208
+ if (!isOpen) {
11209
+ setQuery("");
11210
+ setSelectedUser(null);
11211
+ setSelectedRole("editor");
11212
+ setError(null);
11213
+ setConfirmRemove(null);
11214
+ setStatusMessage(null);
11215
+ setDropdownOpen(false);
11216
+ setDropdownHighlightIndex(0);
11217
+ }
11218
+ }, [isOpen]);
11219
+ const memberIDs = React.useMemo(() => new Set(members.map((m) => m.user.id)), [members]);
11220
+ const availableUsers = React.useMemo(
11221
+ () => users.filter((user) => !memberIDs.has(user.id)),
11222
+ [users, memberIDs]
11223
+ );
11224
+ const filteredUsers = React.useMemo(() => {
11225
+ if (!query.trim()) {
11226
+ return availableUsers;
11227
+ }
11228
+ const q = query.toLowerCase();
11229
+ return availableUsers.filter(
11230
+ (u) => u.firstName.toLowerCase().includes(q) || u.lastName.toLowerCase().includes(q) || `${u.firstName} ${u.lastName}`.toLowerCase().includes(q)
11231
+ );
11232
+ }, [availableUsers, query]);
11233
+ React.useEffect(() => {
11234
+ setDropdownHighlightIndex(
11235
+ (i) => Math.min(Math.max(0, i), Math.max(0, filteredUsers.length - 1))
11236
+ );
11237
+ }, [filteredUsers.length]);
11238
+ React.useEffect(() => () => clearTimeout(statusTimerRef.current), []);
11239
+ const flashStatus = React.useCallback((msg) => {
11240
+ clearTimeout(statusTimerRef.current);
11241
+ setStatusMessage(msg);
11242
+ statusTimerRef.current = setTimeout(() => setStatusMessage(null), 3e3);
11243
+ }, []);
11244
+ const handleAdd = React.useCallback(async () => {
11245
+ if (!sessionId || !selectedUser || !dataSource.addSessionMember) {
11246
+ return;
11247
+ }
11248
+ setSaving(true);
11249
+ setError(null);
11250
+ try {
11251
+ await dataSource.addSessionMember(sessionId, selectedUser.id, selectedRole);
11252
+ setSelectedUser(null);
11253
+ setQuery("");
11254
+ flashStatus(t("BiChat.Share.MemberAdded"));
11255
+ await refresh();
11256
+ } catch {
11257
+ setError(t("BiChat.Share.AddFailed"));
11258
+ } finally {
11259
+ setSaving(false);
11260
+ }
11261
+ }, [sessionId, selectedUser, selectedRole, dataSource.addSessionMember, refresh, t, flashStatus]);
11262
+ const handleUpdateRole = React.useCallback(async (userId, role) => {
11263
+ if (!sessionId || !dataSource.updateSessionMemberRole) {
11264
+ return;
11265
+ }
11266
+ setSaving(true);
11267
+ setError(null);
11268
+ try {
11269
+ await dataSource.updateSessionMemberRole(sessionId, userId, role);
11270
+ await refresh();
11271
+ } catch {
11272
+ setError(t("BiChat.Share.UpdateFailed"));
11273
+ } finally {
11274
+ setSaving(false);
11275
+ }
11276
+ }, [sessionId, dataSource.updateSessionMemberRole, refresh]);
11277
+ const handleRemove = React.useCallback(async (userId) => {
11278
+ if (!sessionId || !dataSource.removeSessionMember) {
11279
+ return;
11280
+ }
11281
+ setSaving(true);
11282
+ setError(null);
11283
+ try {
11284
+ await dataSource.removeSessionMember(sessionId, userId);
11285
+ flashStatus(t("BiChat.Share.MemberRemoved"));
11286
+ await refresh();
11287
+ } catch {
11288
+ setError(t("BiChat.Share.RemoveFailed"));
11289
+ } finally {
11290
+ setSaving(false);
11291
+ }
11292
+ }, [sessionId, dataSource.removeSessionMember, refresh, flashStatus]);
11293
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11294
+ /* @__PURE__ */ jsxRuntime.jsxs(InlineDialog, { open: isOpen, onClose, className: "relative z-40", children: [
11295
+ /* @__PURE__ */ jsxRuntime.jsx(InlineDialogBackdrop, { className: "fixed inset-0 bg-black/40 dark:bg-black/60 backdrop-blur-sm transition-opacity duration-200" }),
11296
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 flex items-center justify-center z-50 p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(
11297
+ InlineDialogPanel,
11298
+ {
11299
+ "aria-labelledby": headingId,
11300
+ className: "bg-white dark:bg-gray-800 rounded-2xl shadow-xl dark:shadow-2xl dark:shadow-black/30 max-w-md w-full",
11301
+ children: [
11302
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 pt-5 pb-4", children: [
11303
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
11304
+ /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(react.UsersThree, { size: 18, weight: "duotone", className: "text-primary-600 dark:text-primary-400" }) }),
11305
+ /* @__PURE__ */ jsxRuntime.jsx(InlineDialogTitle, { id: headingId, className: "text-base font-semibold text-gray-900 dark:text-gray-100", children: t("BiChat.Share.Title") })
11306
+ ] }),
11307
+ /* @__PURE__ */ jsxRuntime.jsx(
11308
+ "button",
11309
+ {
11310
+ type: "button",
11311
+ onClick: onClose,
11312
+ 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",
11313
+ "aria-label": t("BiChat.Common.Close"),
11314
+ children: /* @__PURE__ */ jsxRuntime.jsx(react.X, { size: 18 })
11315
+ }
11316
+ )
11317
+ ] }),
11318
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 pb-5 space-y-4", children: [
11319
+ !canManageMembers && /* @__PURE__ */ jsxRuntime.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") }),
11320
+ error && /* @__PURE__ */ jsxRuntime.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 }),
11321
+ /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-live": "polite", "aria-atomic": "true", className: "sr-only", children: statusMessage }),
11322
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
11323
+ /* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400", children: [
11324
+ t("BiChat.Share.Members"),
11325
+ !loading && members.length > 0 ? ` (${members.length})` : ""
11326
+ ] }),
11327
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-64 overflow-y-auto -mx-1 px-1 space-y-1", children: loading ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11328
+ /* @__PURE__ */ jsxRuntime.jsx(MemberSkeleton, {}),
11329
+ /* @__PURE__ */ jsxRuntime.jsx(MemberSkeleton, {}),
11330
+ /* @__PURE__ */ jsxRuntime.jsx(MemberSkeleton, {})
11331
+ ] }) : members.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-gray-400 dark:text-gray-500", children: [
11332
+ /* @__PURE__ */ jsxRuntime.jsx(react.UsersThree, { size: 32, weight: "thin", className: "mb-2" }),
11333
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: t("BiChat.Share.Empty") })
11334
+ ] }) : members.map((member) => /* @__PURE__ */ jsxRuntime.jsxs(
11335
+ "div",
11336
+ {
11337
+ className: "flex items-center gap-3 rounded-xl px-3 py-2 transition-colors hover:bg-gray-50 dark:hover:bg-gray-700/40",
11338
+ children: [
11339
+ /* @__PURE__ */ jsxRuntime.jsx(
11340
+ MemoizedUserAvatar,
11341
+ {
11342
+ firstName: member.user.firstName,
11343
+ lastName: member.user.lastName,
11344
+ initials: member.user.initials,
11345
+ size: "sm"
11346
+ }
11347
+ ),
11348
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "truncate text-sm font-medium text-gray-900 dark:text-gray-100", children: [
11349
+ member.user.firstName,
11350
+ " ",
11351
+ member.user.lastName
11352
+ ] }) }),
11353
+ member.role === "owner" ? /* @__PURE__ */ jsxRuntime.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: [
11354
+ /* @__PURE__ */ jsxRuntime.jsx(react.Crown, { size: 12, weight: "duotone" }),
11355
+ t("BiChat.Share.RoleOwner")
11356
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
11357
+ /* @__PURE__ */ jsxRuntime.jsx(
11358
+ RoleSegmentedControl,
11359
+ {
11360
+ value: member.role,
11361
+ onChange: (role) => handleUpdateRole(member.user.id, role),
11362
+ disabled: saving,
11363
+ size: "sm",
11364
+ t
11365
+ }
11366
+ ),
11367
+ /* @__PURE__ */ jsxRuntime.jsx(
11368
+ "button",
11369
+ {
11370
+ type: "button",
11371
+ disabled: saving,
11372
+ onClick: () => setConfirmRemove(member),
11373
+ 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",
11374
+ "aria-label": `${t("BiChat.Share.Remove")} ${member.user.firstName} ${member.user.lastName}`,
11375
+ children: /* @__PURE__ */ jsxRuntime.jsx(react.Trash, { size: 14 })
11376
+ }
11377
+ )
11378
+ ] })
11379
+ ]
11380
+ },
11381
+ member.user.id
11382
+ )) })
11383
+ ] }),
11384
+ canManageMembers && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border border-gray-200 dark:border-gray-700 p-3 space-y-3", children: [
11385
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400", children: t("BiChat.Share.AddMember") }),
11386
+ /* @__PURE__ */ jsxRuntime.jsxs(
11387
+ "div",
11388
+ {
11389
+ className: "relative",
11390
+ onBlur: (e) => {
11391
+ if (!e.currentTarget.contains(e.relatedTarget)) {
11392
+ setDropdownOpen(false);
11393
+ }
11394
+ },
11395
+ children: [
11396
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
11397
+ /* @__PURE__ */ jsxRuntime.jsx(
11398
+ react.MagnifyingGlass,
11399
+ {
11400
+ size: 14,
11401
+ className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500"
11402
+ }
11403
+ ),
11404
+ /* @__PURE__ */ jsxRuntime.jsx(
11405
+ "input",
11406
+ {
11407
+ type: "text",
11408
+ 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",
11409
+ placeholder: t("BiChat.Share.SearchUsers"),
11410
+ value: selectedUser ? `${selectedUser.firstName} ${selectedUser.lastName}` : query,
11411
+ onFocus: () => {
11412
+ setDropdownOpen(true);
11413
+ setDropdownHighlightIndex(0);
11414
+ if (selectedUser) {
11415
+ setSelectedUser(null);
11416
+ setQuery("");
11417
+ }
11418
+ },
11419
+ onChange: (e) => {
11420
+ setQuery(e.target.value);
11421
+ setSelectedUser(null);
11422
+ setDropdownOpen(true);
11423
+ setDropdownHighlightIndex(0);
11424
+ },
11425
+ onKeyDown: (e) => {
11426
+ if (!dropdownOpen || filteredUsers.length === 0) {
11427
+ if (e.key === "Escape") {
11428
+ setDropdownOpen(false);
11429
+ }
11430
+ return;
11431
+ }
11432
+ if (e.key === "Escape") {
11433
+ e.preventDefault();
11434
+ setDropdownOpen(false);
11435
+ setDropdownHighlightIndex(0);
11436
+ return;
11437
+ }
11438
+ if (e.key === "ArrowDown") {
11439
+ e.preventDefault();
11440
+ const next = (dropdownHighlightIndex + 1) % filteredUsers.length;
11441
+ setDropdownHighlightIndex(next);
11442
+ setTimeout(() => dropdownOptionRefs.current[next]?.focus(), 0);
11443
+ return;
11444
+ }
11445
+ if (e.key === "ArrowUp") {
11446
+ e.preventDefault();
11447
+ const next = dropdownHighlightIndex <= 0 ? filteredUsers.length - 1 : dropdownHighlightIndex - 1;
11448
+ setDropdownHighlightIndex(next);
11449
+ setTimeout(() => dropdownOptionRefs.current[next]?.focus(), 0);
11450
+ return;
11451
+ }
11452
+ if (e.key === "Enter") {
11453
+ e.preventDefault();
11454
+ const user = filteredUsers[dropdownHighlightIndex];
11455
+ if (user) {
11456
+ setSelectedUser(user);
11457
+ setQuery("");
11458
+ setDropdownOpen(false);
11459
+ }
11460
+ }
11461
+ }
11462
+ }
11463
+ )
11464
+ ] }),
11465
+ dropdownOpen && !selectedUser && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.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__ */ jsxRuntime.jsxs(
11466
+ "button",
11467
+ {
11468
+ type: "button",
11469
+ ref: (el) => {
11470
+ dropdownOptionRefs.current[index] = el;
11471
+ },
11472
+ 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" : ""}`,
11473
+ onMouseDown: (e) => e.preventDefault(),
11474
+ onClick: () => {
11475
+ setSelectedUser(user);
11476
+ setQuery("");
11477
+ setDropdownOpen(false);
11478
+ },
11479
+ children: [
11480
+ /* @__PURE__ */ jsxRuntime.jsx(
11481
+ MemoizedUserAvatar,
11482
+ {
11483
+ firstName: user.firstName,
11484
+ lastName: user.lastName,
11485
+ initials: user.initials,
11486
+ size: "xs"
11487
+ }
11488
+ ),
11489
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-gray-900 dark:text-gray-100", children: [
11490
+ user.firstName,
11491
+ " ",
11492
+ user.lastName
11493
+ ] })
11494
+ ]
11495
+ },
11496
+ user.id
11497
+ )) })
11498
+ ]
11499
+ }
11500
+ ),
11501
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
11502
+ /* @__PURE__ */ jsxRuntime.jsx(
11503
+ RoleSegmentedControl,
11504
+ {
11505
+ value: selectedRole,
11506
+ onChange: setSelectedRole,
11507
+ disabled: saving,
11508
+ t
11509
+ }
11510
+ ),
11511
+ /* @__PURE__ */ jsxRuntime.jsxs(
11512
+ "button",
11513
+ {
11514
+ type: "button",
11515
+ onClick: handleAdd,
11516
+ disabled: saving || !selectedUser,
11517
+ 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",
11518
+ children: [
11519
+ /* @__PURE__ */ jsxRuntime.jsx(react.UserPlus, { size: 14 }),
11520
+ t("BiChat.Share.Add")
11521
+ ]
11522
+ }
11523
+ )
11524
+ ] })
11525
+ ] })
11526
+ ] })
11527
+ ]
11528
+ }
11529
+ ) })
11530
+ ] }),
11531
+ /* @__PURE__ */ jsxRuntime.jsx(
11532
+ ConfirmModal,
11533
+ {
11534
+ isOpen: !!confirmRemove,
11535
+ isDanger: true,
11536
+ title: t("BiChat.Share.RemoveConfirmTitle"),
11537
+ message: confirmRemove ? t("BiChat.Share.RemoveConfirmMessage").replace(
11538
+ "{{name}}",
11539
+ `${confirmRemove.user.firstName} ${confirmRemove.user.lastName}`
11540
+ ) : "",
11541
+ confirmText: t("BiChat.Share.Remove"),
11542
+ onConfirm: () => {
11543
+ if (confirmRemove) {
11544
+ void handleRemove(confirmRemove.user.id);
11545
+ }
11546
+ setConfirmRemove(null);
11547
+ },
11548
+ onCancel: () => setConfirmRemove(null)
11549
+ }
11550
+ )
11551
+ ] });
11552
+ }
11553
+
11554
+ // ui/src/bichat/components/StreamError.tsx
11555
+ init_useTranslation();
11556
+ function StreamError({
11557
+ error,
11558
+ onRetry,
11559
+ onRegenerate,
11560
+ onDismiss,
11561
+ compact = false
11562
+ }) {
11563
+ const { t } = useTranslation();
11564
+ return /* @__PURE__ */ jsxRuntime.jsxs(
11565
+ framerMotion.motion.div,
11566
+ {
11567
+ initial: { opacity: 0, y: 10 },
11568
+ animate: { opacity: 1, y: 0 },
11569
+ exit: { opacity: 0, y: -10 },
11570
+ 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`,
11571
+ role: "alert",
11572
+ children: [
11573
+ /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(
11574
+ react.Warning,
11575
+ {
11576
+ className: "w-4 h-4 text-red-600 dark:text-red-400",
11577
+ weight: "fill"
11578
+ }
11579
+ ) }),
11580
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
11581
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-red-800 dark:text-red-200 leading-snug", children: t("BiChat.Error.Generic") }),
11582
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-xs text-red-600/80 dark:text-red-400/70 break-words leading-relaxed", children: error }),
11583
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
11584
+ onRetry && /* @__PURE__ */ jsxRuntime.jsxs(
11585
+ "button",
11586
+ {
11587
+ onClick: onRetry,
11588
+ 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",
11589
+ type: "button",
11590
+ children: [
11591
+ /* @__PURE__ */ jsxRuntime.jsx(react.ArrowClockwise, { className: "w-3.5 h-3.5" }),
11592
+ t("BiChat.StreamError.Retry")
11593
+ ]
10825
11594
  }
10826
11595
  ),
10827
11596
  onRegenerate && /* @__PURE__ */ jsxRuntime.jsxs(
@@ -10909,7 +11678,8 @@ function ChatSessionCore({
10909
11678
  updateQueueItem
10910
11679
  } = useChatInput();
10911
11680
  const isArchived = session?.status === "archived";
10912
- const effectiveReadOnly = Boolean(readOnly ?? isReadOnly) || isArchived;
11681
+ const accessReadOnly = session?.access ? !session.access.canWrite : false;
11682
+ const effectiveReadOnly = Boolean(readOnly ?? isReadOnly) || isArchived || accessReadOnly;
10913
11683
  const [restoring, setRestoring] = React.useState(false);
10914
11684
  const handleRestore = React.useCallback(async () => {
10915
11685
  if (!session?.id) {
@@ -10930,6 +11700,8 @@ function ChatSessionCore({
10930
11700
  const [artifactsPanelExpanded, setArtifactsPanelExpanded] = React.useState(
10931
11701
  artifactsPanelDefaultExpanded
10932
11702
  );
11703
+ const [membersModalOpen, setMembersModalOpen] = React.useState(false);
11704
+ const [headerMembers, setHeaderMembers] = React.useState(null);
10933
11705
  const [artifactsPanelWidth, setArtifactsPanelWidth] = React.useState(ARTIFACTS_PANEL_WIDTH_DEFAULT);
10934
11706
  const [isResizingArtifactsPanel, setIsResizingArtifactsPanel] = React.useState(false);
10935
11707
  const layoutContainerRef = React.useRef(null);
@@ -10964,6 +11736,25 @@ function ChatSessionCore({
10964
11736
  } catch {
10965
11737
  }
10966
11738
  }, [artifactsPanelStorageKey, showArtifactsPanel]);
11739
+ React.useEffect(() => {
11740
+ if (!session?.id || !dataSource.listSessionMembers) {
11741
+ setHeaderMembers(null);
11742
+ return;
11743
+ }
11744
+ let cancelled = false;
11745
+ dataSource.listSessionMembers(session.id).then((members) => {
11746
+ if (!cancelled) {
11747
+ setHeaderMembers(members.map((m) => m.user));
11748
+ }
11749
+ }).catch(() => {
11750
+ if (!cancelled) {
11751
+ setHeaderMembers(null);
11752
+ }
11753
+ });
11754
+ return () => {
11755
+ cancelled = true;
11756
+ };
11757
+ }, [session?.id, dataSource.listSessionMembers]);
10967
11758
  const handleArtifactsResizeStart = React.useCallback(() => {
10968
11759
  setIsResizingArtifactsPanel(true);
10969
11760
  }, []);
@@ -11052,9 +11843,27 @@ function ChatSessionCore({
11052
11843
  window.dispatchEvent(new CustomEvent("bichat:artifacts-panel-expanded", { detail: { expanded: true } }));
11053
11844
  }
11054
11845
  }
11055
- };
11056
- const headerActions = showArtifactsControls ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11057
- /* @__PURE__ */ jsxRuntime.jsxs(
11846
+ };
11847
+ const canShowShareButton = Boolean(
11848
+ session?.access?.canManageMembers && dataSource.listUsers && dataSource.listSessionMembers && dataSource.addSessionMember && dataSource.updateSessionMemberRole && dataSource.removeSessionMember
11849
+ );
11850
+ const shareButton = canShowShareButton ? /* @__PURE__ */ jsxRuntime.jsxs(
11851
+ "button",
11852
+ {
11853
+ type: "button",
11854
+ onClick: () => setMembersModalOpen(true),
11855
+ 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",
11856
+ "aria-label": t("BiChat.Share.Title"),
11857
+ title: t("BiChat.Share.Title"),
11858
+ children: [
11859
+ /* @__PURE__ */ jsxRuntime.jsx(react.ShareNetwork, { className: "h-4 w-4" }),
11860
+ t("BiChat.Share.Button")
11861
+ ]
11862
+ }
11863
+ ) : null;
11864
+ const headerActions = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11865
+ shareButton,
11866
+ showArtifactsControls && /* @__PURE__ */ jsxRuntime.jsxs(
11058
11867
  "button",
11059
11868
  {
11060
11869
  type: "button",
@@ -11074,7 +11883,7 @@ function ChatSessionCore({
11074
11883
  }
11075
11884
  ),
11076
11885
  actionsSlot
11077
- ] }) : actionsSlot;
11886
+ ] });
11078
11887
  return /* @__PURE__ */ jsxRuntime.jsxs(
11079
11888
  "main",
11080
11889
  {
@@ -11087,7 +11896,9 @@ function ChatSessionCore({
11087
11896
  onBack,
11088
11897
  readOnly: effectiveReadOnly,
11089
11898
  logoSlot,
11090
- actionsSlot: headerActions
11899
+ actionsSlot: headerActions,
11900
+ members: headerMembers ?? (session?.owner ? [session.owner] : void 0),
11901
+ onMembersClick: canShowShareButton ? () => setMembersModalOpen(true) : void 0
11091
11902
  }
11092
11903
  ),
11093
11904
  error && /* @__PURE__ */ jsxRuntime.jsx(
@@ -11284,6 +12095,15 @@ function ChatSessionCore({
11284
12095
  ) })
11285
12096
  ]
11286
12097
  }
12098
+ ),
12099
+ canShowShareButton && /* @__PURE__ */ jsxRuntime.jsx(
12100
+ SessionMembersModal,
12101
+ {
12102
+ isOpen: membersModalOpen,
12103
+ sessionId: session?.id,
12104
+ dataSource,
12105
+ onClose: () => setMembersModalOpen(false)
12106
+ }
11287
12107
  )
11288
12108
  ]
11289
12109
  }
@@ -11306,7 +12126,7 @@ function ChatSession(props) {
11306
12126
  // ui/src/bichat/index.ts
11307
12127
  init_MarkdownRenderer();
11308
12128
  init_ChartCard();
11309
- var sizeClasses = {
12129
+ var sizeClasses2 = {
11310
12130
  sm: {
11311
12131
  container: "py-6 px-3",
11312
12132
  title: "text-sm",
@@ -11331,7 +12151,7 @@ function EmptyState({
11331
12151
  className = "",
11332
12152
  size = "md"
11333
12153
  }) {
11334
- const sizes = sizeClasses[size];
12154
+ const sizes = sizeClasses2[size];
11335
12155
  const prefersReducedMotion2 = framerMotion.useReducedMotion();
11336
12156
  const duration = prefersReducedMotion2 ? 0 : 0.4;
11337
12157
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -11391,7 +12211,7 @@ MemoizedEmptyState.displayName = "EmptyState";
11391
12211
 
11392
12212
  // ui/src/bichat/components/EditableText.tsx
11393
12213
  init_useTranslation();
11394
- var sizeClasses2 = {
12214
+ var sizeClasses3 = {
11395
12215
  sm: "text-sm",
11396
12216
  md: "text-base",
11397
12217
  lg: "text-lg"
@@ -11462,7 +12282,7 @@ var EditableText = React.forwardRef(
11462
12282
  const handleBlur = () => {
11463
12283
  handleSave();
11464
12284
  };
11465
- const sizeClass = sizeClasses2[size];
12285
+ const sizeClass = sizeClasses3[size];
11466
12286
  if (isEditing) {
11467
12287
  return /* @__PURE__ */ jsxRuntime.jsx(
11468
12288
  "div",
@@ -11522,7 +12342,7 @@ var MemoizedEditableText = React.memo(EditableText);
11522
12342
 
11523
12343
  // ui/src/bichat/components/SearchInput.tsx
11524
12344
  init_useTranslation();
11525
- var sizeClasses3 = {
12345
+ var sizeClasses4 = {
11526
12346
  sm: {
11527
12347
  container: "py-1.5 pl-8 pr-8 text-xs",
11528
12348
  icon: 14,
@@ -11555,7 +12375,7 @@ function SearchInput({
11555
12375
  const resolvedPlaceholder = placeholder ?? t("BiChat.Common.Search");
11556
12376
  const resolvedAriaLabel = ariaLabel ?? t("BiChat.Common.Search");
11557
12377
  const inputRef = React.useRef(null);
11558
- const sizes = sizeClasses3[size];
12378
+ const sizes = sizeClasses4[size];
11559
12379
  React.useEffect(() => {
11560
12380
  if (autoFocus && inputRef.current) {
11561
12381
  inputRef.current.focus();
@@ -11982,134 +12802,6 @@ function ToastContainer({ toasts, onDismiss, dismissLabel }) {
11982
12802
  }
11983
12803
  );
11984
12804
  }
11985
-
11986
- // ui/src/bichat/components/ConfirmModal.tsx
11987
- init_useTranslation();
11988
- function ConfirmModalBase({
11989
- isOpen,
11990
- title,
11991
- message,
11992
- onConfirm,
11993
- onCancel,
11994
- confirmText,
11995
- cancelText,
11996
- isDanger = false
11997
- }) {
11998
- const { t } = useTranslation();
11999
- const resolvedConfirmText = confirmText?.trim() ? confirmText : t("BiChat.Common.Confirm");
12000
- const resolvedCancelText = cancelText?.trim() ? cancelText : t("BiChat.Common.Cancel");
12001
- return /* @__PURE__ */ jsxRuntime.jsxs(react$1.Dialog, { open: isOpen, onClose: onCancel, className: "relative z-40", children: [
12002
- /* @__PURE__ */ jsxRuntime.jsx(react$1.DialogBackdrop, { className: "fixed inset-0 bg-black/40 dark:bg-black/60 backdrop-blur-sm transition-opacity duration-200" }),
12003
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 flex items-center justify-center z-50 p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(react$1.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: [
12004
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 pt-6 pb-5", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3.5", children: [
12005
- isDanger && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(react.WarningCircle, { size: 22, weight: "duotone", className: "text-red-600 dark:text-red-400" }) }),
12006
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
12007
- /* @__PURE__ */ jsxRuntime.jsx(react$1.DialogTitle, { className: "text-base font-semibold text-gray-900 dark:text-gray-100 leading-snug", children: title }),
12008
- /* @__PURE__ */ jsxRuntime.jsx(react$1.Description, { className: "mt-2 text-sm text-gray-600 dark:text-gray-400 leading-relaxed", children: message })
12009
- ] })
12010
- ] }) }),
12011
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-2.5 px-6 pb-5", children: [
12012
- /* @__PURE__ */ jsxRuntime.jsx(
12013
- "button",
12014
- {
12015
- type: "button",
12016
- onClick: onCancel,
12017
- ...isDanger ? { "data-autofocus": true } : {},
12018
- 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",
12019
- "data-testid": "confirm-modal-cancel",
12020
- children: resolvedCancelText
12021
- }
12022
- ),
12023
- /* @__PURE__ */ jsxRuntime.jsx(
12024
- "button",
12025
- {
12026
- type: "button",
12027
- ...!isDanger ? { "data-autofocus": true } : {},
12028
- onClick: onConfirm,
12029
- className: [
12030
- "cursor-pointer px-4 py-2 text-sm font-medium rounded-xl text-white",
12031
- "transition-all duration-150 shadow-sm hover:shadow",
12032
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-800",
12033
- 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"
12034
- ].join(" "),
12035
- "data-testid": "confirm-modal-confirm",
12036
- children: resolvedConfirmText
12037
- }
12038
- )
12039
- ] })
12040
- ] }) })
12041
- ] });
12042
- }
12043
- var ConfirmModal = React.memo(ConfirmModalBase);
12044
- ConfirmModal.displayName = "ConfirmModal";
12045
- var ConfirmModal_default = ConfirmModal;
12046
- function hashString(str) {
12047
- let hash = 0;
12048
- for (let i = 0; i < str.length; i++) {
12049
- const char = str.charCodeAt(i);
12050
- hash = (hash << 5) - hash + char;
12051
- hash = hash & hash;
12052
- }
12053
- return Math.abs(hash);
12054
- }
12055
- var colorPalette = [
12056
- { bg: "bg-blue-500", text: "text-white" },
12057
- { bg: "bg-green-500", text: "text-white" },
12058
- { bg: "bg-purple-500", text: "text-white" },
12059
- { bg: "bg-pink-500", text: "text-white" },
12060
- { bg: "bg-indigo-500", text: "text-white" },
12061
- { bg: "bg-teal-500", text: "text-white" },
12062
- { bg: "bg-orange-500", text: "text-white" },
12063
- { bg: "bg-cyan-500", text: "text-white" },
12064
- { bg: "bg-amber-500", text: "text-white" },
12065
- { bg: "bg-lime-500", text: "text-white" }
12066
- ];
12067
- var sizeClasses4 = {
12068
- sm: "w-8 h-8 text-xs",
12069
- md: "w-10 h-10 text-sm",
12070
- lg: "w-12 h-12 text-base"
12071
- };
12072
- function UserAvatar({
12073
- firstName,
12074
- lastName,
12075
- initials: providedInitials,
12076
- size = "md",
12077
- className = ""
12078
- }) {
12079
- const derivedInitials = (() => {
12080
- const firstChar = firstName?.trim()?.charAt(0) || "";
12081
- const lastChar = lastName?.trim()?.charAt(0) || "";
12082
- const combined = `${firstChar}${lastChar}`.trim();
12083
- return combined || "U";
12084
- })();
12085
- const initials = (providedInitials?.trim() || derivedInitials).toUpperCase();
12086
- const fullName = `${firstName}${lastName}`;
12087
- const colorIndex = hashString(fullName) % colorPalette.length;
12088
- const colors = colorPalette[colorIndex];
12089
- return /* @__PURE__ */ jsxRuntime.jsx(
12090
- "div",
12091
- {
12092
- className: `
12093
- ${sizeClasses4[size]}
12094
- ${colors.bg}
12095
- ${colors.text}
12096
- ${className}
12097
- rounded-full
12098
- flex
12099
- items-center
12100
- justify-center
12101
- font-semibold
12102
- flex-shrink-0
12103
- select-none
12104
- `,
12105
- "aria-label": `${firstName} ${lastName}`,
12106
- title: `${firstName} ${lastName}`,
12107
- children: initials
12108
- }
12109
- );
12110
- }
12111
- var MemoizedUserAvatar = React.memo(UserAvatar);
12112
- MemoizedUserAvatar.displayName = "UserAvatar";
12113
12805
  function PermissionGuard({
12114
12806
  permissions,
12115
12807
  mode = "all",
@@ -12551,6 +13243,11 @@ var SessionItem = React.memo(
12551
13243
  const isTitleGenerating = !session.title?.trim();
12552
13244
  const displayTitle = isTitleGenerating ? t("BiChat.Common.Generating") : session.title ?? t("BiChat.Common.Untitled");
12553
13245
  const lastActivity = formatRelativeTime(session.updatedAt, t);
13246
+ const accessRole = session.access?.role ?? "owner";
13247
+ const roleLabel = accessRole === "editor" ? t("BiChat.Share.RoleEditor") : accessRole === "viewer" ? t("BiChat.Share.RoleViewer") : accessRole === "read_all" ? t("BiChat.Share.RoleReadOnly") : "";
13248
+ const visibilityLabel = session.isGroup ? t("BiChat.Sidebar.GroupChat") : accessRole === "editor" || accessRole === "viewer" ? t("BiChat.Sidebar.SharedWithYou") : "";
13249
+ const isGroupOrShared = Boolean(session.isGroup || session.memberCount && session.memberCount > 1);
13250
+ const metaParts = [lastActivity, visibilityLabel, roleLabel].filter(Boolean);
12554
13251
  const { handlers: longPressHandlers } = useLongPress({
12555
13252
  delay: 500,
12556
13253
  onLongPress: (e) => {
@@ -12692,17 +13389,23 @@ var SessionItem = React.memo(
12692
13389
  "data-testid": `${testIdPrefix}-session-${session.id}`,
12693
13390
  ...longPressHandlers,
12694
13391
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
12695
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [
12696
- /* @__PURE__ */ jsxRuntime.jsx(
12697
- MemoizedEditableText,
12698
- {
12699
- ref: editableTitleRef,
12700
- value: displayTitle,
12701
- onSave: (newTitle) => onRename?.(newTitle),
12702
- isLoading: isTitleGenerating
12703
- }
12704
- ),
12705
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] text-gray-400 dark:text-gray-500 truncate mt-0.5", children: lastActivity })
13392
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 min-w-0 flex-1", children: [
13393
+ isGroupOrShared && /* @__PURE__ */ jsxRuntime.jsx(react.UsersThree, { size: 14, weight: "duotone", className: "text-primary-500 dark:text-primary-400 mt-1 flex-shrink-0" }),
13394
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [
13395
+ /* @__PURE__ */ jsxRuntime.jsx(
13396
+ MemoizedEditableText,
13397
+ {
13398
+ ref: editableTitleRef,
13399
+ value: displayTitle,
13400
+ onSave: (newTitle) => onRename?.(newTitle),
13401
+ isLoading: isTitleGenerating
13402
+ }
13403
+ ),
13404
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[11px] text-gray-400 dark:text-gray-500 truncate mt-0.5", children: [
13405
+ metaParts.join(" \u2022 "),
13406
+ isGroupOrShared && session.memberCount && session.memberCount > 0 && /* @__PURE__ */ jsxRuntime.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 })
13407
+ ] })
13408
+ ] })
12706
13409
  ] }),
12707
13410
  !isTouch && hasContextMenu && /* @__PURE__ */ jsxRuntime.jsxs(react$1.Menu, { children: [
12708
13411
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -13056,24 +13759,23 @@ function AllChatsList({ dataSource, onSessionSelect, activeSessionId }) {
13056
13759
  setOffset((prev) => prev + limit);
13057
13760
  }
13058
13761
  }, [fetching, hasMore]);
13059
- const loadMoreRef = React.useCallback(
13060
- (node) => {
13061
- if (!node || fetching || !hasMore) {
13062
- return;
13762
+ const loadMoreNodeRef = React.useRef(null);
13763
+ const loadMoreRef = React.useCallback((node) => {
13764
+ loadMoreNodeRef.current = node;
13765
+ }, []);
13766
+ React.useEffect(() => {
13767
+ const node = loadMoreNodeRef.current;
13768
+ if (!node || fetching || !hasMore) {
13769
+ return;
13770
+ }
13771
+ const observer = new IntersectionObserver((entries) => {
13772
+ if (entries[0].isIntersecting) {
13773
+ handleLoadMore();
13063
13774
  }
13064
- const observer = new IntersectionObserver(
13065
- (entries) => {
13066
- if (entries[0].isIntersecting) {
13067
- handleLoadMore();
13068
- }
13069
- },
13070
- { threshold: 0.1 }
13071
- );
13072
- observer.observe(node);
13073
- return () => observer.disconnect();
13074
- },
13075
- [fetching, hasMore, handleLoadMore]
13076
- );
13775
+ }, { threshold: 0.1 });
13776
+ observer.observe(node);
13777
+ return () => observer.disconnect();
13778
+ }, [fetching, hasMore, handleLoadMore]);
13077
13779
  const derivedUsers = React.useMemo(() => {
13078
13780
  if (dataSource.listUsers) {
13079
13781
  return users;
@@ -13131,57 +13833,61 @@ function AllChatsList({ dataSource, onSessionSelect, activeSessionId }) {
13131
13833
  role: "list",
13132
13834
  "aria-label": t("BiChat.AllChats.OrganizationChatSessions"),
13133
13835
  children: [
13134
- chats.map((chat) => /* @__PURE__ */ jsxRuntime.jsx(
13135
- framerMotion.motion.div,
13136
- {
13137
- initial: { opacity: 0, y: -10 },
13138
- animate: { opacity: 1, y: 0 },
13139
- exit: { opacity: 0, y: -10 },
13140
- children: /* @__PURE__ */ jsxRuntime.jsx(
13141
- "div",
13142
- {
13143
- role: "link",
13144
- tabIndex: 0,
13145
- onClick: () => onSessionSelect(chat.id),
13146
- onKeyDown: (e) => {
13147
- if (e.key === "Enter" || e.key === " ") {
13148
- e.preventDefault();
13149
- onSessionSelect(chat.id);
13150
- }
13151
- },
13152
- className: `
13836
+ chats.map((chat) => {
13837
+ const owner = chat.owner ?? {
13838
+ firstName: "",
13839
+ lastName: "",
13840
+ initials: "U"
13841
+ };
13842
+ const ownerName = [owner.firstName, owner.lastName].filter(Boolean).join(" ");
13843
+ return /* @__PURE__ */ jsxRuntime.jsx(
13844
+ framerMotion.motion.div,
13845
+ {
13846
+ initial: { opacity: 0, y: -10 },
13847
+ animate: { opacity: 1, y: 0 },
13848
+ exit: { opacity: 0, y: -10 },
13849
+ children: /* @__PURE__ */ jsxRuntime.jsx(
13850
+ "div",
13851
+ {
13852
+ role: "link",
13853
+ tabIndex: 0,
13854
+ onClick: () => onSessionSelect(chat.id),
13855
+ onKeyDown: (e) => {
13856
+ if (e.key === "Enter" || e.key === " ") {
13857
+ e.preventDefault();
13858
+ onSessionSelect(chat.id);
13859
+ }
13860
+ },
13861
+ className: `
13153
13862
  block px-3 py-2 rounded-lg transition-smooth group cursor-pointer
13154
13863
  ${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"}
13155
13864
  `,
13156
- "aria-current": chat.id === activeSessionId ? "page" : void 0,
13157
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
13158
- /* @__PURE__ */ jsxRuntime.jsx(
13159
- MemoizedUserAvatar,
13160
- {
13161
- firstName: chat.owner.firstName,
13162
- lastName: chat.owner.lastName,
13163
- initials: chat.owner.initials,
13164
- size: "sm"
13165
- }
13166
- ),
13167
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
13168
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium truncate", children: chat.title || t("BiChat.Common.Untitled") }),
13169
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400 truncate", children: [
13170
- chat.owner.firstName,
13171
- " ",
13172
- chat.owner.lastName
13173
- ] }),
13174
- chat.status === "archived" && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 mt-1 px-2 py-0.5 bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 rounded-full text-xs", children: [
13175
- /* @__PURE__ */ jsxRuntime.jsx(react.Archive, { size: 12, className: "w-3 h-3" }),
13176
- t("BiChat.Chat.Archived")
13865
+ "aria-current": chat.id === activeSessionId ? "page" : void 0,
13866
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
13867
+ /* @__PURE__ */ jsxRuntime.jsx(
13868
+ MemoizedUserAvatar,
13869
+ {
13870
+ firstName: owner.firstName,
13871
+ lastName: owner.lastName,
13872
+ initials: owner.initials,
13873
+ size: "sm"
13874
+ }
13875
+ ),
13876
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
13877
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium truncate", children: chat.title || t("BiChat.Common.Untitled") }),
13878
+ ownerName && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 truncate", children: ownerName }),
13879
+ chat.status === "archived" && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 mt-1 px-2 py-0.5 bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 rounded-full text-xs", children: [
13880
+ /* @__PURE__ */ jsxRuntime.jsx(react.Archive, { size: 12, className: "w-3 h-3" }),
13881
+ t("BiChat.Chat.Archived")
13882
+ ] })
13177
13883
  ] })
13178
13884
  ] })
13179
- ] })
13180
- }
13181
- )
13182
- },
13183
- chat.id
13184
- )),
13885
+ }
13886
+ )
13887
+ },
13888
+ chat.id
13889
+ );
13890
+ }),
13185
13891
  hasMore && /* @__PURE__ */ jsxRuntime.jsx("div", { ref: loadMoreRef, className: "py-4 text-center", children: fetching ? /* @__PURE__ */ jsxRuntime.jsx(SessionSkeleton, { count: 2 }) : /* @__PURE__ */ jsxRuntime.jsx(
13186
13892
  "button",
13187
13893
  {
@@ -13951,19 +14657,22 @@ function Sidebar2({
13951
14657
  animate: "visible",
13952
14658
  role: "list",
13953
14659
  "aria-label": t("BiChat.Sidebar.PinnedChats"),
13954
- children: pinnedSessions.map((session) => /* @__PURE__ */ jsxRuntime.jsx(
13955
- SessionItem_default,
13956
- {
13957
- session,
13958
- isActive: session.id === activeSessionId,
13959
- onSelect: () => handleSessionSelect(session.id),
13960
- onArchive: () => handleSessionArchive(session.id),
13961
- onPin: () => handleSessionPin(session.id, session.pinned),
13962
- onRename: (newTitle) => handleSessionRename(session.id, newTitle),
13963
- onRegenerateTitle: () => handleSessionRegenerateTitle(session.id)
13964
- },
13965
- session.id
13966
- ))
14660
+ children: pinnedSessions.map((session) => {
14661
+ const canWrite = session.access?.canWrite ?? true;
14662
+ return /* @__PURE__ */ jsxRuntime.jsx(
14663
+ SessionItem_default,
14664
+ {
14665
+ session,
14666
+ isActive: session.id === activeSessionId,
14667
+ onSelect: () => handleSessionSelect(session.id),
14668
+ onArchive: canWrite ? () => handleSessionArchive(session.id) : void 0,
14669
+ onPin: canWrite ? () => handleSessionPin(session.id, session.pinned) : void 0,
14670
+ onRename: canWrite ? (newTitle) => handleSessionRename(session.id, newTitle) : void 0,
14671
+ onRegenerateTitle: canWrite ? () => handleSessionRegenerateTitle(session.id) : void 0
14672
+ },
14673
+ session.id
14674
+ );
14675
+ })
13967
14676
  }
13968
14677
  ),
13969
14678
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-gray-200 dark:border-gray-700 my-3" })
@@ -13985,19 +14694,22 @@ function Sidebar2({
13985
14694
  animate: "visible",
13986
14695
  role: "list",
13987
14696
  "aria-label": `${group.name} chats`,
13988
- children: group.sessions.map((session) => /* @__PURE__ */ jsxRuntime.jsx(
13989
- SessionItem_default,
13990
- {
13991
- session,
13992
- isActive: session.id === activeSessionId,
13993
- onSelect: () => handleSessionSelect(session.id),
13994
- onArchive: () => handleSessionArchive(session.id),
13995
- onPin: () => handleSessionPin(session.id, session.pinned),
13996
- onRename: (newTitle) => handleSessionRename(session.id, newTitle),
13997
- onRegenerateTitle: () => handleSessionRegenerateTitle(session.id)
13998
- },
13999
- session.id
14000
- ))
14697
+ children: group.sessions.map((session) => {
14698
+ const canWrite = session.access?.canWrite ?? true;
14699
+ return /* @__PURE__ */ jsxRuntime.jsx(
14700
+ SessionItem_default,
14701
+ {
14702
+ session,
14703
+ isActive: session.id === activeSessionId,
14704
+ onSelect: () => handleSessionSelect(session.id),
14705
+ onArchive: canWrite ? () => handleSessionArchive(session.id) : void 0,
14706
+ onPin: canWrite ? () => handleSessionPin(session.id, session.pinned) : void 0,
14707
+ onRename: canWrite ? (newTitle) => handleSessionRename(session.id, newTitle) : void 0,
14708
+ onRegenerateTitle: canWrite ? () => handleSessionRegenerateTitle(session.id) : void 0
14709
+ },
14710
+ session.id
14711
+ );
14712
+ })
14001
14713
  }
14002
14714
  )
14003
14715
  ] }, group.name)),
@@ -14873,12 +15585,12 @@ function QuestionStep({
14873
15585
  const data = selectedAnswers[question.id] || { };
14874
15586
  setOtherText(data.customText || "");
14875
15587
  }, [question.id]);
14876
- const handleOptionClick = (optionLabel) => {
15588
+ const handleOptionClick = (optionID) => {
14877
15589
  if (isMultiSelect) {
14878
- const newOptions = selectedOptions.includes(optionLabel) ? selectedOptions.filter((a) => a !== optionLabel) : [...selectedOptions, optionLabel];
15590
+ const newOptions = selectedOptions.includes(optionID) ? selectedOptions.filter((a) => a !== optionID) : [...selectedOptions, optionID];
14879
15591
  onAnswer({ options: newOptions, customText: otherText || void 0 });
14880
15592
  } else {
14881
- onAnswer({ options: [optionLabel], customText: otherText || void 0 });
15593
+ onAnswer({ options: [optionID], customText: otherText || void 0 });
14882
15594
  }
14883
15595
  };
14884
15596
  const handleOtherTextChange = (text) => {
@@ -14892,11 +15604,11 @@ function QuestionStep({
14892
15604
  /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white mb-2", children: question.text }) }),
14893
15605
  isMultiSelect && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 dark:text-gray-500 italic", children: t("BiChat.Question.SelectMulti") }),
14894
15606
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: (question.options || []).map((option) => {
14895
- const isSelected = selectedOptions.includes(option.label);
15607
+ const isSelected = selectedOptions.includes(option.id);
14896
15608
  return /* @__PURE__ */ jsxRuntime.jsx(
14897
15609
  "button",
14898
15610
  {
14899
- onClick: () => handleOptionClick(option.label),
15611
+ onClick: () => handleOptionClick(option.id),
14900
15612
  className: `
14901
15613
  cursor-pointer relative p-4 text-left border-2 rounded-lg transition-all
14902
15614
  ${isSelected ? "border-primary-500 bg-white dark:bg-gray-800" : "border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:border-gray-300 dark:hover:border-gray-600"}
@@ -14964,6 +15676,7 @@ function ConfirmationStep({
14964
15676
  const answerData = answers[question.id] || { options: [] };
14965
15677
  const selectedOptions = answerData.options || [];
14966
15678
  const customText = answerData.customText;
15679
+ const optionLabelByID = new Map((question.options || []).map((option) => [option.id, option.label]));
14967
15680
  const hasAnswer = selectedOptions.length > 0 || !!customText;
14968
15681
  return /* @__PURE__ */ jsxRuntime.jsxs(
14969
15682
  "div",
@@ -14976,7 +15689,7 @@ function ConfirmationStep({
14976
15689
  "span",
14977
15690
  {
14978
15691
  className: "inline-flex items-center px-3 py-1 rounded-lg text-sm font-medium border border-primary-500 bg-primary-500/10 text-primary-600 dark:border-primary-400 dark:bg-primary-400/10 dark:text-primary-400",
14979
- children: option
15692
+ children: optionLabelByID.get(option) || option
14980
15693
  },
14981
15694
  option
14982
15695
  )),
@@ -16157,6 +16870,76 @@ function useScrollToBottom(items) {
16157
16870
  scrollToBottom
16158
16871
  };
16159
16872
  }
16873
+ function useHttpDataSourceConfigFromApplet(options) {
16874
+ return React.useMemo(() => {
16875
+ const ctx = typeof window !== "undefined" ? window.__APPLET_CONTEXT__ : void 0;
16876
+ if (!ctx) {
16877
+ throw new Error(
16878
+ "Applet context not found. Ensure window.__APPLET_CONTEXT__ is injected by the backend."
16879
+ );
16880
+ }
16881
+ const rpcEndpoint = ctx.config?.rpcUIEndpoint ?? "/rpc";
16882
+ const streamEndpoint = ctx.config?.streamEndpoint ?? "/stream";
16883
+ const csrfToken = ctx.session?.csrfToken ?? (typeof window !== "undefined" ? window.__CSRF_TOKEN__ : void 0) ?? "";
16884
+ const isDev = typeof undefined?.DEV === "boolean" && undefined?.DEV;
16885
+ if (!csrfToken && isDev) {
16886
+ console.warn(
16887
+ "[useHttpDataSourceConfigFromApplet] CSRF token is empty \u2014 requests may be rejected by the server."
16888
+ );
16889
+ }
16890
+ return {
16891
+ baseUrl: "",
16892
+ rpcEndpoint,
16893
+ streamEndpoint,
16894
+ csrfToken,
16895
+ timeout: options?.timeout ?? 12e4
16896
+ };
16897
+ }, [options?.timeout]);
16898
+ }
16899
+ var SESSION_PATH_REGEX = /\/session\/([^/]+)/;
16900
+ function useBichatRouter({
16901
+ navigate,
16902
+ pathname,
16903
+ onNavigate
16904
+ }) {
16905
+ const activeSessionId = React.useMemo(
16906
+ () => pathname.match(SESSION_PATH_REGEX)?.[1],
16907
+ [pathname]
16908
+ );
16909
+ const maybeClose = React.useCallback(() => {
16910
+ onNavigate?.();
16911
+ }, [onNavigate]);
16912
+ const onSessionSelect = React.useCallback(
16913
+ (sessionId) => {
16914
+ if (sessionId) {
16915
+ navigate(`/session/${sessionId}`);
16916
+ } else {
16917
+ navigate("/");
16918
+ }
16919
+ maybeClose();
16920
+ },
16921
+ [navigate, maybeClose]
16922
+ );
16923
+ const onNewChat = React.useCallback(() => {
16924
+ navigate("/");
16925
+ maybeClose();
16926
+ }, [navigate, maybeClose]);
16927
+ const onArchivedView = React.useCallback(() => {
16928
+ navigate("/archived");
16929
+ maybeClose();
16930
+ }, [navigate, maybeClose]);
16931
+ const onBack = React.useCallback(() => {
16932
+ navigate("/");
16933
+ maybeClose();
16934
+ }, [navigate, maybeClose]);
16935
+ return {
16936
+ activeSessionId,
16937
+ onSessionSelect,
16938
+ onNewChat,
16939
+ onArchivedView,
16940
+ onBack
16941
+ };
16942
+ }
16160
16943
 
16161
16944
  // ui/src/bichat/index.ts
16162
16945
  init_IotaContext();
@@ -16460,10 +17243,59 @@ function resolveArtifactName(artifact) {
16460
17243
  }
16461
17244
  return `${label} artifact`;
16462
17245
  }
17246
+ function mapSessionUser(rawUser) {
17247
+ if (!isRecord2(rawUser)) {
17248
+ return void 0;
17249
+ }
17250
+ const rawId = rawUser.id;
17251
+ const id = readNonEmptyString(rawId) ?? (typeof rawId === "number" && Number.isFinite(rawId) ? String(rawId) : null);
17252
+ if (!id) {
17253
+ return void 0;
17254
+ }
17255
+ const firstName = readString2(rawUser.firstName);
17256
+ const lastName = readString2(rawUser.lastName);
17257
+ const initials = readNonEmptyString(rawUser.initials) || `${firstName.charAt(0)}${lastName.charAt(0)}`.trim().toUpperCase() || "U";
17258
+ return {
17259
+ id,
17260
+ firstName,
17261
+ lastName,
17262
+ initials
17263
+ };
17264
+ }
17265
+ function mapSessionAccess(rawAccess) {
17266
+ if (!isRecord2(rawAccess)) {
17267
+ return void 0;
17268
+ }
17269
+ const role = readString2(rawAccess.role).toLowerCase();
17270
+ const source = readString2(rawAccess.source).toLowerCase();
17271
+ const normalizedRole = role === "owner" || role === "editor" || role === "viewer" || role === "read_all" ? role : "none";
17272
+ const normalizedSource = source === "owner" || source === "member" || source === "permission" ? source : "none";
17273
+ const canRead = rawAccess.canRead === true || rawAccess.canRead === "true";
17274
+ const canWrite = rawAccess.canWrite === true || rawAccess.canWrite === "true";
17275
+ const canManageMembers = rawAccess.canManageMembers === true || rawAccess.canManageMembers === "true";
17276
+ if (normalizedRole === "none" && normalizedSource === "none" && !canRead && !canWrite && !canManageMembers) {
17277
+ return void 0;
17278
+ }
17279
+ return {
17280
+ role: normalizedRole,
17281
+ source: normalizedSource,
17282
+ canRead,
17283
+ canWrite,
17284
+ canManageMembers
17285
+ };
17286
+ }
16463
17287
  function toSession(session) {
16464
17288
  return {
16465
- ...session,
16466
- status: session.status === "archived" ? "archived" : "active"
17289
+ id: readString2(session.id),
17290
+ title: readString2(session.title),
17291
+ status: session.status === "archived" ? "archived" : "active",
17292
+ pinned: Boolean(session.pinned),
17293
+ createdAt: readString2(session.createdAt),
17294
+ updatedAt: readString2(session.updatedAt),
17295
+ owner: mapSessionUser(session.owner),
17296
+ isGroup: Boolean(session.isGroup),
17297
+ memberCount: typeof session.memberCount === "number" ? session.memberCount : void 0,
17298
+ access: mapSessionAccess(session.access)
16467
17299
  };
16468
17300
  }
16469
17301
  function toSessionArtifact(artifact) {
@@ -16737,6 +17569,7 @@ function sanitizeConversationTurn(rawTurn, index, fallbackSessionID) {
16737
17569
  id: userTurnID,
16738
17570
  content: readString2(rawTurn.userTurn.content),
16739
17571
  attachments: sanitizeUserAttachments(rawTurn.userTurn.attachments, turnID),
17572
+ author: mapSessionUser(rawTurn.userTurn.author),
16740
17573
  createdAt: readString2(rawTurn.userTurn.createdAt, createdAt)
16741
17574
  },
16742
17575
  assistantTurn: sanitizeAssistantTurn(rawTurn.assistantTurn, createdAt, turnID),
@@ -16805,10 +17638,11 @@ function sanitizePendingQuestion(rawPendingQuestion, sessionID) {
16805
17638
  return true;
16806
17639
  }).map((option, optionIndex) => {
16807
17640
  const label = readString2(option.label);
17641
+ const id = readString2(option.id, `${questionID}-opt-${optionIndex}`);
16808
17642
  return {
16809
- id: readString2(option.id, `${questionID}-opt-${optionIndex}`),
17643
+ id,
16810
17644
  label,
16811
- value: label
17645
+ value: id
16812
17646
  };
16813
17647
  }) : [];
16814
17648
  return {
@@ -17228,6 +18062,68 @@ async function clearSessionHistory(callRPC, sessionId) {
17228
18062
  async function compactSessionHistory(callRPC, sessionId) {
17229
18063
  return callRPC("bichat.session.compact", { id: sessionId });
17230
18064
  }
18065
+ async function listUsers(callRPC) {
18066
+ const data = await callRPC("bichat.user.list", {});
18067
+ return data.users.map((user) => ({
18068
+ id: String(user.id),
18069
+ firstName: user.firstName || "",
18070
+ lastName: user.lastName || "",
18071
+ initials: user.initials || ""
18072
+ }));
18073
+ }
18074
+ async function listAllSessions(callRPC, options) {
18075
+ const data = await callRPC("bichat.session.listAll", {
18076
+ limit: options?.limit ?? 50,
18077
+ offset: options?.offset ?? 0,
18078
+ includeArchived: options?.includeArchived ?? false,
18079
+ userId: options?.userId ?? null
18080
+ });
18081
+ return {
18082
+ sessions: data.sessions.map(toSession),
18083
+ total: typeof data.total === "number" ? data.total : data.sessions.length,
18084
+ hasMore: Boolean(data.hasMore)
18085
+ };
18086
+ }
18087
+ async function listSessionMembers(callRPC, sessionId) {
18088
+ const data = await callRPC("bichat.session.members.list", { sessionId });
18089
+ return data.members.map((member) => ({
18090
+ user: {
18091
+ id: String(member.user.id),
18092
+ firstName: member.user.firstName,
18093
+ lastName: member.user.lastName,
18094
+ initials: member.user.initials
18095
+ },
18096
+ role: (() => {
18097
+ const normalizedRole = (member.role || "").toLowerCase();
18098
+ if (normalizedRole === "owner") {
18099
+ return "owner";
18100
+ }
18101
+ if (normalizedRole === "editor") {
18102
+ return "editor";
18103
+ }
18104
+ return "viewer";
18105
+ })(),
18106
+ createdAt: member.createdAt,
18107
+ updatedAt: member.updatedAt
18108
+ }));
18109
+ }
18110
+ async function addSessionMember(callRPC, sessionId, userId, role) {
18111
+ await callRPC("bichat.session.members.add", {
18112
+ sessionId,
18113
+ userId,
18114
+ role: role.toUpperCase()
18115
+ });
18116
+ }
18117
+ async function updateSessionMemberRole(callRPC, sessionId, userId, role) {
18118
+ await callRPC("bichat.session.members.updateRole", {
18119
+ sessionId,
18120
+ userId,
18121
+ role: role.toUpperCase()
18122
+ });
18123
+ }
18124
+ async function removeSessionMember(callRPC, sessionId, userId) {
18125
+ await callRPC("bichat.session.members.remove", { sessionId, userId });
18126
+ }
17231
18127
 
17232
18128
  // ui/src/bichat/utils/sseParser.ts
17233
18129
  function* processDataLines(lines) {
@@ -17786,12 +18682,19 @@ async function submitQuestionAnswers(callRPC, sessionId, questionId, answers) {
17786
18682
  flatAnswers[qId] = answerData.options.join(", ");
17787
18683
  }
17788
18684
  }
17789
- await callRPC("bichat.question.submit", {
18685
+ const result = await callRPC("bichat.question.submit", {
17790
18686
  sessionId,
17791
18687
  checkpointId: questionId,
17792
18688
  answers: flatAnswers
17793
18689
  });
17794
- return { success: true };
18690
+ return {
18691
+ success: true,
18692
+ data: {
18693
+ session: toSession(result.session),
18694
+ turns: normalizeTurns(sanitizeConversationTurns(result.turns, sessionId)),
18695
+ pendingQuestion: sanitizePendingQuestion(result.pendingQuestion, sessionId)
18696
+ }
18697
+ };
17795
18698
  } catch (err) {
17796
18699
  return { success: false, error: err instanceof Error ? err.message : "Unknown error" };
17797
18700
  }
@@ -17833,7 +18736,12 @@ async function uploadSessionArtifacts(callRPC, sessionId, files, uploadFileFn) {
17833
18736
  const data = await callRPC("bichat.session.uploadArtifacts", {
17834
18737
  sessionId,
17835
18738
  attachments: uploads.map((upload) => ({
17836
- uploadId: upload.id
18739
+ id: String(upload.id),
18740
+ filename: upload.name,
18741
+ uploadId: upload.id,
18742
+ mimeType: upload.mimetype || "application/octet-stream",
18743
+ sizeBytes: upload.size,
18744
+ url: upload.url
17837
18745
  }))
17838
18746
  });
17839
18747
  return {
@@ -17960,6 +18868,24 @@ var HttpDataSource = class {
17960
18868
  async compactSessionHistory(sessionId) {
17961
18869
  return compactSessionHistory(this.boundCallRPC, sessionId);
17962
18870
  }
18871
+ async listUsers() {
18872
+ return listUsers(this.boundCallRPC);
18873
+ }
18874
+ async listAllSessions(options) {
18875
+ return listAllSessions(this.boundCallRPC, options);
18876
+ }
18877
+ async listSessionMembers(sessionId) {
18878
+ return listSessionMembers(this.boundCallRPC, sessionId);
18879
+ }
18880
+ async addSessionMember(sessionId, userId, role) {
18881
+ return addSessionMember(this.boundCallRPC, sessionId, userId, role);
18882
+ }
18883
+ async updateSessionMemberRole(sessionId, userId, role) {
18884
+ return updateSessionMemberRole(this.boundCallRPC, sessionId, userId, role);
18885
+ }
18886
+ async removeSessionMember(sessionId, userId) {
18887
+ return removeSessionMember(this.boundCallRPC, sessionId, userId);
18888
+ }
17963
18889
  // -------------------------------------------------------------------------
17964
18890
  // Message transport (delegates to MessageTransport)
17965
18891
  // -------------------------------------------------------------------------
@@ -18095,6 +19021,7 @@ exports.AttachmentGrid = MemoizedAttachmentGrid;
18095
19021
  exports.AttachmentPreview = AttachmentPreview_default;
18096
19022
  exports.AttachmentUpload = AttachmentUpload_default;
18097
19023
  exports.Avatar = Avatar;
19024
+ exports.AvatarStack = AvatarStack;
18098
19025
  exports.BiChatLayout = BiChatLayout;
18099
19026
  exports.Bubble = Bubble;
18100
19027
  exports.CHART_VISUAL = CHART_VISUAL;
@@ -18138,6 +19065,7 @@ exports.SessionArtifactList = SessionArtifactList;
18138
19065
  exports.SessionArtifactPreview = SessionArtifactPreview;
18139
19066
  exports.SessionArtifactsPanel = SessionArtifactsPanel;
18140
19067
  exports.SessionItem = SessionItem_default;
19068
+ exports.SessionMembersModal = SessionMembersModal;
18141
19069
  exports.SessionSkeleton = SessionSkeleton;
18142
19070
  exports.Sidebar = Sidebar2;
18143
19071
  exports.Skeleton = MemoizedSkeleton;
@@ -18204,6 +19132,7 @@ exports.useActionButtonContext = useActionButtonContext;
18204
19132
  exports.useAttachments = useAttachments;
18205
19133
  exports.useAutoScroll = useAutoScroll;
18206
19134
  exports.useAvatarContext = useAvatarContext;
19135
+ exports.useBichatRouter = useBichatRouter;
18207
19136
  exports.useBubbleContext = useBubbleContext;
18208
19137
  exports.useChatInput = useChatInput;
18209
19138
  exports.useChatMessaging = useChatMessaging;
@@ -18211,6 +19140,7 @@ exports.useChatSession = useChatSession;
18211
19140
  exports.useConfig = useConfig;
18212
19141
  exports.useDataTable = useDataTable;
18213
19142
  exports.useFocusTrap = useFocusTrap;
19143
+ exports.useHttpDataSourceConfigFromApplet = useHttpDataSourceConfigFromApplet;
18214
19144
  exports.useImageGallery = useImageGallery;
18215
19145
  exports.useIotaContext = useIotaContext;
18216
19146
  exports.useKeyboardShortcuts = useKeyboardShortcuts;