@iota-uz/sdk 0.4.37 → 0.4.38

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.
@@ -12,7 +12,7 @@ import rehypeKatex from 'rehype-katex';
12
12
  import { motion, useMotionValue, useTransform, AnimatePresence, MotionConfig, useReducedMotion } from 'framer-motion';
13
13
  import 'react-dom/client';
14
14
  import { differenceInMinutes, differenceInHours, differenceInDays, format, isSameDay, startOfDay, isToday, isYesterday } from 'date-fns';
15
- import { Menu, MenuButton, MenuItems, MenuItem, Popover, PopoverButton, PopoverPanel, Dialog, DialogBackdrop, DialogPanel, Transition } from '@headlessui/react';
15
+ import { Menu, MenuButton, MenuItems, MenuItem, Popover, PopoverButton, PopoverPanel, Transition } from '@headlessui/react';
16
16
  import { createPortal } from 'react-dom';
17
17
 
18
18
  var __defProp = Object.defineProperty;
@@ -2091,6 +2091,14 @@ var ChatMachine = class {
2091
2091
  this.lastSendAttempt = null;
2092
2092
  /** Prevents fetchSession effect from clobbering state while stream is active. */
2093
2093
  this.sendingSessionId = null;
2094
+ /**
2095
+ * Monotonic counter bumped on every real session switch. An async op (send
2096
+ * stream, post-stream sync, queue drain, resume, HITL) captures it at start
2097
+ * and re-checks before writing to the single shared `state.messaging`; a stale
2098
+ * epoch means the user navigated to a different session, so the write is
2099
+ * dropped instead of bleeding into / clobbering the now-visible session.
2100
+ */
2101
+ this.viewEpoch = 0;
2094
2102
  this.fetchCancelled = false;
2095
2103
  this.disposed = false;
2096
2104
  this.reasoningEffortOptions = null;
@@ -2275,6 +2283,20 @@ var ChatMachine = class {
2275
2283
  if (id === prev) {
2276
2284
  return;
2277
2285
  }
2286
+ this.viewEpoch++;
2287
+ this.abortController?.abort();
2288
+ this.sendingSessionId = null;
2289
+ this._stopPassivePolling();
2290
+ this._updateMessaging({
2291
+ streamingContent: "",
2292
+ thinkingContent: "",
2293
+ activeSteps: [],
2294
+ isStreaming: false,
2295
+ loading: false,
2296
+ generationInProgress: false,
2297
+ streamError: null,
2298
+ streamErrorRetryable: false
2299
+ });
2278
2300
  this.state.session.currentSessionId = id;
2279
2301
  this._hydrateDebugModeForSession(id);
2280
2302
  this._notifySession();
@@ -2352,6 +2374,48 @@ var ChatMachine = class {
2352
2374
  }
2353
2375
  saveQueue(sid, this.state.input.messageQueue);
2354
2376
  }
2377
+ /**
2378
+ * True while `epoch` is still the active view — i.e. the machine has not been
2379
+ * disposed and no session switch has happened since `epoch` was captured.
2380
+ * Async ops gate their writes to the shared messaging state on this.
2381
+ */
2382
+ _isCurrentEpoch(epoch) {
2383
+ return !this.disposed && this.viewEpoch === epoch;
2384
+ }
2385
+ /** A send can't start while generating, mid-resume, or with an open question. */
2386
+ _isBlockedForSend() {
2387
+ return this.state.messaging.loading || this.state.messaging.generationInProgress || isOpenQuestionStatus(this.state.messaging.pendingQuestion?.status);
2388
+ }
2389
+ /**
2390
+ * Send the next queued message, but only if we still own the view (`epoch`)
2391
+ * and nothing is blocking (an in-flight generation or an open question). The
2392
+ * item is dequeued optimistically and restored to the front if it turns out we
2393
+ * can't send when the deferred tick fires — so a queued message is never
2394
+ * silently dropped.
2395
+ */
2396
+ _drainQueueIfReady(epoch) {
2397
+ if (!this._isCurrentEpoch(epoch)) {
2398
+ return;
2399
+ }
2400
+ const queue = this.state.input.messageQueue;
2401
+ if (queue.length === 0 || this._isBlockedForSend()) {
2402
+ return;
2403
+ }
2404
+ const next = queue[0];
2405
+ this._updateInput({ messageQueue: queue.slice(1) });
2406
+ setTimeout(() => {
2407
+ if (!this._isCurrentEpoch(epoch)) {
2408
+ return;
2409
+ }
2410
+ if (this._isBlockedForSend()) {
2411
+ this._updateInput({
2412
+ messageQueue: [next, ...this.state.input.messageQueue]
2413
+ });
2414
+ return;
2415
+ }
2416
+ this._sendMessageCore(next.content, next.attachments);
2417
+ }, 0);
2418
+ }
2355
2419
  _setDebugModeForSession(sessionId, enabled) {
2356
2420
  const nextDebugModeBySession = { ...this.state.session.debugModeBySession };
2357
2421
  if (enabled) {
@@ -2566,7 +2630,9 @@ var ChatMachine = class {
2566
2630
  return;
2567
2631
  }
2568
2632
  this.sendingSessionId = sessionId;
2569
- this.abortController = new AbortController();
2633
+ const controller = new AbortController();
2634
+ this.abortController = controller;
2635
+ const epoch = this.viewEpoch;
2570
2636
  this._updateMessaging({ isStreaming: true });
2571
2637
  try {
2572
2638
  let accumulatedContent = "";
@@ -2574,6 +2640,9 @@ var ChatMachine = class {
2574
2640
  sessionId,
2575
2641
  runId,
2576
2642
  (chunk) => {
2643
+ if (!this._isCurrentEpoch(epoch)) {
2644
+ return;
2645
+ }
2577
2646
  if (chunk.type === "snapshot" && chunk.snapshot?.partialContent !== void 0) {
2578
2647
  accumulatedContent = chunk.snapshot.partialContent;
2579
2648
  this._updateMessaging({ streamingContent: accumulatedContent });
@@ -2589,21 +2658,25 @@ var ChatMachine = class {
2589
2658
  } else if (chunk.type === "done" || chunk.type === "error") {
2590
2659
  }
2591
2660
  },
2592
- this.abortController.signal
2661
+ controller.signal
2593
2662
  );
2594
2663
  clearRunMarker(sessionId);
2595
- await this._syncSessionFromServer(sessionId, true);
2664
+ await this._syncSessionFromServer(sessionId, true, epoch);
2596
2665
  } finally {
2597
- this.sendingSessionId = null;
2598
- this.abortController = null;
2599
- this._updateMessaging({
2600
- isStreaming: false,
2601
- loading: false,
2602
- streamingContent: "",
2603
- thinkingContent: "",
2604
- activeSteps: [],
2605
- generationInProgress: false
2606
- });
2666
+ if (this.abortController === controller) {
2667
+ this.abortController = null;
2668
+ }
2669
+ if (this._isCurrentEpoch(epoch)) {
2670
+ this.sendingSessionId = null;
2671
+ this._updateMessaging({
2672
+ isStreaming: false,
2673
+ loading: false,
2674
+ streamingContent: "",
2675
+ thinkingContent: "",
2676
+ activeSteps: [],
2677
+ generationInProgress: false
2678
+ });
2679
+ }
2607
2680
  }
2608
2681
  }
2609
2682
  async _resumeAcceptedRunOrPoll(sessionId, runId) {
@@ -2846,11 +2919,14 @@ var ChatMachine = class {
2846
2919
  }
2847
2920
  return { activeSessionId, shouldNavigateAfter };
2848
2921
  }
2849
- async _syncSessionFromServer(sessionId, allowEmptyTurns = false) {
2922
+ async _syncSessionFromServer(sessionId, allowEmptyTurns = false, epoch) {
2850
2923
  const fetchResult = await this.dataSource.fetchSession(sessionId);
2851
2924
  if (!fetchResult) {
2852
2925
  return;
2853
2926
  }
2927
+ if (epoch !== void 0 && !this._isCurrentEpoch(epoch)) {
2928
+ return;
2929
+ }
2854
2930
  this._updateSession({ session: fetchResult.session });
2855
2931
  this._setTurnsFromFetch(
2856
2932
  allowEmptyTurns ? fetchResult.turns ?? [] : fetchResult.turns,
@@ -2866,7 +2942,9 @@ var ChatMachine = class {
2866
2942
  replaceFromMessageID,
2867
2943
  reasoningEffort,
2868
2944
  model,
2869
- tempTurnId
2945
+ tempTurnId,
2946
+ controller,
2947
+ epoch
2870
2948
  } = params;
2871
2949
  let accumulatedContent = "";
2872
2950
  let createdSessionId;
@@ -2877,7 +2955,7 @@ var ChatMachine = class {
2877
2955
  activeSessionId || "new",
2878
2956
  content,
2879
2957
  attachments,
2880
- this.abortController?.signal,
2958
+ controller.signal,
2881
2959
  {
2882
2960
  debugMode,
2883
2961
  replaceFromMessageID,
@@ -2885,7 +2963,7 @@ var ChatMachine = class {
2885
2963
  model
2886
2964
  }
2887
2965
  )) {
2888
- if (this.abortController?.signal.aborted) {
2966
+ if (controller.signal.aborted || !this._isCurrentEpoch(epoch)) {
2889
2967
  break;
2890
2968
  }
2891
2969
  if (chunk.type === "stream_started" && chunk.runId) {
@@ -2926,7 +3004,7 @@ var ChatMachine = class {
2926
3004
  sessionFetched = true;
2927
3005
  const finalSessionId2 = createdSessionId || activeSessionId;
2928
3006
  if (finalSessionId2 && finalSessionId2 !== "new") {
2929
- await this._syncSessionFromServer(finalSessionId2);
3007
+ await this._syncSessionFromServer(finalSessionId2, false, epoch);
2930
3008
  }
2931
3009
  }
2932
3010
  } else if (chunk.type === "done") {
@@ -2937,7 +3015,7 @@ var ChatMachine = class {
2937
3015
  sessionFetched = true;
2938
3016
  const finalSessionId2 = createdSessionId || activeSessionId;
2939
3017
  if (finalSessionId2 && finalSessionId2 !== "new") {
2940
- await this._syncSessionFromServer(finalSessionId2);
3018
+ await this._syncSessionFromServer(finalSessionId2, false, epoch);
2941
3019
  }
2942
3020
  }
2943
3021
  } else if (chunk.type === "user_message" && chunk.sessionId) {
@@ -2951,10 +3029,10 @@ var ChatMachine = class {
2951
3029
  if (finalSessionId && finalSessionId !== "new") {
2952
3030
  clearRunMarker(finalSessionId);
2953
3031
  }
2954
- const stopped = this.abortController?.signal.aborted ?? false;
3032
+ const stopped = controller.signal.aborted;
2955
3033
  return { createdSessionId, sessionFetched, stopped };
2956
3034
  }
2957
- async _ensureSessionSyncAfterStream(activeSessionId, createdSessionId, sessionFetched) {
3035
+ async _ensureSessionSyncAfterStream(activeSessionId, createdSessionId, sessionFetched, epoch) {
2958
3036
  if (sessionFetched) {
2959
3037
  return;
2960
3038
  }
@@ -2963,7 +3041,7 @@ var ChatMachine = class {
2963
3041
  return;
2964
3042
  }
2965
3043
  try {
2966
- await this._syncSessionFromServer(finalSessionId, true);
3044
+ await this._syncSessionFromServer(finalSessionId, true, epoch);
2967
3045
  } catch (fetchErr) {
2968
3046
  console.error("Failed to fetch session after stream:", fetchErr);
2969
3047
  }
@@ -2981,32 +3059,36 @@ var ChatMachine = class {
2981
3059
  this._clearStreamError();
2982
3060
  this.lastSendAttempt = null;
2983
3061
  }
2984
- _handleSendError(err, content, tempTurnId) {
3062
+ _handleSendError(err, content, tempTurn, epoch) {
3063
+ if (!this._isCurrentEpoch(epoch)) {
3064
+ return false;
3065
+ }
2985
3066
  if (err instanceof Error && err.name === "AbortError") {
2986
3067
  this._updateInput({ message: content });
2987
3068
  this._clearStreamError();
2988
3069
  this._updateMessaging({
2989
3070
  turns: this.state.messaging.turns.filter(
2990
- (turn) => turn.id !== tempTurnId
3071
+ (turn) => turn.id !== tempTurn.id
2991
3072
  )
2992
3073
  });
2993
3074
  const sessionId = this.sendingSessionId ?? this.state.session.currentSessionId;
2994
3075
  if (sessionId && sessionId !== "new") {
2995
- this._syncSessionFromServer(sessionId, true).catch(() => {
3076
+ this._syncSessionFromServer(sessionId, true, epoch).catch(() => {
2996
3077
  });
2997
3078
  }
2998
3079
  return false;
2999
3080
  }
3000
- this._updateMessaging({
3001
- turns: this.state.messaging.turns.filter(
3002
- (turn) => turn.id !== tempTurnId
3003
- )
3004
- });
3005
3081
  const normalized = normalizeRPCError(err, "Failed to send message");
3006
- this._updateInput({
3007
- message: content,
3008
- inputError: normalized.userMessage
3009
- });
3082
+ if (this.lastSendAttempt) {
3083
+ this.lastSendAttempt = {
3084
+ ...this.lastSendAttempt,
3085
+ options: {
3086
+ ...this.lastSendAttempt.options,
3087
+ replaceFromMessageID: tempTurn.userTurn.id
3088
+ }
3089
+ };
3090
+ }
3091
+ this._updateInput({ message: "", inputError: null });
3010
3092
  this._updateMessaging({
3011
3093
  streamError: normalized.userMessage,
3012
3094
  streamErrorRetryable: normalized.retryable
@@ -3067,7 +3149,9 @@ var ChatMachine = class {
3067
3149
  loading: true,
3068
3150
  streamingContent: ""
3069
3151
  });
3070
- this.abortController = new AbortController();
3152
+ const controller = new AbortController();
3153
+ this.abortController = controller;
3154
+ const epoch = this.viewEpoch;
3071
3155
  const curSessionId = this.state.session.currentSessionId;
3072
3156
  const curDebugMode = deriveDebugMode(this.state);
3073
3157
  const replaceFromMessageID = options?.replaceFromMessageID;
@@ -3093,9 +3177,12 @@ var ChatMachine = class {
3093
3177
  this.state.session.reasoningEffort
3094
3178
  ),
3095
3179
  model: this.state.session.model,
3096
- tempTurnId: tempTurn.id
3180
+ tempTurnId: tempTurn.id,
3181
+ controller,
3182
+ epoch
3097
3183
  });
3098
- if (stopped) {
3184
+ if (!this._isCurrentEpoch(epoch)) {
3185
+ } else if (stopped) {
3099
3186
  this._updateMessaging({
3100
3187
  turns: this.state.messaging.turns.filter(
3101
3188
  (turn) => turn.id !== tempTurn.id
@@ -3105,39 +3192,37 @@ var ChatMachine = class {
3105
3192
  this._clearStreamError();
3106
3193
  const syncId = createdSessionId || activeSessionId;
3107
3194
  if (syncId && syncId !== "new") {
3108
- await this._syncSessionFromServer(syncId, true).catch(() => {
3195
+ await this._syncSessionFromServer(syncId, true, epoch).catch(() => {
3109
3196
  });
3110
3197
  }
3111
3198
  } else {
3112
3199
  await this._ensureSessionSyncAfterStream(
3113
3200
  activeSessionId,
3114
3201
  createdSessionId,
3115
- sessionFetched
3202
+ sessionFetched,
3203
+ epoch
3116
3204
  );
3117
3205
  const targetSessionId = createdSessionId || activeSessionId;
3118
3206
  this._finalizeSuccessfulSend(targetSessionId, shouldNavigateAfter);
3119
3207
  }
3120
3208
  } catch (err) {
3121
- shouldDrainQueue = this._handleSendError(err, content, tempTurn.id);
3209
+ shouldDrainQueue = this._handleSendError(err, content, tempTurn, epoch);
3122
3210
  } finally {
3123
- this._updateMessaging({
3124
- loading: false,
3125
- streamingContent: "",
3126
- isStreaming: false,
3127
- thinkingContent: "",
3128
- activeSteps: []
3129
- });
3130
- this.abortController = null;
3131
- this.sendingSessionId = null;
3211
+ if (this._isCurrentEpoch(epoch)) {
3212
+ this._updateMessaging({
3213
+ loading: false,
3214
+ streamingContent: "",
3215
+ isStreaming: false,
3216
+ thinkingContent: "",
3217
+ activeSteps: []
3218
+ });
3219
+ this.sendingSessionId = null;
3220
+ }
3221
+ if (this.abortController === controller) {
3222
+ this.abortController = null;
3223
+ }
3132
3224
  if (shouldDrainQueue) {
3133
- const queue = this.state.input.messageQueue;
3134
- if (queue.length > 0 && !isOpenQuestionStatus(this.state.messaging.pendingQuestion?.status)) {
3135
- const next = queue[0];
3136
- this._updateInput({ messageQueue: queue.slice(1) });
3137
- setTimeout(() => {
3138
- this._sendMessageCore(next.content, next.attachments);
3139
- }, 0);
3140
- }
3225
+ this._drainQueueIfReady(epoch);
3141
3226
  }
3142
3227
  }
3143
3228
  }
@@ -3224,7 +3309,7 @@ var ChatMachine = class {
3224
3309
  );
3225
3310
  }
3226
3311
  // ── Regenerate / Edit ───────────────────────────────────────────────────
3227
- async _handleRegenerate(turnId) {
3312
+ async _handleRegenerate(turnId, model) {
3228
3313
  const curSessionId = this.state.session.currentSessionId;
3229
3314
  if (!curSessionId || curSessionId === "new") {
3230
3315
  return;
@@ -3233,6 +3318,9 @@ var ChatMachine = class {
3233
3318
  if (!turn) {
3234
3319
  return;
3235
3320
  }
3321
+ if (model && model !== this.state.session.model) {
3322
+ this._setModel(model);
3323
+ }
3236
3324
  this._updateSession({ error: null, errorRetryable: false });
3237
3325
  await this._sendMessageDirect(
3238
3326
  turn.userTurn.content,
@@ -3280,6 +3368,7 @@ var ChatMachine = class {
3280
3368
  if (!curSessionId || !curPendingQuestion) {
3281
3369
  return;
3282
3370
  }
3371
+ const epoch = this.viewEpoch;
3283
3372
  this._updateMessaging({ loading: true });
3284
3373
  this._updateSession({ error: null, errorRetryable: false });
3285
3374
  const previousPendingQuestion = curPendingQuestion;
@@ -3289,7 +3378,7 @@ var ChatMachine = class {
3289
3378
  previousPendingQuestion.id,
3290
3379
  answers
3291
3380
  );
3292
- if (this.disposed) {
3381
+ if (!this._isCurrentEpoch(epoch)) {
3293
3382
  return;
3294
3383
  }
3295
3384
  if (result.success) {
@@ -3310,7 +3399,7 @@ var ChatMachine = class {
3310
3399
  await this._resumeAcceptedRunOrPoll(curSessionId, result.data.runId);
3311
3400
  if (!this.state.messaging.generationInProgress) {
3312
3401
  const fetchResult = await this.dataSource.fetchSession(curSessionId);
3313
- if (this.disposed) {
3402
+ if (!this._isCurrentEpoch(epoch)) {
3314
3403
  return;
3315
3404
  }
3316
3405
  if (fetchResult) {
@@ -3328,7 +3417,7 @@ var ChatMachine = class {
3328
3417
  }
3329
3418
  } else if (curSessionId !== "new") {
3330
3419
  const fetchResult = await this.dataSource.fetchSession(curSessionId);
3331
- if (this.disposed) {
3420
+ if (!this._isCurrentEpoch(epoch)) {
3332
3421
  return;
3333
3422
  }
3334
3423
  if (fetchResult) {
@@ -3351,7 +3440,7 @@ var ChatMachine = class {
3351
3440
  });
3352
3441
  }
3353
3442
  } catch (err) {
3354
- if (this.disposed) {
3443
+ if (!this._isCurrentEpoch(epoch)) {
3355
3444
  return;
3356
3445
  }
3357
3446
  const normalized = normalizeRPCError(err, "Failed to submit answers");
@@ -3360,8 +3449,11 @@ var ChatMachine = class {
3360
3449
  errorRetryable: normalized.retryable
3361
3450
  });
3362
3451
  } finally {
3363
- if (!this.disposed) {
3452
+ if (this._isCurrentEpoch(epoch)) {
3364
3453
  this._updateMessaging({ loading: false });
3454
+ if (!this.state.session.error) {
3455
+ this._drainQueueIfReady(epoch);
3456
+ }
3365
3457
  }
3366
3458
  }
3367
3459
  }
@@ -3371,9 +3463,10 @@ var ChatMachine = class {
3371
3463
  if (!curSessionId || !curPendingQuestion) {
3372
3464
  return;
3373
3465
  }
3466
+ const epoch = this.viewEpoch;
3374
3467
  try {
3375
3468
  const result = await this.dataSource.rejectPendingQuestion(curSessionId);
3376
- if (this.disposed) {
3469
+ if (!this._isCurrentEpoch(epoch)) {
3377
3470
  return;
3378
3471
  }
3379
3472
  if (result.success) {
@@ -3392,7 +3485,7 @@ var ChatMachine = class {
3392
3485
  await this._resumeAcceptedRunOrPoll(curSessionId, result.data.runId);
3393
3486
  if (!this.state.messaging.generationInProgress && curSessionId !== "new") {
3394
3487
  const fetchResult = await this.dataSource.fetchSession(curSessionId);
3395
- if (this.disposed) {
3488
+ if (!this._isCurrentEpoch(epoch)) {
3396
3489
  return;
3397
3490
  }
3398
3491
  if (fetchResult) {
@@ -3410,7 +3503,7 @@ var ChatMachine = class {
3410
3503
  }
3411
3504
  } else if (curSessionId !== "new") {
3412
3505
  const fetchResult = await this.dataSource.fetchSession(curSessionId);
3413
- if (this.disposed) {
3506
+ if (!this._isCurrentEpoch(epoch)) {
3414
3507
  return;
3415
3508
  }
3416
3509
  if (fetchResult) {
@@ -3428,7 +3521,7 @@ var ChatMachine = class {
3428
3521
  });
3429
3522
  }
3430
3523
  } catch (err) {
3431
- if (this.disposed) {
3524
+ if (!this._isCurrentEpoch(epoch)) {
3432
3525
  return;
3433
3526
  }
3434
3527
  const normalized = normalizeRPCError(err, "Failed to reject question");
@@ -3436,6 +3529,10 @@ var ChatMachine = class {
3436
3529
  error: normalized.userMessage,
3437
3530
  errorRetryable: normalized.retryable
3438
3531
  });
3532
+ } finally {
3533
+ if (this._isCurrentEpoch(epoch) && !this.state.session.error) {
3534
+ this._drainQueueIfReady(epoch);
3535
+ }
3439
3536
  }
3440
3537
  }
3441
3538
  // ── Input / queue ───────────────────────────────────────────────────────
@@ -4292,6 +4389,167 @@ function AttachmentGrid({
4292
4389
  var MemoizedAttachmentGrid = React.memo(AttachmentGrid);
4293
4390
  MemoizedAttachmentGrid.displayName = "AttachmentGrid";
4294
4391
  var AttachmentGrid_default = MemoizedAttachmentGrid;
4392
+ function deepActiveElement(root) {
4393
+ let active = root.activeElement;
4394
+ while (active?.shadowRoot?.activeElement) {
4395
+ active = active.shadowRoot.activeElement;
4396
+ }
4397
+ return active instanceof HTMLElement ? active : null;
4398
+ }
4399
+ function useFocusTrap(containerRef, isActive, restoreFocusOnDeactivate) {
4400
+ useEffect(() => {
4401
+ if (!isActive || !containerRef.current) {
4402
+ return;
4403
+ }
4404
+ const container = containerRef.current;
4405
+ const root = container.getRootNode();
4406
+ const previouslyFocused = deepActiveElement(root);
4407
+ const getFocusableElements = () => {
4408
+ const selector = [
4409
+ "button:not([disabled])",
4410
+ "[href]",
4411
+ "input:not([disabled])",
4412
+ "select:not([disabled])",
4413
+ "textarea:not([disabled])",
4414
+ '[tabindex]:not([tabindex="-1"])'
4415
+ ].join(", ");
4416
+ return Array.from(container.querySelectorAll(selector));
4417
+ };
4418
+ const focusableElements = getFocusableElements();
4419
+ const initialTarget = container.querySelector("[data-autofocus]") ?? focusableElements[0];
4420
+ initialTarget?.focus();
4421
+ const handleTabKey = (e) => {
4422
+ if (e.key !== "Tab") {
4423
+ return;
4424
+ }
4425
+ const focusableElements2 = getFocusableElements();
4426
+ if (focusableElements2.length === 0) {
4427
+ return;
4428
+ }
4429
+ const firstElement = focusableElements2[0];
4430
+ const lastElement = focusableElements2[focusableElements2.length - 1];
4431
+ const active = deepActiveElement(root);
4432
+ if (e.shiftKey) {
4433
+ if (active === firstElement) {
4434
+ e.preventDefault();
4435
+ lastElement.focus();
4436
+ }
4437
+ } else {
4438
+ if (active === lastElement) {
4439
+ e.preventDefault();
4440
+ firstElement.focus();
4441
+ }
4442
+ }
4443
+ };
4444
+ container.addEventListener("keydown", handleTabKey);
4445
+ return () => {
4446
+ container.removeEventListener("keydown", handleTabKey);
4447
+ if (restoreFocusOnDeactivate) {
4448
+ restoreFocusOnDeactivate.focus();
4449
+ } else if (previouslyFocused) {
4450
+ previouslyFocused.focus();
4451
+ }
4452
+ };
4453
+ }, [containerRef, isActive, restoreFocusOnDeactivate]);
4454
+ }
4455
+ var lockCount = 0;
4456
+ var restorePreviousStyles = null;
4457
+ function applyBodyLock() {
4458
+ const body = document.body;
4459
+ const previousOverflow = body.style.overflow;
4460
+ const previousPaddingRight = body.style.paddingRight;
4461
+ const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
4462
+ body.style.overflow = "hidden";
4463
+ if (scrollbarWidth > 0) {
4464
+ const currentPadding = parseFloat(window.getComputedStyle(body).paddingRight) || 0;
4465
+ body.style.paddingRight = `${currentPadding + scrollbarWidth}px`;
4466
+ }
4467
+ restorePreviousStyles = () => {
4468
+ body.style.overflow = previousOverflow;
4469
+ body.style.paddingRight = previousPaddingRight;
4470
+ };
4471
+ }
4472
+ function useModalLock(isOpen) {
4473
+ useEffect(() => {
4474
+ if (!isOpen) {
4475
+ return;
4476
+ }
4477
+ if (lockCount === 0) {
4478
+ applyBodyLock();
4479
+ }
4480
+ lockCount += 1;
4481
+ return () => {
4482
+ lockCount -= 1;
4483
+ if (lockCount === 0 && restorePreviousStyles) {
4484
+ restorePreviousStyles();
4485
+ restorePreviousStyles = null;
4486
+ }
4487
+ };
4488
+ }, [isOpen]);
4489
+ }
4490
+ var DialogContext = createContext(null);
4491
+ function InlineDialog({ open, onClose, className, children }) {
4492
+ const containerRef = useRef(null);
4493
+ useModalLock(open);
4494
+ useFocusTrap(containerRef, open);
4495
+ useEffect(() => {
4496
+ if (!open) {
4497
+ return;
4498
+ }
4499
+ const container = containerRef.current;
4500
+ if (!container) {
4501
+ return;
4502
+ }
4503
+ const handler = (e) => {
4504
+ if (e.key === "Escape") {
4505
+ onClose();
4506
+ }
4507
+ };
4508
+ container.addEventListener("keydown", handler);
4509
+ return () => container.removeEventListener("keydown", handler);
4510
+ }, [open, onClose]);
4511
+ if (!open) {
4512
+ return null;
4513
+ }
4514
+ return /* @__PURE__ */ jsx(DialogContext.Provider, { value: onClose, children: /* @__PURE__ */ jsx(
4515
+ "div",
4516
+ {
4517
+ ref: containerRef,
4518
+ className,
4519
+ onClick: onClose,
4520
+ tabIndex: -1,
4521
+ children
4522
+ }
4523
+ ) });
4524
+ }
4525
+ function InlineDialogBackdrop(props) {
4526
+ return /* @__PURE__ */ jsx("div", { "aria-hidden": "true", ...props });
4527
+ }
4528
+ function InlineDialogPanel({
4529
+ children,
4530
+ onClick,
4531
+ ...rest
4532
+ }) {
4533
+ return /* @__PURE__ */ jsx(
4534
+ "div",
4535
+ {
4536
+ role: "dialog",
4537
+ "aria-modal": "true",
4538
+ onClick: (e) => {
4539
+ e.stopPropagation();
4540
+ onClick?.(e);
4541
+ },
4542
+ ...rest,
4543
+ children
4544
+ }
4545
+ );
4546
+ }
4547
+ function InlineDialogTitle(props) {
4548
+ return /* @__PURE__ */ jsx("h2", { ...props });
4549
+ }
4550
+ function InlineDialogDescription(props) {
4551
+ return /* @__PURE__ */ jsx("p", { ...props });
4552
+ }
4295
4553
  init_useTranslation();
4296
4554
  function ToolbarButton({
4297
4555
  onClick,
@@ -4494,16 +4752,16 @@ function ImageModal({
4494
4752
  }, [isZoomed, onClose]);
4495
4753
  const previewUrl = attachment.preview || createDataUrl(attachment.base64Data, attachment.mimeType);
4496
4754
  const zoomPercent = Math.round(scale * 100);
4497
- return /* @__PURE__ */ jsxs(Dialog, { open: isOpen, onClose, className: "relative", style: { zIndex: 99999 }, children: [
4755
+ return /* @__PURE__ */ jsxs(InlineDialog, { open: isOpen, onClose, className: "relative z-[99999]", children: [
4498
4756
  /* @__PURE__ */ jsx(
4499
- DialogBackdrop,
4757
+ InlineDialogBackdrop,
4500
4758
  {
4501
4759
  className: "fixed inset-0 bg-black/90 backdrop-blur-sm",
4502
4760
  style: { zIndex: 99999 }
4503
4761
  }
4504
4762
  ),
4505
4763
  /* @__PURE__ */ jsxs(
4506
- DialogPanel,
4764
+ InlineDialogPanel,
4507
4765
  {
4508
4766
  className: "fixed inset-0 flex flex-col",
4509
4767
  style: { zIndex: 1e5 },
@@ -4652,7 +4910,7 @@ var defaultClassNames = {
4652
4910
  bubble: "bg-primary-600 text-white rounded-2xl rounded-br-sm px-4 py-3 shadow-sm",
4653
4911
  content: "text-sm whitespace-pre-wrap break-words leading-relaxed",
4654
4912
  attachments: "mb-2 w-full",
4655
- actions: "flex items-center gap-1 mt-2 transition-opacity duration-150 group-focus-within:opacity-100 [@media(hover:hover)]:opacity-0 [@media(hover:hover)]:group-hover:opacity-100",
4913
+ actions: "flex items-center gap-1 mt-2",
4656
4914
  actionButton: "cursor-pointer p-2 min-h-[44px] min-w-[44px] flex items-center justify-center text-gray-500 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 active:bg-gray-200 dark:active:bg-gray-700 rounded-md transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50",
4657
4915
  timestamp: "text-xs text-gray-400 dark:text-gray-500 mr-1"
4658
4916
  };
@@ -5051,6 +5309,7 @@ function UserTurnView({
5051
5309
  }
5052
5310
  );
5053
5311
  }
5312
+ init_IotaContext();
5054
5313
  init_useTranslation();
5055
5314
  function toBase64(str) {
5056
5315
  const bytes = new TextEncoder().encode(str);
@@ -6165,6 +6424,8 @@ function FullscreenOverlay({ title, onClose, closeLabel, children }) {
6165
6424
  const panelRef = useRef(null);
6166
6425
  const onCloseRef = useRef(onClose);
6167
6426
  onCloseRef.current = onClose;
6427
+ useModalLock(true);
6428
+ useFocusTrap(panelRef, true);
6168
6429
  useEffect(() => {
6169
6430
  const onKeyDown = (e) => {
6170
6431
  if (e.key === "Escape") {
@@ -6173,7 +6434,6 @@ function FullscreenOverlay({ title, onClose, closeLabel, children }) {
6173
6434
  }
6174
6435
  };
6175
6436
  document.addEventListener("keydown", onKeyDown);
6176
- panelRef.current?.focus();
6177
6437
  return () => document.removeEventListener("keydown", onKeyDown);
6178
6438
  }, []);
6179
6439
  return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0", style: { zIndex: 99999 }, children: [
@@ -7742,7 +8002,7 @@ var defaultClassNames2 = {
7742
8002
  artifacts: "mb-1 flex flex-wrap gap-2",
7743
8003
  sources: "",
7744
8004
  explanation: "mt-4 border-t border-gray-100 dark:border-gray-700 pt-4",
7745
- actions: "flex items-center gap-1 transition-opacity duration-150 group-focus-within:opacity-100 [@media(hover:hover)]:opacity-0 [@media(hover:hover)]:group-hover:opacity-100",
8005
+ actions: "flex items-center gap-1",
7746
8006
  actionButton: "cursor-pointer p-2 min-h-[44px] min-w-[44px] flex items-center justify-center text-gray-500 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 active:bg-gray-200 dark:active:bg-gray-700 rounded-md transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50",
7747
8007
  timestamp: "text-xs text-gray-400 dark:text-gray-500 mr-1"
7748
8008
  };
@@ -7776,6 +8036,7 @@ function AssistantMessage({
7776
8036
  classNames: classNameOverrides,
7777
8037
  onCopy,
7778
8038
  onRegenerate,
8039
+ regenerateModels,
7779
8040
  onSendMessage,
7780
8041
  sendDisabled = false,
7781
8042
  hideAvatar = false,
@@ -7786,6 +8047,8 @@ function AssistantMessage({
7786
8047
  const { t } = useTranslation();
7787
8048
  const [explanationExpanded, setExplanationExpanded] = useState(false);
7788
8049
  const [isCopied, setIsCopied] = useState(false);
8050
+ const [showRegenPicker, setShowRegenPicker] = useState(false);
8051
+ const regenPickerRef = useRef(null);
7789
8052
  const copyFeedbackTimeoutRef = useRef(
7790
8053
  null
7791
8054
  );
@@ -7838,11 +8101,56 @@ function AssistantMessage({
7838
8101
  console.error("Failed to copy:", err);
7839
8102
  }
7840
8103
  }, [onCopy, turn.content]);
8104
+ const hasRegenChoices = (regenerateModels?.length ?? 0) >= 2;
7841
8105
  const handleRegenerateClick = useCallback(async () => {
7842
- if (onRegenerate && turnId) {
7843
- await onRegenerate(turnId);
8106
+ if (!onRegenerate || !turnId) {
8107
+ return;
7844
8108
  }
7845
- }, [onRegenerate, turnId]);
8109
+ if (hasRegenChoices) {
8110
+ setShowRegenPicker((prev) => !prev);
8111
+ return;
8112
+ }
8113
+ await onRegenerate(turnId);
8114
+ }, [onRegenerate, turnId, hasRegenChoices]);
8115
+ const handleRegenerateWithModel = useCallback(
8116
+ async (modelId) => {
8117
+ setShowRegenPicker(false);
8118
+ if (onRegenerate && turnId) {
8119
+ await onRegenerate(turnId, modelId);
8120
+ }
8121
+ },
8122
+ [onRegenerate, turnId]
8123
+ );
8124
+ useEffect(() => {
8125
+ if (!showRegenPicker) {
8126
+ return;
8127
+ }
8128
+ const handlePointer = (e) => {
8129
+ const root = regenPickerRef.current;
8130
+ if (!root) {
8131
+ return;
8132
+ }
8133
+ const path = typeof e.composedPath === "function" ? e.composedPath() : [];
8134
+ const insideViaPath = path.includes(root);
8135
+ const insideViaContains = root.contains(e.target);
8136
+ if (!insideViaPath && !insideViaContains) {
8137
+ setShowRegenPicker(false);
8138
+ }
8139
+ };
8140
+ const handleKey = (e) => {
8141
+ if (e.key === "Escape") {
8142
+ setShowRegenPicker(false);
8143
+ }
8144
+ };
8145
+ document.addEventListener("mousedown", handlePointer);
8146
+ document.addEventListener("touchstart", handlePointer);
8147
+ document.addEventListener("keydown", handleKey);
8148
+ return () => {
8149
+ document.removeEventListener("mousedown", handlePointer);
8150
+ document.removeEventListener("touchstart", handlePointer);
8151
+ document.removeEventListener("keydown", handleKey);
8152
+ };
8153
+ }, [showRegenPicker]);
7846
8154
  const timestamp = formatRelativeTime(turn.createdAt, t);
7847
8155
  const avatarSlotProps = {
7848
8156
  text: isSystemMessage ? "SYS" : "AI"
@@ -7869,7 +8177,17 @@ function AssistantMessage({
7869
8177
  };
7870
8178
  const actionsSlotProps = {
7871
8179
  onCopy: handleCopyClick,
7872
- onRegenerate: canRegenerate ? handleRegenerateClick : void 0,
8180
+ onRegenerate: canRegenerate ? (model) => {
8181
+ if (!onRegenerate || !turnId) {
8182
+ return;
8183
+ }
8184
+ if (model) {
8185
+ void handleRegenerateWithModel(model);
8186
+ } else {
8187
+ void handleRegenerateClick();
8188
+ }
8189
+ } : void 0,
8190
+ regenerateModels,
7873
8191
  timestamp,
7874
8192
  canCopy: hasContent,
7875
8193
  canRegenerate
@@ -8060,16 +8378,56 @@ function AssistantMessage({
8060
8378
  children: isCopied ? /* @__PURE__ */ jsx(Check, { size: 14, weight: "bold" }) : /* @__PURE__ */ jsx(Copy, { size: 14, weight: "regular" })
8061
8379
  }
8062
8380
  ),
8063
- canRegenerate && /* @__PURE__ */ jsx(
8064
- "button",
8065
- {
8066
- onClick: handleRegenerateClick,
8067
- className: `cursor-pointer ${classes.actionButton}`,
8068
- "aria-label": t("BiChat.Message.Regenerate"),
8069
- title: t("BiChat.Message.Regenerate"),
8070
- children: /* @__PURE__ */ jsx(ArrowsClockwise, { size: 14, weight: "regular" })
8071
- }
8072
- )
8381
+ canRegenerate && /* @__PURE__ */ jsxs("div", { ref: regenPickerRef, className: "relative inline-flex", children: [
8382
+ /* @__PURE__ */ jsx(
8383
+ "button",
8384
+ {
8385
+ onClick: handleRegenerateClick,
8386
+ className: `cursor-pointer ${classes.actionButton} ${showRegenPicker ? "bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-200" : ""}`,
8387
+ "aria-label": t("BiChat.Message.Regenerate"),
8388
+ title: t("BiChat.Message.Regenerate"),
8389
+ "aria-haspopup": hasRegenChoices ? "menu" : void 0,
8390
+ "aria-expanded": hasRegenChoices ? showRegenPicker : void 0,
8391
+ children: /* @__PURE__ */ jsx(ArrowsClockwise, { size: 14, weight: "regular" })
8392
+ }
8393
+ ),
8394
+ hasRegenChoices && showRegenPicker && /* @__PURE__ */ jsx(
8395
+ "div",
8396
+ {
8397
+ role: "menu",
8398
+ "aria-label": t("BiChat.Message.Regenerate"),
8399
+ className: "animate-slide-up absolute left-0 top-full z-20 mt-1 flex flex-col gap-0.5 rounded-lg border border-gray-200 bg-white p-1 shadow-lg dark:border-gray-700 dark:bg-gray-800",
8400
+ children: regenerateModels.map((m, i) => {
8401
+ const isFast = i === 0;
8402
+ const Icon = isFast ? Lightning : Brain;
8403
+ const accent = isFast ? "text-amber-600 dark:text-amber-400" : "text-blue-600 dark:text-blue-400";
8404
+ return /* @__PURE__ */ jsxs(
8405
+ "button",
8406
+ {
8407
+ role: "menuitem",
8408
+ type: "button",
8409
+ onClick: () => {
8410
+ void handleRegenerateWithModel(m.id);
8411
+ },
8412
+ className: "flex items-center gap-2 whitespace-nowrap rounded-md px-2.5 py-1.5 text-xs font-medium text-gray-700 transition-colors duration-150 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700",
8413
+ children: [
8414
+ /* @__PURE__ */ jsx(
8415
+ Icon,
8416
+ {
8417
+ size: 14,
8418
+ weight: "fill",
8419
+ className: accent
8420
+ }
8421
+ ),
8422
+ /* @__PURE__ */ jsx("span", { children: t(m.label) })
8423
+ ]
8424
+ },
8425
+ m.id
8426
+ );
8427
+ })
8428
+ }
8429
+ )
8430
+ ] })
8073
8431
  ] })
8074
8432
  )
8075
8433
  }
@@ -8229,6 +8587,14 @@ function AssistantTurnView({
8229
8587
  }) {
8230
8588
  const { debugMode } = useChatSession();
8231
8589
  const { handleCopy, handleRegenerate, pendingQuestion, sendMessage: sendMessage2, loading } = useChatMessaging();
8590
+ const iotaContext = useIotaContext();
8591
+ const regenerateModels = useMemo(() => {
8592
+ const models = iotaContext.extensions?.llm?.models;
8593
+ if (!models || models.length < 2) {
8594
+ return void 0;
8595
+ }
8596
+ return models.map((m) => ({ id: m.id, label: m.label }));
8597
+ }, [iotaContext.extensions?.llm?.models]);
8232
8598
  const assistantTurn = turn.assistantTurn;
8233
8599
  if (!assistantTurn) {
8234
8600
  return null;
@@ -8257,6 +8623,7 @@ function AssistantTurnView({
8257
8623
  classNames,
8258
8624
  onCopy: handleCopy,
8259
8625
  onRegenerate: allowRegenerate ? handleRegenerate : void 0,
8626
+ regenerateModels: allowRegenerate ? regenerateModels : void 0,
8260
8627
  onSendMessage: sendMessage2,
8261
8628
  sendDisabled: loading || isStreaming,
8262
8629
  hideAvatar,
@@ -10557,8 +10924,6 @@ function SessionArtifactList({
10557
10924
  }) })
10558
10925
  ] }, group.type)) });
10559
10926
  }
10560
-
10561
- // ui/src/bichat/components/SessionArtifactPreviewModal.tsx
10562
10927
  init_useTranslation();
10563
10928
 
10564
10929
  // ui/src/bichat/components/SessionArtifactPreview.tsx
@@ -10948,9 +11313,9 @@ function SessionArtifactPreviewModal({
10948
11313
  if (!artifact) {
10949
11314
  return null;
10950
11315
  }
10951
- return /* @__PURE__ */ jsxs(Dialog, { open: isOpen, onClose: handleClose, className: "relative z-50", children: [
10952
- /* @__PURE__ */ jsx(DialogBackdrop, { className: "fixed inset-0 bg-black/50 backdrop-blur-sm" }),
10953
- /* @__PURE__ */ jsx("div", { className: "fixed inset-0 overflow-y-auto p-4 lg:p-6", children: /* @__PURE__ */ jsx("div", { className: "mx-auto flex min-h-full w-full max-w-6xl items-center justify-center", children: /* @__PURE__ */ jsxs(DialogPanel, { className: "flex max-h-[92vh] w-full flex-col overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-xl dark:border-gray-700 dark:bg-gray-900", children: [
11316
+ return /* @__PURE__ */ jsxs(InlineDialog, { open: isOpen, onClose: handleClose, className: "relative z-50", children: [
11317
+ /* @__PURE__ */ jsx(InlineDialogBackdrop, { className: "fixed inset-0 bg-black/50 backdrop-blur-sm" }),
11318
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 overflow-y-auto p-4 lg:p-6", children: /* @__PURE__ */ jsx("div", { className: "mx-auto flex min-h-full w-full max-w-6xl items-center justify-center", children: /* @__PURE__ */ jsxs(InlineDialogPanel, { className: "flex max-h-[92vh] w-full flex-col overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-xl dark:border-gray-700 dark:bg-gray-900", children: [
10954
11319
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 border-b border-gray-200 px-4 py-3 dark:border-gray-700", children: [
10955
11320
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
10956
11321
  isEditingName ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
@@ -11450,109 +11815,6 @@ function SessionArtifactsPanel({
11450
11815
  }
11451
11816
  );
11452
11817
  }
11453
- var DialogContext = createContext(null);
11454
- var FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
11455
- function InlineDialog({ open, onClose, className, children }) {
11456
- const containerRef = useRef(null);
11457
- const previousFocusRef = useRef(null);
11458
- useEffect(() => {
11459
- if (!open) {
11460
- return;
11461
- }
11462
- previousFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
11463
- const container = containerRef.current;
11464
- if (!container) {
11465
- return;
11466
- }
11467
- const getFocusable = () => Array.from(
11468
- container.querySelectorAll(FOCUSABLE_SELECTOR)
11469
- );
11470
- const handler = (e) => {
11471
- if (e.key === "Escape") {
11472
- onClose();
11473
- return;
11474
- }
11475
- if (e.key !== "Tab") {
11476
- return;
11477
- }
11478
- const focusable = getFocusable();
11479
- if (focusable.length === 0) {
11480
- e.preventDefault();
11481
- container.focus();
11482
- return;
11483
- }
11484
- const first = focusable[0];
11485
- const last = focusable[focusable.length - 1];
11486
- if (e.shiftKey && document.activeElement === first) {
11487
- e.preventDefault();
11488
- last.focus();
11489
- } else if (!e.shiftKey && document.activeElement === last) {
11490
- e.preventDefault();
11491
- first.focus();
11492
- }
11493
- };
11494
- (getFocusable()[0] ?? container)?.focus();
11495
- container.addEventListener("keydown", handler);
11496
- return () => {
11497
- container.removeEventListener("keydown", handler);
11498
- previousFocusRef.current?.focus();
11499
- };
11500
- }, [open, onClose]);
11501
- if (!open) {
11502
- return null;
11503
- }
11504
- return /* @__PURE__ */ jsx(DialogContext.Provider, { value: onClose, children: /* @__PURE__ */ jsx(
11505
- "div",
11506
- {
11507
- ref: containerRef,
11508
- className,
11509
- onClick: onClose,
11510
- tabIndex: -1,
11511
- children
11512
- }
11513
- ) });
11514
- }
11515
- function InlineDialogBackdrop(props) {
11516
- return /* @__PURE__ */ jsx("div", { "aria-hidden": "true", ...props });
11517
- }
11518
- function InlineDialogPanel({
11519
- children,
11520
- onClick,
11521
- ...rest
11522
- }) {
11523
- const ref = useRef(null);
11524
- useEffect(() => {
11525
- if (!ref.current) {
11526
- return;
11527
- }
11528
- const target = ref.current.querySelector("[data-autofocus]") ?? ref.current.querySelector(
11529
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
11530
- );
11531
- target?.focus();
11532
- }, []);
11533
- return /* @__PURE__ */ jsx(
11534
- "div",
11535
- {
11536
- ref,
11537
- role: "dialog",
11538
- "aria-modal": "true",
11539
- onClick: (e) => {
11540
- e.stopPropagation();
11541
- onClick?.(e);
11542
- },
11543
- ...rest,
11544
- children
11545
- }
11546
- );
11547
- }
11548
- function InlineDialogTitle(props) {
11549
- return /* @__PURE__ */ jsx("h2", { ...props });
11550
- }
11551
- function InlineDialogDescription(props) {
11552
- return /* @__PURE__ */ jsx("p", { ...props });
11553
- }
11554
-
11555
- // ui/src/bichat/components/SessionMembersModal.tsx
11556
11818
  init_useTranslation();
11557
11819
  init_useTranslation();
11558
11820
  function ConfirmModalBase({
@@ -12439,7 +12701,7 @@ function ChatSessionCore({
12439
12701
  "div",
12440
12702
  {
12441
12703
  ref: layoutContainerRef,
12442
- className: "relative flex min-h-0 flex-1 overflow-hidden",
12704
+ className: "relative flex min-h-0 flex-1 overflow-clip",
12443
12705
  children: [
12444
12706
  /* @__PURE__ */ jsx("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: showWelcome ? /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col overflow-auto", children: /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center px-4 py-8", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-5xl", children: [
12445
12707
  welcomeSlot || /* @__PURE__ */ jsx(WelcomeContent_default, { onPromptSelect: handlePromptSelect, disabled: loading || composerDisabled }),
@@ -15891,61 +16153,6 @@ function useSidebarState() {
15891
16153
  const toggleMobile = useCallback(() => setIsMobileOpen((v) => !v), []);
15892
16154
  return { isMobile, isMobileOpen, openMobile, closeMobile, toggleMobile };
15893
16155
  }
15894
- function useFocusTrap(containerRef, isActive, restoreFocusOnDeactivate) {
15895
- useEffect(() => {
15896
- if (!isActive || !containerRef.current) {
15897
- return;
15898
- }
15899
- const container = containerRef.current;
15900
- const previouslyFocused = document.activeElement;
15901
- const getFocusableElements = () => {
15902
- const selector = [
15903
- "button:not([disabled])",
15904
- "[href]",
15905
- "input:not([disabled])",
15906
- "select:not([disabled])",
15907
- "textarea:not([disabled])",
15908
- '[tabindex]:not([tabindex="-1"])'
15909
- ].join(", ");
15910
- return Array.from(container.querySelectorAll(selector));
15911
- };
15912
- const focusableElements = getFocusableElements();
15913
- if (focusableElements.length > 0) {
15914
- focusableElements[0].focus();
15915
- }
15916
- const handleTabKey = (e) => {
15917
- if (e.key !== "Tab") {
15918
- return;
15919
- }
15920
- const focusableElements2 = getFocusableElements();
15921
- if (focusableElements2.length === 0) {
15922
- return;
15923
- }
15924
- const firstElement = focusableElements2[0];
15925
- const lastElement = focusableElements2[focusableElements2.length - 1];
15926
- if (e.shiftKey) {
15927
- if (document.activeElement === firstElement) {
15928
- e.preventDefault();
15929
- lastElement.focus();
15930
- }
15931
- } else {
15932
- if (document.activeElement === lastElement) {
15933
- e.preventDefault();
15934
- firstElement.focus();
15935
- }
15936
- }
15937
- };
15938
- container.addEventListener("keydown", handleTabKey);
15939
- return () => {
15940
- container.removeEventListener("keydown", handleTabKey);
15941
- if (restoreFocusOnDeactivate) {
15942
- restoreFocusOnDeactivate.focus();
15943
- } else if (previouslyFocused instanceof HTMLElement) {
15944
- previouslyFocused.focus();
15945
- }
15946
- };
15947
- }, [containerRef, isActive, restoreFocusOnDeactivate]);
15948
- }
15949
16156
 
15950
16157
  // ui/src/bichat/components/BiChatLayout.tsx
15951
16158
  init_useTranslation();
@@ -17195,18 +17402,6 @@ function useActiveRuns(dataSource, options = {}) {
17195
17402
 
17196
17403
  // ui/src/bichat/index.ts
17197
17404
  init_useTranslation();
17198
- function useModalLock(isOpen) {
17199
- useEffect(() => {
17200
- if (!isOpen) {
17201
- return;
17202
- }
17203
- const originalOverflow = document.body.style.overflow;
17204
- document.body.style.overflow = "hidden";
17205
- return () => {
17206
- document.body.style.overflow = originalOverflow;
17207
- };
17208
- }, [isOpen]);
17209
- }
17210
17405
  function useImageGallery(options = {}) {
17211
17406
  const { images: initialImages = [], wrap = false, onOpen, onClose, onNavigate } = options;
17212
17407
  const [isOpen, setIsOpen] = useState(false);