@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.
@@ -2103,6 +2103,14 @@ var ChatMachine = class {
2103
2103
  this.lastSendAttempt = null;
2104
2104
  /** Prevents fetchSession effect from clobbering state while stream is active. */
2105
2105
  this.sendingSessionId = null;
2106
+ /**
2107
+ * Monotonic counter bumped on every real session switch. An async op (send
2108
+ * stream, post-stream sync, queue drain, resume, HITL) captures it at start
2109
+ * and re-checks before writing to the single shared `state.messaging`; a stale
2110
+ * epoch means the user navigated to a different session, so the write is
2111
+ * dropped instead of bleeding into / clobbering the now-visible session.
2112
+ */
2113
+ this.viewEpoch = 0;
2106
2114
  this.fetchCancelled = false;
2107
2115
  this.disposed = false;
2108
2116
  this.reasoningEffortOptions = null;
@@ -2287,6 +2295,20 @@ var ChatMachine = class {
2287
2295
  if (id === prev) {
2288
2296
  return;
2289
2297
  }
2298
+ this.viewEpoch++;
2299
+ this.abortController?.abort();
2300
+ this.sendingSessionId = null;
2301
+ this._stopPassivePolling();
2302
+ this._updateMessaging({
2303
+ streamingContent: "",
2304
+ thinkingContent: "",
2305
+ activeSteps: [],
2306
+ isStreaming: false,
2307
+ loading: false,
2308
+ generationInProgress: false,
2309
+ streamError: null,
2310
+ streamErrorRetryable: false
2311
+ });
2290
2312
  this.state.session.currentSessionId = id;
2291
2313
  this._hydrateDebugModeForSession(id);
2292
2314
  this._notifySession();
@@ -2364,6 +2386,48 @@ var ChatMachine = class {
2364
2386
  }
2365
2387
  saveQueue(sid, this.state.input.messageQueue);
2366
2388
  }
2389
+ /**
2390
+ * True while `epoch` is still the active view — i.e. the machine has not been
2391
+ * disposed and no session switch has happened since `epoch` was captured.
2392
+ * Async ops gate their writes to the shared messaging state on this.
2393
+ */
2394
+ _isCurrentEpoch(epoch) {
2395
+ return !this.disposed && this.viewEpoch === epoch;
2396
+ }
2397
+ /** A send can't start while generating, mid-resume, or with an open question. */
2398
+ _isBlockedForSend() {
2399
+ return this.state.messaging.loading || this.state.messaging.generationInProgress || isOpenQuestionStatus(this.state.messaging.pendingQuestion?.status);
2400
+ }
2401
+ /**
2402
+ * Send the next queued message, but only if we still own the view (`epoch`)
2403
+ * and nothing is blocking (an in-flight generation or an open question). The
2404
+ * item is dequeued optimistically and restored to the front if it turns out we
2405
+ * can't send when the deferred tick fires — so a queued message is never
2406
+ * silently dropped.
2407
+ */
2408
+ _drainQueueIfReady(epoch) {
2409
+ if (!this._isCurrentEpoch(epoch)) {
2410
+ return;
2411
+ }
2412
+ const queue = this.state.input.messageQueue;
2413
+ if (queue.length === 0 || this._isBlockedForSend()) {
2414
+ return;
2415
+ }
2416
+ const next = queue[0];
2417
+ this._updateInput({ messageQueue: queue.slice(1) });
2418
+ setTimeout(() => {
2419
+ if (!this._isCurrentEpoch(epoch)) {
2420
+ return;
2421
+ }
2422
+ if (this._isBlockedForSend()) {
2423
+ this._updateInput({
2424
+ messageQueue: [next, ...this.state.input.messageQueue]
2425
+ });
2426
+ return;
2427
+ }
2428
+ this._sendMessageCore(next.content, next.attachments);
2429
+ }, 0);
2430
+ }
2367
2431
  _setDebugModeForSession(sessionId, enabled) {
2368
2432
  const nextDebugModeBySession = { ...this.state.session.debugModeBySession };
2369
2433
  if (enabled) {
@@ -2578,7 +2642,9 @@ var ChatMachine = class {
2578
2642
  return;
2579
2643
  }
2580
2644
  this.sendingSessionId = sessionId;
2581
- this.abortController = new AbortController();
2645
+ const controller = new AbortController();
2646
+ this.abortController = controller;
2647
+ const epoch = this.viewEpoch;
2582
2648
  this._updateMessaging({ isStreaming: true });
2583
2649
  try {
2584
2650
  let accumulatedContent = "";
@@ -2586,6 +2652,9 @@ var ChatMachine = class {
2586
2652
  sessionId,
2587
2653
  runId,
2588
2654
  (chunk) => {
2655
+ if (!this._isCurrentEpoch(epoch)) {
2656
+ return;
2657
+ }
2589
2658
  if (chunk.type === "snapshot" && chunk.snapshot?.partialContent !== void 0) {
2590
2659
  accumulatedContent = chunk.snapshot.partialContent;
2591
2660
  this._updateMessaging({ streamingContent: accumulatedContent });
@@ -2601,21 +2670,25 @@ var ChatMachine = class {
2601
2670
  } else if (chunk.type === "done" || chunk.type === "error") {
2602
2671
  }
2603
2672
  },
2604
- this.abortController.signal
2673
+ controller.signal
2605
2674
  );
2606
2675
  clearRunMarker(sessionId);
2607
- await this._syncSessionFromServer(sessionId, true);
2676
+ await this._syncSessionFromServer(sessionId, true, epoch);
2608
2677
  } finally {
2609
- this.sendingSessionId = null;
2610
- this.abortController = null;
2611
- this._updateMessaging({
2612
- isStreaming: false,
2613
- loading: false,
2614
- streamingContent: "",
2615
- thinkingContent: "",
2616
- activeSteps: [],
2617
- generationInProgress: false
2618
- });
2678
+ if (this.abortController === controller) {
2679
+ this.abortController = null;
2680
+ }
2681
+ if (this._isCurrentEpoch(epoch)) {
2682
+ this.sendingSessionId = null;
2683
+ this._updateMessaging({
2684
+ isStreaming: false,
2685
+ loading: false,
2686
+ streamingContent: "",
2687
+ thinkingContent: "",
2688
+ activeSteps: [],
2689
+ generationInProgress: false
2690
+ });
2691
+ }
2619
2692
  }
2620
2693
  }
2621
2694
  async _resumeAcceptedRunOrPoll(sessionId, runId) {
@@ -2858,11 +2931,14 @@ var ChatMachine = class {
2858
2931
  }
2859
2932
  return { activeSessionId, shouldNavigateAfter };
2860
2933
  }
2861
- async _syncSessionFromServer(sessionId, allowEmptyTurns = false) {
2934
+ async _syncSessionFromServer(sessionId, allowEmptyTurns = false, epoch) {
2862
2935
  const fetchResult = await this.dataSource.fetchSession(sessionId);
2863
2936
  if (!fetchResult) {
2864
2937
  return;
2865
2938
  }
2939
+ if (epoch !== void 0 && !this._isCurrentEpoch(epoch)) {
2940
+ return;
2941
+ }
2866
2942
  this._updateSession({ session: fetchResult.session });
2867
2943
  this._setTurnsFromFetch(
2868
2944
  allowEmptyTurns ? fetchResult.turns ?? [] : fetchResult.turns,
@@ -2878,7 +2954,9 @@ var ChatMachine = class {
2878
2954
  replaceFromMessageID,
2879
2955
  reasoningEffort,
2880
2956
  model,
2881
- tempTurnId
2957
+ tempTurnId,
2958
+ controller,
2959
+ epoch
2882
2960
  } = params;
2883
2961
  let accumulatedContent = "";
2884
2962
  let createdSessionId;
@@ -2889,7 +2967,7 @@ var ChatMachine = class {
2889
2967
  activeSessionId || "new",
2890
2968
  content,
2891
2969
  attachments,
2892
- this.abortController?.signal,
2970
+ controller.signal,
2893
2971
  {
2894
2972
  debugMode,
2895
2973
  replaceFromMessageID,
@@ -2897,7 +2975,7 @@ var ChatMachine = class {
2897
2975
  model
2898
2976
  }
2899
2977
  )) {
2900
- if (this.abortController?.signal.aborted) {
2978
+ if (controller.signal.aborted || !this._isCurrentEpoch(epoch)) {
2901
2979
  break;
2902
2980
  }
2903
2981
  if (chunk.type === "stream_started" && chunk.runId) {
@@ -2938,7 +3016,7 @@ var ChatMachine = class {
2938
3016
  sessionFetched = true;
2939
3017
  const finalSessionId2 = createdSessionId || activeSessionId;
2940
3018
  if (finalSessionId2 && finalSessionId2 !== "new") {
2941
- await this._syncSessionFromServer(finalSessionId2);
3019
+ await this._syncSessionFromServer(finalSessionId2, false, epoch);
2942
3020
  }
2943
3021
  }
2944
3022
  } else if (chunk.type === "done") {
@@ -2949,7 +3027,7 @@ var ChatMachine = class {
2949
3027
  sessionFetched = true;
2950
3028
  const finalSessionId2 = createdSessionId || activeSessionId;
2951
3029
  if (finalSessionId2 && finalSessionId2 !== "new") {
2952
- await this._syncSessionFromServer(finalSessionId2);
3030
+ await this._syncSessionFromServer(finalSessionId2, false, epoch);
2953
3031
  }
2954
3032
  }
2955
3033
  } else if (chunk.type === "user_message" && chunk.sessionId) {
@@ -2963,10 +3041,10 @@ var ChatMachine = class {
2963
3041
  if (finalSessionId && finalSessionId !== "new") {
2964
3042
  clearRunMarker(finalSessionId);
2965
3043
  }
2966
- const stopped = this.abortController?.signal.aborted ?? false;
3044
+ const stopped = controller.signal.aborted;
2967
3045
  return { createdSessionId, sessionFetched, stopped };
2968
3046
  }
2969
- async _ensureSessionSyncAfterStream(activeSessionId, createdSessionId, sessionFetched) {
3047
+ async _ensureSessionSyncAfterStream(activeSessionId, createdSessionId, sessionFetched, epoch) {
2970
3048
  if (sessionFetched) {
2971
3049
  return;
2972
3050
  }
@@ -2975,7 +3053,7 @@ var ChatMachine = class {
2975
3053
  return;
2976
3054
  }
2977
3055
  try {
2978
- await this._syncSessionFromServer(finalSessionId, true);
3056
+ await this._syncSessionFromServer(finalSessionId, true, epoch);
2979
3057
  } catch (fetchErr) {
2980
3058
  console.error("Failed to fetch session after stream:", fetchErr);
2981
3059
  }
@@ -2993,32 +3071,36 @@ var ChatMachine = class {
2993
3071
  this._clearStreamError();
2994
3072
  this.lastSendAttempt = null;
2995
3073
  }
2996
- _handleSendError(err, content, tempTurnId) {
3074
+ _handleSendError(err, content, tempTurn, epoch) {
3075
+ if (!this._isCurrentEpoch(epoch)) {
3076
+ return false;
3077
+ }
2997
3078
  if (err instanceof Error && err.name === "AbortError") {
2998
3079
  this._updateInput({ message: content });
2999
3080
  this._clearStreamError();
3000
3081
  this._updateMessaging({
3001
3082
  turns: this.state.messaging.turns.filter(
3002
- (turn) => turn.id !== tempTurnId
3083
+ (turn) => turn.id !== tempTurn.id
3003
3084
  )
3004
3085
  });
3005
3086
  const sessionId = this.sendingSessionId ?? this.state.session.currentSessionId;
3006
3087
  if (sessionId && sessionId !== "new") {
3007
- this._syncSessionFromServer(sessionId, true).catch(() => {
3088
+ this._syncSessionFromServer(sessionId, true, epoch).catch(() => {
3008
3089
  });
3009
3090
  }
3010
3091
  return false;
3011
3092
  }
3012
- this._updateMessaging({
3013
- turns: this.state.messaging.turns.filter(
3014
- (turn) => turn.id !== tempTurnId
3015
- )
3016
- });
3017
3093
  const normalized = normalizeRPCError(err, "Failed to send message");
3018
- this._updateInput({
3019
- message: content,
3020
- inputError: normalized.userMessage
3021
- });
3094
+ if (this.lastSendAttempt) {
3095
+ this.lastSendAttempt = {
3096
+ ...this.lastSendAttempt,
3097
+ options: {
3098
+ ...this.lastSendAttempt.options,
3099
+ replaceFromMessageID: tempTurn.userTurn.id
3100
+ }
3101
+ };
3102
+ }
3103
+ this._updateInput({ message: "", inputError: null });
3022
3104
  this._updateMessaging({
3023
3105
  streamError: normalized.userMessage,
3024
3106
  streamErrorRetryable: normalized.retryable
@@ -3079,7 +3161,9 @@ var ChatMachine = class {
3079
3161
  loading: true,
3080
3162
  streamingContent: ""
3081
3163
  });
3082
- this.abortController = new AbortController();
3164
+ const controller = new AbortController();
3165
+ this.abortController = controller;
3166
+ const epoch = this.viewEpoch;
3083
3167
  const curSessionId = this.state.session.currentSessionId;
3084
3168
  const curDebugMode = deriveDebugMode(this.state);
3085
3169
  const replaceFromMessageID = options?.replaceFromMessageID;
@@ -3105,9 +3189,12 @@ var ChatMachine = class {
3105
3189
  this.state.session.reasoningEffort
3106
3190
  ),
3107
3191
  model: this.state.session.model,
3108
- tempTurnId: tempTurn.id
3192
+ tempTurnId: tempTurn.id,
3193
+ controller,
3194
+ epoch
3109
3195
  });
3110
- if (stopped) {
3196
+ if (!this._isCurrentEpoch(epoch)) {
3197
+ } else if (stopped) {
3111
3198
  this._updateMessaging({
3112
3199
  turns: this.state.messaging.turns.filter(
3113
3200
  (turn) => turn.id !== tempTurn.id
@@ -3117,39 +3204,37 @@ var ChatMachine = class {
3117
3204
  this._clearStreamError();
3118
3205
  const syncId = createdSessionId || activeSessionId;
3119
3206
  if (syncId && syncId !== "new") {
3120
- await this._syncSessionFromServer(syncId, true).catch(() => {
3207
+ await this._syncSessionFromServer(syncId, true, epoch).catch(() => {
3121
3208
  });
3122
3209
  }
3123
3210
  } else {
3124
3211
  await this._ensureSessionSyncAfterStream(
3125
3212
  activeSessionId,
3126
3213
  createdSessionId,
3127
- sessionFetched
3214
+ sessionFetched,
3215
+ epoch
3128
3216
  );
3129
3217
  const targetSessionId = createdSessionId || activeSessionId;
3130
3218
  this._finalizeSuccessfulSend(targetSessionId, shouldNavigateAfter);
3131
3219
  }
3132
3220
  } catch (err) {
3133
- shouldDrainQueue = this._handleSendError(err, content, tempTurn.id);
3221
+ shouldDrainQueue = this._handleSendError(err, content, tempTurn, epoch);
3134
3222
  } finally {
3135
- this._updateMessaging({
3136
- loading: false,
3137
- streamingContent: "",
3138
- isStreaming: false,
3139
- thinkingContent: "",
3140
- activeSteps: []
3141
- });
3142
- this.abortController = null;
3143
- this.sendingSessionId = null;
3223
+ if (this._isCurrentEpoch(epoch)) {
3224
+ this._updateMessaging({
3225
+ loading: false,
3226
+ streamingContent: "",
3227
+ isStreaming: false,
3228
+ thinkingContent: "",
3229
+ activeSteps: []
3230
+ });
3231
+ this.sendingSessionId = null;
3232
+ }
3233
+ if (this.abortController === controller) {
3234
+ this.abortController = null;
3235
+ }
3144
3236
  if (shouldDrainQueue) {
3145
- const queue = this.state.input.messageQueue;
3146
- if (queue.length > 0 && !isOpenQuestionStatus(this.state.messaging.pendingQuestion?.status)) {
3147
- const next = queue[0];
3148
- this._updateInput({ messageQueue: queue.slice(1) });
3149
- setTimeout(() => {
3150
- this._sendMessageCore(next.content, next.attachments);
3151
- }, 0);
3152
- }
3237
+ this._drainQueueIfReady(epoch);
3153
3238
  }
3154
3239
  }
3155
3240
  }
@@ -3236,7 +3321,7 @@ var ChatMachine = class {
3236
3321
  );
3237
3322
  }
3238
3323
  // ── Regenerate / Edit ───────────────────────────────────────────────────
3239
- async _handleRegenerate(turnId) {
3324
+ async _handleRegenerate(turnId, model) {
3240
3325
  const curSessionId = this.state.session.currentSessionId;
3241
3326
  if (!curSessionId || curSessionId === "new") {
3242
3327
  return;
@@ -3245,6 +3330,9 @@ var ChatMachine = class {
3245
3330
  if (!turn) {
3246
3331
  return;
3247
3332
  }
3333
+ if (model && model !== this.state.session.model) {
3334
+ this._setModel(model);
3335
+ }
3248
3336
  this._updateSession({ error: null, errorRetryable: false });
3249
3337
  await this._sendMessageDirect(
3250
3338
  turn.userTurn.content,
@@ -3292,6 +3380,7 @@ var ChatMachine = class {
3292
3380
  if (!curSessionId || !curPendingQuestion) {
3293
3381
  return;
3294
3382
  }
3383
+ const epoch = this.viewEpoch;
3295
3384
  this._updateMessaging({ loading: true });
3296
3385
  this._updateSession({ error: null, errorRetryable: false });
3297
3386
  const previousPendingQuestion = curPendingQuestion;
@@ -3301,7 +3390,7 @@ var ChatMachine = class {
3301
3390
  previousPendingQuestion.id,
3302
3391
  answers
3303
3392
  );
3304
- if (this.disposed) {
3393
+ if (!this._isCurrentEpoch(epoch)) {
3305
3394
  return;
3306
3395
  }
3307
3396
  if (result.success) {
@@ -3322,7 +3411,7 @@ var ChatMachine = class {
3322
3411
  await this._resumeAcceptedRunOrPoll(curSessionId, result.data.runId);
3323
3412
  if (!this.state.messaging.generationInProgress) {
3324
3413
  const fetchResult = await this.dataSource.fetchSession(curSessionId);
3325
- if (this.disposed) {
3414
+ if (!this._isCurrentEpoch(epoch)) {
3326
3415
  return;
3327
3416
  }
3328
3417
  if (fetchResult) {
@@ -3340,7 +3429,7 @@ var ChatMachine = class {
3340
3429
  }
3341
3430
  } else if (curSessionId !== "new") {
3342
3431
  const fetchResult = await this.dataSource.fetchSession(curSessionId);
3343
- if (this.disposed) {
3432
+ if (!this._isCurrentEpoch(epoch)) {
3344
3433
  return;
3345
3434
  }
3346
3435
  if (fetchResult) {
@@ -3363,7 +3452,7 @@ var ChatMachine = class {
3363
3452
  });
3364
3453
  }
3365
3454
  } catch (err) {
3366
- if (this.disposed) {
3455
+ if (!this._isCurrentEpoch(epoch)) {
3367
3456
  return;
3368
3457
  }
3369
3458
  const normalized = normalizeRPCError(err, "Failed to submit answers");
@@ -3372,8 +3461,11 @@ var ChatMachine = class {
3372
3461
  errorRetryable: normalized.retryable
3373
3462
  });
3374
3463
  } finally {
3375
- if (!this.disposed) {
3464
+ if (this._isCurrentEpoch(epoch)) {
3376
3465
  this._updateMessaging({ loading: false });
3466
+ if (!this.state.session.error) {
3467
+ this._drainQueueIfReady(epoch);
3468
+ }
3377
3469
  }
3378
3470
  }
3379
3471
  }
@@ -3383,9 +3475,10 @@ var ChatMachine = class {
3383
3475
  if (!curSessionId || !curPendingQuestion) {
3384
3476
  return;
3385
3477
  }
3478
+ const epoch = this.viewEpoch;
3386
3479
  try {
3387
3480
  const result = await this.dataSource.rejectPendingQuestion(curSessionId);
3388
- if (this.disposed) {
3481
+ if (!this._isCurrentEpoch(epoch)) {
3389
3482
  return;
3390
3483
  }
3391
3484
  if (result.success) {
@@ -3404,7 +3497,7 @@ var ChatMachine = class {
3404
3497
  await this._resumeAcceptedRunOrPoll(curSessionId, result.data.runId);
3405
3498
  if (!this.state.messaging.generationInProgress && curSessionId !== "new") {
3406
3499
  const fetchResult = await this.dataSource.fetchSession(curSessionId);
3407
- if (this.disposed) {
3500
+ if (!this._isCurrentEpoch(epoch)) {
3408
3501
  return;
3409
3502
  }
3410
3503
  if (fetchResult) {
@@ -3422,7 +3515,7 @@ var ChatMachine = class {
3422
3515
  }
3423
3516
  } else if (curSessionId !== "new") {
3424
3517
  const fetchResult = await this.dataSource.fetchSession(curSessionId);
3425
- if (this.disposed) {
3518
+ if (!this._isCurrentEpoch(epoch)) {
3426
3519
  return;
3427
3520
  }
3428
3521
  if (fetchResult) {
@@ -3440,7 +3533,7 @@ var ChatMachine = class {
3440
3533
  });
3441
3534
  }
3442
3535
  } catch (err) {
3443
- if (this.disposed) {
3536
+ if (!this._isCurrentEpoch(epoch)) {
3444
3537
  return;
3445
3538
  }
3446
3539
  const normalized = normalizeRPCError(err, "Failed to reject question");
@@ -3448,6 +3541,10 @@ var ChatMachine = class {
3448
3541
  error: normalized.userMessage,
3449
3542
  errorRetryable: normalized.retryable
3450
3543
  });
3544
+ } finally {
3545
+ if (this._isCurrentEpoch(epoch) && !this.state.session.error) {
3546
+ this._drainQueueIfReady(epoch);
3547
+ }
3451
3548
  }
3452
3549
  }
3453
3550
  // ── Input / queue ───────────────────────────────────────────────────────
@@ -4304,6 +4401,167 @@ function AttachmentGrid({
4304
4401
  var MemoizedAttachmentGrid = React__default.default.memo(AttachmentGrid);
4305
4402
  MemoizedAttachmentGrid.displayName = "AttachmentGrid";
4306
4403
  var AttachmentGrid_default = MemoizedAttachmentGrid;
4404
+ function deepActiveElement(root) {
4405
+ let active = root.activeElement;
4406
+ while (active?.shadowRoot?.activeElement) {
4407
+ active = active.shadowRoot.activeElement;
4408
+ }
4409
+ return active instanceof HTMLElement ? active : null;
4410
+ }
4411
+ function useFocusTrap(containerRef, isActive, restoreFocusOnDeactivate) {
4412
+ React.useEffect(() => {
4413
+ if (!isActive || !containerRef.current) {
4414
+ return;
4415
+ }
4416
+ const container = containerRef.current;
4417
+ const root = container.getRootNode();
4418
+ const previouslyFocused = deepActiveElement(root);
4419
+ const getFocusableElements = () => {
4420
+ const selector = [
4421
+ "button:not([disabled])",
4422
+ "[href]",
4423
+ "input:not([disabled])",
4424
+ "select:not([disabled])",
4425
+ "textarea:not([disabled])",
4426
+ '[tabindex]:not([tabindex="-1"])'
4427
+ ].join(", ");
4428
+ return Array.from(container.querySelectorAll(selector));
4429
+ };
4430
+ const focusableElements = getFocusableElements();
4431
+ const initialTarget = container.querySelector("[data-autofocus]") ?? focusableElements[0];
4432
+ initialTarget?.focus();
4433
+ const handleTabKey = (e) => {
4434
+ if (e.key !== "Tab") {
4435
+ return;
4436
+ }
4437
+ const focusableElements2 = getFocusableElements();
4438
+ if (focusableElements2.length === 0) {
4439
+ return;
4440
+ }
4441
+ const firstElement = focusableElements2[0];
4442
+ const lastElement = focusableElements2[focusableElements2.length - 1];
4443
+ const active = deepActiveElement(root);
4444
+ if (e.shiftKey) {
4445
+ if (active === firstElement) {
4446
+ e.preventDefault();
4447
+ lastElement.focus();
4448
+ }
4449
+ } else {
4450
+ if (active === lastElement) {
4451
+ e.preventDefault();
4452
+ firstElement.focus();
4453
+ }
4454
+ }
4455
+ };
4456
+ container.addEventListener("keydown", handleTabKey);
4457
+ return () => {
4458
+ container.removeEventListener("keydown", handleTabKey);
4459
+ if (restoreFocusOnDeactivate) {
4460
+ restoreFocusOnDeactivate.focus();
4461
+ } else if (previouslyFocused) {
4462
+ previouslyFocused.focus();
4463
+ }
4464
+ };
4465
+ }, [containerRef, isActive, restoreFocusOnDeactivate]);
4466
+ }
4467
+ var lockCount = 0;
4468
+ var restorePreviousStyles = null;
4469
+ function applyBodyLock() {
4470
+ const body = document.body;
4471
+ const previousOverflow = body.style.overflow;
4472
+ const previousPaddingRight = body.style.paddingRight;
4473
+ const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
4474
+ body.style.overflow = "hidden";
4475
+ if (scrollbarWidth > 0) {
4476
+ const currentPadding = parseFloat(window.getComputedStyle(body).paddingRight) || 0;
4477
+ body.style.paddingRight = `${currentPadding + scrollbarWidth}px`;
4478
+ }
4479
+ restorePreviousStyles = () => {
4480
+ body.style.overflow = previousOverflow;
4481
+ body.style.paddingRight = previousPaddingRight;
4482
+ };
4483
+ }
4484
+ function useModalLock(isOpen) {
4485
+ React.useEffect(() => {
4486
+ if (!isOpen) {
4487
+ return;
4488
+ }
4489
+ if (lockCount === 0) {
4490
+ applyBodyLock();
4491
+ }
4492
+ lockCount += 1;
4493
+ return () => {
4494
+ lockCount -= 1;
4495
+ if (lockCount === 0 && restorePreviousStyles) {
4496
+ restorePreviousStyles();
4497
+ restorePreviousStyles = null;
4498
+ }
4499
+ };
4500
+ }, [isOpen]);
4501
+ }
4502
+ var DialogContext = React.createContext(null);
4503
+ function InlineDialog({ open, onClose, className, children }) {
4504
+ const containerRef = React.useRef(null);
4505
+ useModalLock(open);
4506
+ useFocusTrap(containerRef, open);
4507
+ React.useEffect(() => {
4508
+ if (!open) {
4509
+ return;
4510
+ }
4511
+ const container = containerRef.current;
4512
+ if (!container) {
4513
+ return;
4514
+ }
4515
+ const handler = (e) => {
4516
+ if (e.key === "Escape") {
4517
+ onClose();
4518
+ }
4519
+ };
4520
+ container.addEventListener("keydown", handler);
4521
+ return () => container.removeEventListener("keydown", handler);
4522
+ }, [open, onClose]);
4523
+ if (!open) {
4524
+ return null;
4525
+ }
4526
+ return /* @__PURE__ */ jsxRuntime.jsx(DialogContext.Provider, { value: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(
4527
+ "div",
4528
+ {
4529
+ ref: containerRef,
4530
+ className,
4531
+ onClick: onClose,
4532
+ tabIndex: -1,
4533
+ children
4534
+ }
4535
+ ) });
4536
+ }
4537
+ function InlineDialogBackdrop(props) {
4538
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", ...props });
4539
+ }
4540
+ function InlineDialogPanel({
4541
+ children,
4542
+ onClick,
4543
+ ...rest
4544
+ }) {
4545
+ return /* @__PURE__ */ jsxRuntime.jsx(
4546
+ "div",
4547
+ {
4548
+ role: "dialog",
4549
+ "aria-modal": "true",
4550
+ onClick: (e) => {
4551
+ e.stopPropagation();
4552
+ onClick?.(e);
4553
+ },
4554
+ ...rest,
4555
+ children
4556
+ }
4557
+ );
4558
+ }
4559
+ function InlineDialogTitle(props) {
4560
+ return /* @__PURE__ */ jsxRuntime.jsx("h2", { ...props });
4561
+ }
4562
+ function InlineDialogDescription(props) {
4563
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { ...props });
4564
+ }
4307
4565
  init_useTranslation();
4308
4566
  function ToolbarButton({
4309
4567
  onClick,
@@ -4506,16 +4764,16 @@ function ImageModal({
4506
4764
  }, [isZoomed, onClose]);
4507
4765
  const previewUrl = attachment.preview || createDataUrl(attachment.base64Data, attachment.mimeType);
4508
4766
  const zoomPercent = Math.round(scale * 100);
4509
- return /* @__PURE__ */ jsxRuntime.jsxs(react$1.Dialog, { open: isOpen, onClose, className: "relative", style: { zIndex: 99999 }, children: [
4767
+ return /* @__PURE__ */ jsxRuntime.jsxs(InlineDialog, { open: isOpen, onClose, className: "relative z-[99999]", children: [
4510
4768
  /* @__PURE__ */ jsxRuntime.jsx(
4511
- react$1.DialogBackdrop,
4769
+ InlineDialogBackdrop,
4512
4770
  {
4513
4771
  className: "fixed inset-0 bg-black/90 backdrop-blur-sm",
4514
4772
  style: { zIndex: 99999 }
4515
4773
  }
4516
4774
  ),
4517
4775
  /* @__PURE__ */ jsxRuntime.jsxs(
4518
- react$1.DialogPanel,
4776
+ InlineDialogPanel,
4519
4777
  {
4520
4778
  className: "fixed inset-0 flex flex-col",
4521
4779
  style: { zIndex: 1e5 },
@@ -4664,7 +4922,7 @@ var defaultClassNames = {
4664
4922
  bubble: "bg-primary-600 text-white rounded-2xl rounded-br-sm px-4 py-3 shadow-sm",
4665
4923
  content: "text-sm whitespace-pre-wrap break-words leading-relaxed",
4666
4924
  attachments: "mb-2 w-full",
4667
- 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",
4925
+ actions: "flex items-center gap-1 mt-2",
4668
4926
  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",
4669
4927
  timestamp: "text-xs text-gray-400 dark:text-gray-500 mr-1"
4670
4928
  };
@@ -5063,6 +5321,7 @@ function UserTurnView({
5063
5321
  }
5064
5322
  );
5065
5323
  }
5324
+ init_IotaContext();
5066
5325
  init_useTranslation();
5067
5326
  function toBase64(str) {
5068
5327
  const bytes = new TextEncoder().encode(str);
@@ -6177,6 +6436,8 @@ function FullscreenOverlay({ title, onClose, closeLabel, children }) {
6177
6436
  const panelRef = React.useRef(null);
6178
6437
  const onCloseRef = React.useRef(onClose);
6179
6438
  onCloseRef.current = onClose;
6439
+ useModalLock(true);
6440
+ useFocusTrap(panelRef, true);
6180
6441
  React.useEffect(() => {
6181
6442
  const onKeyDown = (e) => {
6182
6443
  if (e.key === "Escape") {
@@ -6185,7 +6446,6 @@ function FullscreenOverlay({ title, onClose, closeLabel, children }) {
6185
6446
  }
6186
6447
  };
6187
6448
  document.addEventListener("keydown", onKeyDown);
6188
- panelRef.current?.focus();
6189
6449
  return () => document.removeEventListener("keydown", onKeyDown);
6190
6450
  }, []);
6191
6451
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fixed inset-0", style: { zIndex: 99999 }, children: [
@@ -7754,7 +8014,7 @@ var defaultClassNames2 = {
7754
8014
  artifacts: "mb-1 flex flex-wrap gap-2",
7755
8015
  sources: "",
7756
8016
  explanation: "mt-4 border-t border-gray-100 dark:border-gray-700 pt-4",
7757
- 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",
8017
+ actions: "flex items-center gap-1",
7758
8018
  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",
7759
8019
  timestamp: "text-xs text-gray-400 dark:text-gray-500 mr-1"
7760
8020
  };
@@ -7788,6 +8048,7 @@ function AssistantMessage({
7788
8048
  classNames: classNameOverrides,
7789
8049
  onCopy,
7790
8050
  onRegenerate,
8051
+ regenerateModels,
7791
8052
  onSendMessage,
7792
8053
  sendDisabled = false,
7793
8054
  hideAvatar = false,
@@ -7798,6 +8059,8 @@ function AssistantMessage({
7798
8059
  const { t } = useTranslation();
7799
8060
  const [explanationExpanded, setExplanationExpanded] = React.useState(false);
7800
8061
  const [isCopied, setIsCopied] = React.useState(false);
8062
+ const [showRegenPicker, setShowRegenPicker] = React.useState(false);
8063
+ const regenPickerRef = React.useRef(null);
7801
8064
  const copyFeedbackTimeoutRef = React.useRef(
7802
8065
  null
7803
8066
  );
@@ -7850,11 +8113,56 @@ function AssistantMessage({
7850
8113
  console.error("Failed to copy:", err);
7851
8114
  }
7852
8115
  }, [onCopy, turn.content]);
8116
+ const hasRegenChoices = (regenerateModels?.length ?? 0) >= 2;
7853
8117
  const handleRegenerateClick = React.useCallback(async () => {
7854
- if (onRegenerate && turnId) {
7855
- await onRegenerate(turnId);
8118
+ if (!onRegenerate || !turnId) {
8119
+ return;
7856
8120
  }
7857
- }, [onRegenerate, turnId]);
8121
+ if (hasRegenChoices) {
8122
+ setShowRegenPicker((prev) => !prev);
8123
+ return;
8124
+ }
8125
+ await onRegenerate(turnId);
8126
+ }, [onRegenerate, turnId, hasRegenChoices]);
8127
+ const handleRegenerateWithModel = React.useCallback(
8128
+ async (modelId) => {
8129
+ setShowRegenPicker(false);
8130
+ if (onRegenerate && turnId) {
8131
+ await onRegenerate(turnId, modelId);
8132
+ }
8133
+ },
8134
+ [onRegenerate, turnId]
8135
+ );
8136
+ React.useEffect(() => {
8137
+ if (!showRegenPicker) {
8138
+ return;
8139
+ }
8140
+ const handlePointer = (e) => {
8141
+ const root = regenPickerRef.current;
8142
+ if (!root) {
8143
+ return;
8144
+ }
8145
+ const path = typeof e.composedPath === "function" ? e.composedPath() : [];
8146
+ const insideViaPath = path.includes(root);
8147
+ const insideViaContains = root.contains(e.target);
8148
+ if (!insideViaPath && !insideViaContains) {
8149
+ setShowRegenPicker(false);
8150
+ }
8151
+ };
8152
+ const handleKey = (e) => {
8153
+ if (e.key === "Escape") {
8154
+ setShowRegenPicker(false);
8155
+ }
8156
+ };
8157
+ document.addEventListener("mousedown", handlePointer);
8158
+ document.addEventListener("touchstart", handlePointer);
8159
+ document.addEventListener("keydown", handleKey);
8160
+ return () => {
8161
+ document.removeEventListener("mousedown", handlePointer);
8162
+ document.removeEventListener("touchstart", handlePointer);
8163
+ document.removeEventListener("keydown", handleKey);
8164
+ };
8165
+ }, [showRegenPicker]);
7858
8166
  const timestamp = formatRelativeTime(turn.createdAt, t);
7859
8167
  const avatarSlotProps = {
7860
8168
  text: isSystemMessage ? "SYS" : "AI"
@@ -7881,7 +8189,17 @@ function AssistantMessage({
7881
8189
  };
7882
8190
  const actionsSlotProps = {
7883
8191
  onCopy: handleCopyClick,
7884
- onRegenerate: canRegenerate ? handleRegenerateClick : void 0,
8192
+ onRegenerate: canRegenerate ? (model) => {
8193
+ if (!onRegenerate || !turnId) {
8194
+ return;
8195
+ }
8196
+ if (model) {
8197
+ void handleRegenerateWithModel(model);
8198
+ } else {
8199
+ void handleRegenerateClick();
8200
+ }
8201
+ } : void 0,
8202
+ regenerateModels,
7885
8203
  timestamp,
7886
8204
  canCopy: hasContent,
7887
8205
  canRegenerate
@@ -8072,16 +8390,56 @@ function AssistantMessage({
8072
8390
  children: isCopied ? /* @__PURE__ */ jsxRuntime.jsx(react.Check, { size: 14, weight: "bold" }) : /* @__PURE__ */ jsxRuntime.jsx(react.Copy, { size: 14, weight: "regular" })
8073
8391
  }
8074
8392
  ),
8075
- canRegenerate && /* @__PURE__ */ jsxRuntime.jsx(
8076
- "button",
8077
- {
8078
- onClick: handleRegenerateClick,
8079
- className: `cursor-pointer ${classes.actionButton}`,
8080
- "aria-label": t("BiChat.Message.Regenerate"),
8081
- title: t("BiChat.Message.Regenerate"),
8082
- children: /* @__PURE__ */ jsxRuntime.jsx(react.ArrowsClockwise, { size: 14, weight: "regular" })
8083
- }
8084
- )
8393
+ canRegenerate && /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: regenPickerRef, className: "relative inline-flex", children: [
8394
+ /* @__PURE__ */ jsxRuntime.jsx(
8395
+ "button",
8396
+ {
8397
+ onClick: handleRegenerateClick,
8398
+ className: `cursor-pointer ${classes.actionButton} ${showRegenPicker ? "bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-200" : ""}`,
8399
+ "aria-label": t("BiChat.Message.Regenerate"),
8400
+ title: t("BiChat.Message.Regenerate"),
8401
+ "aria-haspopup": hasRegenChoices ? "menu" : void 0,
8402
+ "aria-expanded": hasRegenChoices ? showRegenPicker : void 0,
8403
+ children: /* @__PURE__ */ jsxRuntime.jsx(react.ArrowsClockwise, { size: 14, weight: "regular" })
8404
+ }
8405
+ ),
8406
+ hasRegenChoices && showRegenPicker && /* @__PURE__ */ jsxRuntime.jsx(
8407
+ "div",
8408
+ {
8409
+ role: "menu",
8410
+ "aria-label": t("BiChat.Message.Regenerate"),
8411
+ 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",
8412
+ children: regenerateModels.map((m, i) => {
8413
+ const isFast = i === 0;
8414
+ const Icon = isFast ? react.Lightning : react.Brain;
8415
+ const accent = isFast ? "text-amber-600 dark:text-amber-400" : "text-blue-600 dark:text-blue-400";
8416
+ return /* @__PURE__ */ jsxRuntime.jsxs(
8417
+ "button",
8418
+ {
8419
+ role: "menuitem",
8420
+ type: "button",
8421
+ onClick: () => {
8422
+ void handleRegenerateWithModel(m.id);
8423
+ },
8424
+ 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",
8425
+ children: [
8426
+ /* @__PURE__ */ jsxRuntime.jsx(
8427
+ Icon,
8428
+ {
8429
+ size: 14,
8430
+ weight: "fill",
8431
+ className: accent
8432
+ }
8433
+ ),
8434
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t(m.label) })
8435
+ ]
8436
+ },
8437
+ m.id
8438
+ );
8439
+ })
8440
+ }
8441
+ )
8442
+ ] })
8085
8443
  ] })
8086
8444
  )
8087
8445
  }
@@ -8241,6 +8599,14 @@ function AssistantTurnView({
8241
8599
  }) {
8242
8600
  const { debugMode } = useChatSession();
8243
8601
  const { handleCopy, handleRegenerate, pendingQuestion, sendMessage: sendMessage2, loading } = useChatMessaging();
8602
+ const iotaContext = useIotaContext();
8603
+ const regenerateModels = React.useMemo(() => {
8604
+ const models = iotaContext.extensions?.llm?.models;
8605
+ if (!models || models.length < 2) {
8606
+ return void 0;
8607
+ }
8608
+ return models.map((m) => ({ id: m.id, label: m.label }));
8609
+ }, [iotaContext.extensions?.llm?.models]);
8244
8610
  const assistantTurn = turn.assistantTurn;
8245
8611
  if (!assistantTurn) {
8246
8612
  return null;
@@ -8269,6 +8635,7 @@ function AssistantTurnView({
8269
8635
  classNames,
8270
8636
  onCopy: handleCopy,
8271
8637
  onRegenerate: allowRegenerate ? handleRegenerate : void 0,
8638
+ regenerateModels: allowRegenerate ? regenerateModels : void 0,
8272
8639
  onSendMessage: sendMessage2,
8273
8640
  sendDisabled: loading || isStreaming,
8274
8641
  hideAvatar,
@@ -10569,8 +10936,6 @@ function SessionArtifactList({
10569
10936
  }) })
10570
10937
  ] }, group.type)) });
10571
10938
  }
10572
-
10573
- // ui/src/bichat/components/SessionArtifactPreviewModal.tsx
10574
10939
  init_useTranslation();
10575
10940
 
10576
10941
  // ui/src/bichat/components/SessionArtifactPreview.tsx
@@ -10960,9 +11325,9 @@ function SessionArtifactPreviewModal({
10960
11325
  if (!artifact) {
10961
11326
  return null;
10962
11327
  }
10963
- return /* @__PURE__ */ jsxRuntime.jsxs(react$1.Dialog, { open: isOpen, onClose: handleClose, className: "relative z-50", children: [
10964
- /* @__PURE__ */ jsxRuntime.jsx(react$1.DialogBackdrop, { className: "fixed inset-0 bg-black/50 backdrop-blur-sm" }),
10965
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 overflow-y-auto p-4 lg:p-6", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto flex min-h-full w-full max-w-6xl items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs(react$1.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: [
11328
+ return /* @__PURE__ */ jsxRuntime.jsxs(InlineDialog, { open: isOpen, onClose: handleClose, className: "relative z-50", children: [
11329
+ /* @__PURE__ */ jsxRuntime.jsx(InlineDialogBackdrop, { className: "fixed inset-0 bg-black/50 backdrop-blur-sm" }),
11330
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 overflow-y-auto p-4 lg:p-6", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto flex min-h-full w-full max-w-6xl items-center justify-center", children: /* @__PURE__ */ jsxRuntime.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: [
10966
11331
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3 border-b border-gray-200 px-4 py-3 dark:border-gray-700", children: [
10967
11332
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
10968
11333
  isEditingName ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
@@ -11462,109 +11827,6 @@ function SessionArtifactsPanel({
11462
11827
  }
11463
11828
  );
11464
11829
  }
11465
- var DialogContext = React.createContext(null);
11466
- var FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
11467
- function InlineDialog({ open, onClose, className, children }) {
11468
- const containerRef = React.useRef(null);
11469
- const previousFocusRef = React.useRef(null);
11470
- React.useEffect(() => {
11471
- if (!open) {
11472
- return;
11473
- }
11474
- previousFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
11475
- const container = containerRef.current;
11476
- if (!container) {
11477
- return;
11478
- }
11479
- const getFocusable = () => Array.from(
11480
- container.querySelectorAll(FOCUSABLE_SELECTOR)
11481
- );
11482
- const handler = (e) => {
11483
- if (e.key === "Escape") {
11484
- onClose();
11485
- return;
11486
- }
11487
- if (e.key !== "Tab") {
11488
- return;
11489
- }
11490
- const focusable = getFocusable();
11491
- if (focusable.length === 0) {
11492
- e.preventDefault();
11493
- container.focus();
11494
- return;
11495
- }
11496
- const first = focusable[0];
11497
- const last = focusable[focusable.length - 1];
11498
- if (e.shiftKey && document.activeElement === first) {
11499
- e.preventDefault();
11500
- last.focus();
11501
- } else if (!e.shiftKey && document.activeElement === last) {
11502
- e.preventDefault();
11503
- first.focus();
11504
- }
11505
- };
11506
- (getFocusable()[0] ?? container)?.focus();
11507
- container.addEventListener("keydown", handler);
11508
- return () => {
11509
- container.removeEventListener("keydown", handler);
11510
- previousFocusRef.current?.focus();
11511
- };
11512
- }, [open, onClose]);
11513
- if (!open) {
11514
- return null;
11515
- }
11516
- return /* @__PURE__ */ jsxRuntime.jsx(DialogContext.Provider, { value: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(
11517
- "div",
11518
- {
11519
- ref: containerRef,
11520
- className,
11521
- onClick: onClose,
11522
- tabIndex: -1,
11523
- children
11524
- }
11525
- ) });
11526
- }
11527
- function InlineDialogBackdrop(props) {
11528
- return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", ...props });
11529
- }
11530
- function InlineDialogPanel({
11531
- children,
11532
- onClick,
11533
- ...rest
11534
- }) {
11535
- const ref = React.useRef(null);
11536
- React.useEffect(() => {
11537
- if (!ref.current) {
11538
- return;
11539
- }
11540
- const target = ref.current.querySelector("[data-autofocus]") ?? ref.current.querySelector(
11541
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
11542
- );
11543
- target?.focus();
11544
- }, []);
11545
- return /* @__PURE__ */ jsxRuntime.jsx(
11546
- "div",
11547
- {
11548
- ref,
11549
- role: "dialog",
11550
- "aria-modal": "true",
11551
- onClick: (e) => {
11552
- e.stopPropagation();
11553
- onClick?.(e);
11554
- },
11555
- ...rest,
11556
- children
11557
- }
11558
- );
11559
- }
11560
- function InlineDialogTitle(props) {
11561
- return /* @__PURE__ */ jsxRuntime.jsx("h2", { ...props });
11562
- }
11563
- function InlineDialogDescription(props) {
11564
- return /* @__PURE__ */ jsxRuntime.jsx("p", { ...props });
11565
- }
11566
-
11567
- // ui/src/bichat/components/SessionMembersModal.tsx
11568
11830
  init_useTranslation();
11569
11831
  init_useTranslation();
11570
11832
  function ConfirmModalBase({
@@ -12451,7 +12713,7 @@ function ChatSessionCore({
12451
12713
  "div",
12452
12714
  {
12453
12715
  ref: layoutContainerRef,
12454
- className: "relative flex min-h-0 flex-1 overflow-hidden",
12716
+ className: "relative flex min-h-0 flex-1 overflow-clip",
12455
12717
  children: [
12456
12718
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: showWelcome ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 flex-col overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 items-center justify-center px-4 py-8", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-5xl", children: [
12457
12719
  welcomeSlot || /* @__PURE__ */ jsxRuntime.jsx(WelcomeContent_default, { onPromptSelect: handlePromptSelect, disabled: loading || composerDisabled }),
@@ -15903,61 +16165,6 @@ function useSidebarState() {
15903
16165
  const toggleMobile = React.useCallback(() => setIsMobileOpen((v) => !v), []);
15904
16166
  return { isMobile, isMobileOpen, openMobile, closeMobile, toggleMobile };
15905
16167
  }
15906
- function useFocusTrap(containerRef, isActive, restoreFocusOnDeactivate) {
15907
- React.useEffect(() => {
15908
- if (!isActive || !containerRef.current) {
15909
- return;
15910
- }
15911
- const container = containerRef.current;
15912
- const previouslyFocused = document.activeElement;
15913
- const getFocusableElements = () => {
15914
- const selector = [
15915
- "button:not([disabled])",
15916
- "[href]",
15917
- "input:not([disabled])",
15918
- "select:not([disabled])",
15919
- "textarea:not([disabled])",
15920
- '[tabindex]:not([tabindex="-1"])'
15921
- ].join(", ");
15922
- return Array.from(container.querySelectorAll(selector));
15923
- };
15924
- const focusableElements = getFocusableElements();
15925
- if (focusableElements.length > 0) {
15926
- focusableElements[0].focus();
15927
- }
15928
- const handleTabKey = (e) => {
15929
- if (e.key !== "Tab") {
15930
- return;
15931
- }
15932
- const focusableElements2 = getFocusableElements();
15933
- if (focusableElements2.length === 0) {
15934
- return;
15935
- }
15936
- const firstElement = focusableElements2[0];
15937
- const lastElement = focusableElements2[focusableElements2.length - 1];
15938
- if (e.shiftKey) {
15939
- if (document.activeElement === firstElement) {
15940
- e.preventDefault();
15941
- lastElement.focus();
15942
- }
15943
- } else {
15944
- if (document.activeElement === lastElement) {
15945
- e.preventDefault();
15946
- firstElement.focus();
15947
- }
15948
- }
15949
- };
15950
- container.addEventListener("keydown", handleTabKey);
15951
- return () => {
15952
- container.removeEventListener("keydown", handleTabKey);
15953
- if (restoreFocusOnDeactivate) {
15954
- restoreFocusOnDeactivate.focus();
15955
- } else if (previouslyFocused instanceof HTMLElement) {
15956
- previouslyFocused.focus();
15957
- }
15958
- };
15959
- }, [containerRef, isActive, restoreFocusOnDeactivate]);
15960
- }
15961
16168
 
15962
16169
  // ui/src/bichat/components/BiChatLayout.tsx
15963
16170
  init_useTranslation();
@@ -17207,18 +17414,6 @@ function useActiveRuns(dataSource, options = {}) {
17207
17414
 
17208
17415
  // ui/src/bichat/index.ts
17209
17416
  init_useTranslation();
17210
- function useModalLock(isOpen) {
17211
- React.useEffect(() => {
17212
- if (!isOpen) {
17213
- return;
17214
- }
17215
- const originalOverflow = document.body.style.overflow;
17216
- document.body.style.overflow = "hidden";
17217
- return () => {
17218
- document.body.style.overflow = originalOverflow;
17219
- };
17220
- }, [isOpen]);
17221
- }
17222
17417
  function useImageGallery(options = {}) {
17223
17418
  const { images: initialImages = [], wrap = false, onOpen, onClose, onNavigate } = options;
17224
17419
  const [isOpen, setIsOpen] = React.useState(false);