@iota-uz/sdk 0.4.10 → 0.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,12 +2,12 @@ import React, { createContext, lazy, memo, forwardRef, useState, useRef, useMemo
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import ReactApexChart from 'react-apexcharts';
4
4
  import ApexCharts from 'apexcharts';
5
- import { X, Bug, ArrowUp, ArrowDown, Stack, Paperclip, PaperPlaneRight, CircleNotch, ArrowUUpLeft, PencilSimple, Check, Bookmark, ArrowsClockwise, Archive, Trash, DotsThree, Warning, ArrowClockwise, Image, ImageBroken, CaretLeft, CaretRight, MagnifyingGlass, WarningCircle, CaretDown, Info, CheckCircle, XCircle, Spinner, Copy, FilePdf, FileXls, FileCsv, FileDoc, FileCode, FileText, File, ChartBar, DownloadSimple, Download, ChatCircleDots, PencilSimpleLine, ArrowLeft, PaperPlaneTilt, ArrowRight, Timer, Lightning, Database, Wrench, ClockCounterClockwise, Lightbulb, Package, Plus, ArrowsCounterClockwise, Code, ArrowSquareOut, SpinnerGap, FloppyDisk, Sidebar } from '@phosphor-icons/react';
5
+ import { X, Bug, ArrowUp, ArrowDown, Stack, Paperclip, PaperPlaneRight, CircleNotch, ArrowUUpLeft, PencilSimple, Check, Bookmark, ArrowsClockwise, Archive, Trash, DotsThree, Warning, ArrowClockwise, Image, ImageBroken, CaretLeft, CaretRight, MagnifyingGlass, WarningCircle, CaretDown, Info, CheckCircle, XCircle, Spinner, Copy, FilePdf, FileXls, FileCsv, FileDoc, FileCode, FileText, File, ChartBar, DownloadSimple, Download, ChatCircleDots, PencilSimpleLine, ArrowLeft, PaperPlaneTilt, ArrowRight, Timer, Lightning, Database, Wrench, ClockCounterClockwise, Lightbulb, Package, Plus, Gear, Users, List, CaretLineLeft, CaretLineRight, ArrowsCounterClockwise, Code, ArrowSquareOut, SpinnerGap, FloppyDisk, Sidebar } from '@phosphor-icons/react';
6
6
  import { Prism } from 'react-syntax-highlighter';
7
7
  import { vscDarkPlus, vs } from 'react-syntax-highlighter/dist/esm/styles/prism';
8
8
  import ReactMarkdown from 'react-markdown';
9
9
  import remarkGfm from 'remark-gfm';
10
- import { motion, AnimatePresence, useReducedMotion } from 'framer-motion';
10
+ import { useMotionValue, useTransform, motion, AnimatePresence, useReducedMotion } from 'framer-motion';
11
11
  import { formatDistanceToNow, startOfDay, differenceInDays } from 'date-fns';
12
12
  import { Menu, MenuButton, MenuItems, MenuItem, Dialog, DialogBackdrop, DialogPanel, DialogTitle, Description, Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react';
13
13
  import { createPortal } from 'react-dom';
@@ -7079,6 +7079,19 @@ var SessionItem = memo(
7079
7079
  const [menuAnchor, setMenuAnchor] = useState(null);
7080
7080
  const [isTouch, setIsTouch] = useState(false);
7081
7081
  const { t } = useTranslation();
7082
+ const isDraggingRef = useRef(false);
7083
+ const dragX = useMotionValue(0);
7084
+ const archiveOpacity = useTransform(dragX, [-80, -40, 0], [1, 0.5, 0]);
7085
+ const archiveScale = useTransform(dragX, [-80, -40, 0], [1, 0.8, 0.6]);
7086
+ const canDragArchive = !!onArchive;
7087
+ const handleDragEnd = (_, info) => {
7088
+ if (info.offset.x < -80 && onArchive) {
7089
+ onArchive();
7090
+ }
7091
+ requestAnimationFrame(() => {
7092
+ isDraggingRef.current = false;
7093
+ });
7094
+ };
7082
7095
  useEffect(() => {
7083
7096
  setIsTouch("ontouchend" in document);
7084
7097
  }, []);
@@ -7097,17 +7110,15 @@ var SessionItem = memo(
7097
7110
  const element = itemRef.current;
7098
7111
  if (!element) return;
7099
7112
  const isIPad = /iPad|Macintosh/i.test(navigator.userAgent) && "ontouchend" in document;
7100
- if (isIPad) {
7101
- const handleContextMenu = (e) => {
7102
- e.preventDefault();
7103
- const target = e.currentTarget;
7104
- setMenuAnchor(target.getBoundingClientRect());
7105
- setMenuOpen(true);
7106
- };
7107
- element.addEventListener("contextmenu", handleContextMenu);
7108
- return () => element.removeEventListener("contextmenu", handleContextMenu);
7109
- }
7110
- return void 0;
7113
+ if (!isIPad) return;
7114
+ const handleContextMenu = (e) => {
7115
+ e.preventDefault();
7116
+ const target = e.currentTarget;
7117
+ setMenuAnchor(target.getBoundingClientRect());
7118
+ setMenuOpen(true);
7119
+ };
7120
+ element.addEventListener("contextmenu", handleContextMenu);
7121
+ return () => element.removeEventListener("contextmenu", handleContextMenu);
7111
7122
  }, [itemRef]);
7112
7123
  const contextMenuItems = mode === "archived" ? [
7113
7124
  ...onRestore ? [{
@@ -7158,7 +7169,7 @@ var SessionItem = memo(
7158
7169
  ];
7159
7170
  const hasContextMenu = contextMenuItems.length > 0;
7160
7171
  return /* @__PURE__ */ jsxs(Fragment, { children: [
7161
- /* @__PURE__ */ jsx(
7172
+ /* @__PURE__ */ jsxs(
7162
7173
  motion.div,
7163
7174
  {
7164
7175
  variants: sessionItemVariants,
@@ -7166,165 +7177,196 @@ var SessionItem = memo(
7166
7177
  animate: "animate",
7167
7178
  whileHover: "hover",
7168
7179
  exit: "exit",
7169
- children: /* @__PURE__ */ jsx(
7170
- "div",
7171
- {
7172
- role: "button",
7173
- tabIndex: 0,
7174
- ref: itemRef,
7175
- onClick: () => onSelect(session.id),
7176
- onKeyDown: (e) => {
7177
- if (e.key === "Enter" || e.key === " ") {
7178
- e.preventDefault();
7179
- onSelect(session.id);
7180
- }
7181
- },
7182
- className: `block w-full text-left px-3 py-2 rounded-lg transition-smooth group relative touch-tap cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 ${isActive ? "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"} ${className}`,
7183
- "aria-current": isActive ? "page" : void 0,
7184
- "data-session-item": true,
7185
- "data-testid": `${testIdPrefix}-session-${session.id}`,
7186
- ...longPressHandlers,
7187
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
7188
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 min-w-0 flex-1", children: /* @__PURE__ */ jsx(
7189
- MemoizedEditableText,
7180
+ className: "relative overflow-hidden rounded-lg",
7181
+ children: [
7182
+ canDragArchive && /* @__PURE__ */ jsx(
7183
+ motion.div,
7184
+ {
7185
+ className: "absolute inset-y-0 right-0 w-20 flex items-center justify-center bg-gray-500 dark:bg-gray-600 rounded-r-lg",
7186
+ style: { opacity: archiveOpacity, scale: archiveScale },
7187
+ "aria-hidden": "true",
7188
+ children: /* @__PURE__ */ jsx(Archive, { size: 20, className: "text-white" })
7189
+ }
7190
+ ),
7191
+ /* @__PURE__ */ jsx(
7192
+ motion.div,
7193
+ {
7194
+ drag: canDragArchive ? "x" : false,
7195
+ dragDirectionLock: true,
7196
+ dragConstraints: { left: -100, right: 0 },
7197
+ dragElastic: { left: 0.2, right: 0.5 },
7198
+ dragSnapToOrigin: true,
7199
+ style: { x: canDragArchive ? dragX : void 0 },
7200
+ onDragStart: () => {
7201
+ isDraggingRef.current = true;
7202
+ },
7203
+ onDragEnd: canDragArchive ? handleDragEnd : void 0,
7204
+ className: "relative",
7205
+ children: /* @__PURE__ */ jsx(
7206
+ "div",
7190
7207
  {
7191
- ref: editableTitleRef,
7192
- value: displayTitle,
7193
- onSave: (newTitle) => onRename?.(newTitle),
7194
- isLoading: isTitleGenerating
7195
- }
7196
- ) }),
7197
- !isTouch && hasContextMenu && /* @__PURE__ */ jsxs(Menu, { children: [
7198
- /* @__PURE__ */ jsx(
7199
- MenuButton,
7200
- {
7201
- onClick: (e) => {
7208
+ role: "button",
7209
+ tabIndex: 0,
7210
+ ref: itemRef,
7211
+ onClick: () => {
7212
+ if (isDraggingRef.current) return;
7213
+ onSelect(session.id);
7214
+ },
7215
+ onKeyDown: (e) => {
7216
+ if (e.key === "Enter" || e.key === " ") {
7202
7217
  e.preventDefault();
7203
- e.stopPropagation();
7204
- },
7205
- className: "opacity-0 group-hover:opacity-100 p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700 transition-smooth flex-shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50",
7206
- "aria-label": t("BiChat.Sidebar.ChatOptions"),
7207
- "data-testid": `${testIdPrefix}-session-options-${session.id}`,
7208
- children: /* @__PURE__ */ jsx(DotsThree, { size: 16, className: "w-4 h-4", weight: "bold" })
7209
- }
7210
- ),
7211
- /* @__PURE__ */ jsxs(
7212
- MenuItems,
7213
- {
7214
- anchor: "bottom start",
7215
- className: "w-52 bg-white/95 dark:bg-gray-900/95 backdrop-blur rounded-xl shadow-xl border border-gray-200 dark:border-gray-700 z-30 [--anchor-gap:8px] mt-1 p-2 space-y-1",
7216
- children: [
7217
- mode !== "archived" && onPin && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxs(
7218
- "button",
7219
- {
7220
- onClick: (e) => {
7221
- e.preventDefault();
7222
- e.stopPropagation();
7223
- onPin();
7224
- },
7225
- className: `cursor-pointer flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 transition-smooth ${focus ? "bg-gray-100 dark:bg-gray-800/70 ring-1 ring-gray-200/80 dark:ring-gray-700/80" : "hover:bg-gray-50 dark:hover:bg-gray-800"}`,
7226
- "aria-label": session.pinned ? t("BiChat.Sidebar.UnpinChat") : t("BiChat.Sidebar.PinChat"),
7227
- "data-testid": `${testIdPrefix}-session-pin-${session.id}`,
7228
- children: [
7229
- session.pinned ? /* @__PURE__ */ jsx(Check, { size: 16, className: "w-4 h-4" }) : /* @__PURE__ */ jsx(Bookmark, { size: 16, className: "w-4 h-4" }),
7230
- session.pinned ? t("BiChat.Sidebar.UnpinChat") : t("BiChat.Sidebar.PinChat")
7231
- ]
7232
- }
7233
- ) }),
7234
- onRename && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus, close }) => /* @__PURE__ */ jsxs(
7235
- "button",
7236
- {
7237
- onClick: (e) => {
7238
- e.preventDefault();
7239
- e.stopPropagation();
7240
- editableTitleRef.current?.startEditing();
7241
- close();
7242
- },
7243
- className: `cursor-pointer flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 transition-smooth ${focus ? "bg-gray-100 dark:bg-gray-800/70 ring-1 ring-gray-200/80 dark:ring-gray-700/80" : "hover:bg-gray-50 dark:hover:bg-gray-800"}`,
7244
- "aria-label": t("BiChat.Sidebar.RenameChat"),
7245
- "data-testid": `${testIdPrefix}-session-rename-${session.id}`,
7246
- children: [
7247
- /* @__PURE__ */ jsx(PencilSimple, { size: 16, className: "w-4 h-4" }),
7248
- t("BiChat.Sidebar.RenameChat")
7249
- ]
7250
- }
7251
- ) }),
7252
- mode !== "archived" && onRegenerateTitle && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus, close }) => /* @__PURE__ */ jsxs(
7253
- "button",
7254
- {
7255
- onClick: (e) => {
7256
- e.preventDefault();
7257
- e.stopPropagation();
7258
- onRegenerateTitle();
7259
- close();
7260
- },
7261
- className: `cursor-pointer flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 transition-smooth ${focus ? "bg-gray-100 dark:bg-gray-800/70 ring-1 ring-gray-200/80 dark:ring-gray-700/80" : "hover:bg-gray-50 dark:hover:bg-gray-800"}`,
7262
- "aria-label": t("BiChat.Sidebar.RegenerateTitle"),
7263
- "data-testid": `${testIdPrefix}-session-regenerate-${session.id}`,
7264
- children: [
7265
- /* @__PURE__ */ jsx(ArrowsClockwise, { size: 16, className: "w-4 h-4" }),
7266
- t("BiChat.Sidebar.RegenerateTitle")
7267
- ]
7268
- }
7269
- ) }),
7270
- mode === "archived" && onRestore && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxs(
7271
- "button",
7272
- {
7273
- onClick: (e) => {
7274
- e.preventDefault();
7275
- e.stopPropagation();
7276
- onRestore();
7277
- },
7278
- className: `cursor-pointer flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-smooth ${focus ? "text-green-700 dark:text-green-300 bg-green-50 dark:bg-green-900/20 ring-1 ring-green-200/70 dark:ring-green-500/30" : "text-green-700 dark:text-green-300 hover:bg-green-50/70 dark:hover:bg-green-900/10"}`,
7279
- "aria-label": t("BiChat.Archived.RestoreButton"),
7280
- "data-testid": `${testIdPrefix}-session-restore-${session.id}`,
7281
- children: [
7282
- /* @__PURE__ */ jsx(ArrowUUpLeft, { size: 16, className: "w-4 h-4" }),
7283
- t("BiChat.Archived.RestoreButton")
7284
- ]
7285
- }
7286
- ) }),
7287
- mode !== "archived" && onArchive && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxs(
7288
- "button",
7218
+ onSelect(session.id);
7219
+ }
7220
+ },
7221
+ className: `block w-full text-left px-3 py-2 rounded-lg transition-smooth group relative touch-tap cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 ${isActive ? "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"} ${className}`,
7222
+ "aria-current": isActive ? "page" : void 0,
7223
+ "data-session-item": true,
7224
+ "data-testid": `${testIdPrefix}-session-${session.id}`,
7225
+ ...longPressHandlers,
7226
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
7227
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 min-w-0 flex-1", children: /* @__PURE__ */ jsx(
7228
+ MemoizedEditableText,
7229
+ {
7230
+ ref: editableTitleRef,
7231
+ value: displayTitle,
7232
+ onSave: (newTitle) => onRename?.(newTitle),
7233
+ isLoading: isTitleGenerating
7234
+ }
7235
+ ) }),
7236
+ !isTouch && hasContextMenu && /* @__PURE__ */ jsxs(Menu, { children: [
7237
+ /* @__PURE__ */ jsx(
7238
+ MenuButton,
7289
7239
  {
7290
7240
  onClick: (e) => {
7291
7241
  e.preventDefault();
7292
7242
  e.stopPropagation();
7293
- onArchive();
7294
7243
  },
7295
- className: `cursor-pointer flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-smooth ${focus ? "text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/20 ring-1 ring-amber-200/70 dark:ring-amber-500/30" : "text-amber-600 dark:text-amber-400 hover:bg-amber-50/70 dark:hover:bg-amber-900/10"}`,
7296
- "aria-label": t("BiChat.Sidebar.ArchiveChat"),
7297
- "data-testid": `${testIdPrefix}-session-archive-${session.id}`,
7298
- children: [
7299
- /* @__PURE__ */ jsx(Archive, { size: 16, className: "w-4 h-4" }),
7300
- t("BiChat.Sidebar.ArchiveChat")
7301
- ]
7244
+ className: "opacity-0 group-hover:opacity-100 p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700 transition-smooth flex-shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50",
7245
+ "aria-label": t("BiChat.Sidebar.ChatOptions"),
7246
+ "data-testid": `${testIdPrefix}-session-options-${session.id}`,
7247
+ children: /* @__PURE__ */ jsx(DotsThree, { size: 16, className: "w-4 h-4", weight: "bold" })
7302
7248
  }
7303
- ) }),
7304
- onDelete && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxs(
7305
- "button",
7249
+ ),
7250
+ /* @__PURE__ */ jsxs(
7251
+ MenuItems,
7306
7252
  {
7307
- onClick: (e) => {
7308
- e.preventDefault();
7309
- e.stopPropagation();
7310
- onDelete();
7311
- },
7312
- className: `cursor-pointer flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-smooth ${focus ? "text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20 ring-1 ring-red-200/70 dark:ring-red-500/30" : "text-red-600 dark:text-red-400 hover:bg-red-50/70 dark:hover:bg-red-900/10"}`,
7313
- "aria-label": t("BiChat.Sidebar.DeleteChat"),
7314
- "data-testid": `${testIdPrefix}-session-delete-${session.id}`,
7253
+ anchor: "bottom start",
7254
+ className: "w-52 bg-white/95 dark:bg-gray-900/95 backdrop-blur rounded-xl shadow-xl border border-gray-200 dark:border-gray-700 z-30 [--anchor-gap:8px] mt-1 p-2 space-y-1",
7315
7255
  children: [
7316
- /* @__PURE__ */ jsx(Trash, { size: 16, className: "w-4 h-4" }),
7317
- t("BiChat.Sidebar.DeleteChat")
7256
+ mode !== "archived" && onPin && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxs(
7257
+ "button",
7258
+ {
7259
+ onClick: (e) => {
7260
+ e.preventDefault();
7261
+ e.stopPropagation();
7262
+ onPin();
7263
+ },
7264
+ className: `cursor-pointer flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 transition-smooth ${focus ? "bg-gray-100 dark:bg-gray-800/70 ring-1 ring-gray-200/80 dark:ring-gray-700/80" : "hover:bg-gray-50 dark:hover:bg-gray-800"}`,
7265
+ "aria-label": session.pinned ? t("BiChat.Sidebar.UnpinChat") : t("BiChat.Sidebar.PinChat"),
7266
+ "data-testid": `${testIdPrefix}-session-pin-${session.id}`,
7267
+ children: [
7268
+ session.pinned ? /* @__PURE__ */ jsx(Check, { size: 16, className: "w-4 h-4" }) : /* @__PURE__ */ jsx(Bookmark, { size: 16, className: "w-4 h-4" }),
7269
+ session.pinned ? t("BiChat.Sidebar.UnpinChat") : t("BiChat.Sidebar.PinChat")
7270
+ ]
7271
+ }
7272
+ ) }),
7273
+ onRename && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus, close }) => /* @__PURE__ */ jsxs(
7274
+ "button",
7275
+ {
7276
+ onClick: (e) => {
7277
+ e.preventDefault();
7278
+ e.stopPropagation();
7279
+ editableTitleRef.current?.startEditing();
7280
+ close();
7281
+ },
7282
+ className: `cursor-pointer flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 transition-smooth ${focus ? "bg-gray-100 dark:bg-gray-800/70 ring-1 ring-gray-200/80 dark:ring-gray-700/80" : "hover:bg-gray-50 dark:hover:bg-gray-800"}`,
7283
+ "aria-label": t("BiChat.Sidebar.RenameChat"),
7284
+ "data-testid": `${testIdPrefix}-session-rename-${session.id}`,
7285
+ children: [
7286
+ /* @__PURE__ */ jsx(PencilSimple, { size: 16, className: "w-4 h-4" }),
7287
+ t("BiChat.Sidebar.RenameChat")
7288
+ ]
7289
+ }
7290
+ ) }),
7291
+ mode !== "archived" && onRegenerateTitle && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus, close }) => /* @__PURE__ */ jsxs(
7292
+ "button",
7293
+ {
7294
+ onClick: (e) => {
7295
+ e.preventDefault();
7296
+ e.stopPropagation();
7297
+ onRegenerateTitle();
7298
+ close();
7299
+ },
7300
+ className: `cursor-pointer flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 transition-smooth ${focus ? "bg-gray-100 dark:bg-gray-800/70 ring-1 ring-gray-200/80 dark:ring-gray-700/80" : "hover:bg-gray-50 dark:hover:bg-gray-800"}`,
7301
+ "aria-label": t("BiChat.Sidebar.RegenerateTitle"),
7302
+ "data-testid": `${testIdPrefix}-session-regenerate-${session.id}`,
7303
+ children: [
7304
+ /* @__PURE__ */ jsx(ArrowsClockwise, { size: 16, className: "w-4 h-4" }),
7305
+ t("BiChat.Sidebar.RegenerateTitle")
7306
+ ]
7307
+ }
7308
+ ) }),
7309
+ mode === "archived" && onRestore && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxs(
7310
+ "button",
7311
+ {
7312
+ onClick: (e) => {
7313
+ e.preventDefault();
7314
+ e.stopPropagation();
7315
+ onRestore();
7316
+ },
7317
+ className: `cursor-pointer flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-smooth ${focus ? "text-green-700 dark:text-green-300 bg-green-50 dark:bg-green-900/20 ring-1 ring-green-200/70 dark:ring-green-500/30" : "text-green-700 dark:text-green-300 hover:bg-green-50/70 dark:hover:bg-green-900/10"}`,
7318
+ "aria-label": t("BiChat.Archived.RestoreButton"),
7319
+ "data-testid": `${testIdPrefix}-session-restore-${session.id}`,
7320
+ children: [
7321
+ /* @__PURE__ */ jsx(ArrowUUpLeft, { size: 16, className: "w-4 h-4" }),
7322
+ t("BiChat.Archived.RestoreButton")
7323
+ ]
7324
+ }
7325
+ ) }),
7326
+ mode !== "archived" && onArchive && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxs(
7327
+ "button",
7328
+ {
7329
+ onClick: (e) => {
7330
+ e.preventDefault();
7331
+ e.stopPropagation();
7332
+ onArchive();
7333
+ },
7334
+ className: `cursor-pointer flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-smooth ${focus ? "text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/20 ring-1 ring-amber-200/70 dark:ring-amber-500/30" : "text-amber-600 dark:text-amber-400 hover:bg-amber-50/70 dark:hover:bg-amber-900/10"}`,
7335
+ "aria-label": t("BiChat.Sidebar.ArchiveChat"),
7336
+ "data-testid": `${testIdPrefix}-session-archive-${session.id}`,
7337
+ children: [
7338
+ /* @__PURE__ */ jsx(Archive, { size: 16, className: "w-4 h-4" }),
7339
+ t("BiChat.Sidebar.ArchiveChat")
7340
+ ]
7341
+ }
7342
+ ) }),
7343
+ onDelete && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxs(
7344
+ "button",
7345
+ {
7346
+ onClick: (e) => {
7347
+ e.preventDefault();
7348
+ e.stopPropagation();
7349
+ onDelete();
7350
+ },
7351
+ className: `cursor-pointer flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-smooth ${focus ? "text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20 ring-1 ring-red-200/70 dark:ring-red-500/30" : "text-red-600 dark:text-red-400 hover:bg-red-50/70 dark:hover:bg-red-900/10"}`,
7352
+ "aria-label": t("BiChat.Sidebar.DeleteChat"),
7353
+ "data-testid": `${testIdPrefix}-session-delete-${session.id}`,
7354
+ children: [
7355
+ /* @__PURE__ */ jsx(Trash, { size: 16, className: "w-4 h-4" }),
7356
+ t("BiChat.Sidebar.DeleteChat")
7357
+ ]
7358
+ }
7359
+ ) })
7318
7360
  ]
7319
7361
  }
7320
- ) })
7321
- ]
7322
- }
7323
- )
7324
- ] })
7325
- ] })
7326
- }
7327
- )
7362
+ )
7363
+ ] })
7364
+ ] })
7365
+ }
7366
+ )
7367
+ }
7368
+ )
7369
+ ]
7328
7370
  }
7329
7371
  ),
7330
7372
  /* @__PURE__ */ jsx(
@@ -7844,6 +7886,113 @@ function groupSessionsByDate(sessions, t) {
7844
7886
  });
7845
7887
  return groups;
7846
7888
  }
7889
+
7890
+ // ui/src/bichat/utils/errorDisplay.ts
7891
+ function isPermissionDeniedError(error) {
7892
+ if (!error) return false;
7893
+ if (error instanceof Error) {
7894
+ const msg = error.message.toLowerCase();
7895
+ if (msg.includes("forbidden") || msg.includes("permission denied")) return true;
7896
+ }
7897
+ if (typeof error === "object" && error !== null) {
7898
+ const obj = error;
7899
+ if (obj.code === "forbidden" || obj.code === 403) return true;
7900
+ if (obj.status === 403) return true;
7901
+ if (obj.statusCode === 403) return true;
7902
+ if (typeof obj.response === "object" && obj.response !== null) {
7903
+ const resp = obj.response;
7904
+ if (resp.status === 403) return true;
7905
+ }
7906
+ }
7907
+ if (typeof error === "string") {
7908
+ const lower = error.toLowerCase();
7909
+ if (lower.includes("forbidden") || lower.includes("permission denied")) return true;
7910
+ }
7911
+ return false;
7912
+ }
7913
+ function toErrorDisplay(error, fallbackTitle) {
7914
+ const permDenied = isPermissionDeniedError(error);
7915
+ let title = fallbackTitle;
7916
+ let description = "";
7917
+ if (error instanceof Error) {
7918
+ description = error.message;
7919
+ } else if (typeof error === "object" && error !== null) {
7920
+ const obj = error;
7921
+ if (typeof obj.message === "string" && obj.message) description = obj.message;
7922
+ if (typeof obj.title === "string" && obj.title) title = obj.title;
7923
+ if (typeof obj.detail === "string" && obj.detail) description = obj.detail;
7924
+ } else if (typeof error === "string") {
7925
+ description = error;
7926
+ }
7927
+ if (permDenied && !description) {
7928
+ description = "Your account does not have permission for this action.";
7929
+ }
7930
+ return { title, description, isPermissionDenied: permDenied };
7931
+ }
7932
+ function ErrorAlert({ error }) {
7933
+ const amber = error.isPermissionDenied;
7934
+ return /* @__PURE__ */ jsxs(
7935
+ "div",
7936
+ {
7937
+ className: `mx-2 mt-4 p-3 border rounded-xl cursor-default ${amber ? "bg-amber-50 dark:bg-amber-900/20 border-amber-200 dark:border-amber-800" : "bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800"}`,
7938
+ children: [
7939
+ /* @__PURE__ */ jsx(
7940
+ "p",
7941
+ {
7942
+ className: `text-xs font-medium ${amber ? "text-amber-700 dark:text-amber-300" : "text-red-600 dark:text-red-400"}`,
7943
+ children: error.title
7944
+ }
7945
+ ),
7946
+ error.description && /* @__PURE__ */ jsx(
7947
+ "p",
7948
+ {
7949
+ className: `mt-1 text-xs ${amber ? "text-amber-600 dark:text-amber-400" : "text-red-500 dark:text-red-300"}`,
7950
+ children: error.description
7951
+ }
7952
+ )
7953
+ ]
7954
+ }
7955
+ );
7956
+ }
7957
+ var COLLAPSE_STORAGE_KEY = "bichat-sidebar-collapsed";
7958
+ function useSidebarCollapse() {
7959
+ const [isCollapsed, setIsCollapsed] = useState(() => {
7960
+ try {
7961
+ return localStorage.getItem(COLLAPSE_STORAGE_KEY) === "true";
7962
+ } catch {
7963
+ return false;
7964
+ }
7965
+ });
7966
+ const isCollapsedRef = useRef(isCollapsed);
7967
+ useEffect(() => {
7968
+ isCollapsedRef.current = isCollapsed;
7969
+ }, [isCollapsed]);
7970
+ const toggle = useCallback(() => {
7971
+ setIsCollapsed((prev) => {
7972
+ const next = !prev;
7973
+ try {
7974
+ localStorage.setItem(COLLAPSE_STORAGE_KEY, String(next));
7975
+ } catch {
7976
+ }
7977
+ return next;
7978
+ });
7979
+ }, []);
7980
+ const expand = useCallback(() => {
7981
+ setIsCollapsed(false);
7982
+ try {
7983
+ localStorage.setItem(COLLAPSE_STORAGE_KEY, "false");
7984
+ } catch {
7985
+ }
7986
+ }, []);
7987
+ const collapse = useCallback(() => {
7988
+ setIsCollapsed(true);
7989
+ try {
7990
+ localStorage.setItem(COLLAPSE_STORAGE_KEY, "true");
7991
+ } catch {
7992
+ }
7993
+ }, []);
7994
+ return { isCollapsed, isCollapsedRef, toggle, expand, collapse };
7995
+ }
7847
7996
  function Sidebar2({
7848
7997
  dataSource,
7849
7998
  onSessionSelect,
@@ -7862,11 +8011,64 @@ function Sidebar2({
7862
8011
  const toast = useToast();
7863
8012
  const shouldReduceMotion = useReducedMotion();
7864
8013
  const sessionListRef = useRef(null);
8014
+ const searchContainerRef = useRef(null);
8015
+ const { isCollapsed, isCollapsedRef, toggle, expand, collapse } = useSidebarCollapse();
8016
+ const collapsible = !onClose;
8017
+ const handleSidebarClick = useCallback(
8018
+ (e) => {
8019
+ if (!collapsible) return;
8020
+ const interactive = 'a, button, input, summary, [role="button"]';
8021
+ if (e.target.closest(interactive)) return;
8022
+ toggle();
8023
+ },
8024
+ [collapsible, toggle]
8025
+ );
8026
+ const focusSearch = useCallback(() => {
8027
+ if (!collapsible) return;
8028
+ if (isCollapsedRef.current) {
8029
+ expand();
8030
+ setTimeout(() => {
8031
+ searchContainerRef.current?.querySelector("input")?.focus();
8032
+ }, 250);
8033
+ } else {
8034
+ searchContainerRef.current?.querySelector("input")?.focus();
8035
+ }
8036
+ }, [collapsible, expand, isCollapsedRef]);
8037
+ useEffect(() => {
8038
+ if (!collapsible) return;
8039
+ const handleKeyDown = (e) => {
8040
+ const isMod = e.metaKey || e.ctrlKey;
8041
+ if (isMod && e.key === "b") {
8042
+ e.preventDefault();
8043
+ toggle();
8044
+ }
8045
+ if (isMod && e.key === "k") {
8046
+ e.preventDefault();
8047
+ focusSearch();
8048
+ }
8049
+ };
8050
+ document.addEventListener("keydown", handleKeyDown);
8051
+ return () => document.removeEventListener("keydown", handleKeyDown);
8052
+ }, [collapsible, toggle, focusSearch]);
8053
+ useEffect(() => {
8054
+ if (!collapsible) return;
8055
+ const handler = (e) => {
8056
+ const detail = e.detail;
8057
+ if (detail?.expanded) {
8058
+ collapse();
8059
+ }
8060
+ };
8061
+ window.addEventListener("bichat:artifacts-panel-expanded", handler);
8062
+ return () => window.removeEventListener("bichat:artifacts-panel-expanded", handler);
8063
+ }, [collapsible, collapse]);
8064
+ const showCollapsed = collapsible && isCollapsed;
7865
8065
  const [activeTab, setActiveTab] = useState("my-chats");
7866
8066
  const [searchQuery, setSearchQuery] = useState("");
7867
8067
  const [sessions, setSessions] = useState([]);
7868
8068
  const [loading, setLoading] = useState(true);
7869
- const [error, setError] = useState(null);
8069
+ const [loadError, setLoadError] = useState(null);
8070
+ const [actionError, setActionError] = useState(null);
8071
+ const accessDenied = loadError?.isPermissionDenied === true;
7870
8072
  const [refreshKey, setRefreshKey] = useState(0);
7871
8073
  const [showConfirm, setShowConfirm] = useState(false);
7872
8074
  const [sessionToArchive, setSessionToArchive] = useState(null);
@@ -7880,12 +8082,13 @@ function Sidebar2({
7880
8082
  const fetchSessions = useCallback(async () => {
7881
8083
  try {
7882
8084
  setLoading(true);
7883
- setError(null);
8085
+ setLoadError(null);
8086
+ setActionError(null);
7884
8087
  const result = await dataSource.listSessions({ limit: 50 });
7885
8088
  setSessions(result.sessions);
7886
8089
  } catch (err) {
7887
8090
  console.error("Failed to load sessions:", err);
7888
- setError(t("BiChat.Sidebar.FailedToLoadSessions"));
8091
+ setLoadError(toErrorDisplay(err, t("BiChat.Sidebar.FailedToLoadSessions")));
7889
8092
  } finally {
7890
8093
  setLoading(false);
7891
8094
  }
@@ -7930,7 +8133,9 @@ function Sidebar2({
7930
8133
  }
7931
8134
  } catch (err) {
7932
8135
  console.error("Failed to archive session:", err);
7933
- toast.error(t("BiChat.Sidebar.FailedToArchiveChat"));
8136
+ const display = toErrorDisplay(err, t("BiChat.Sidebar.FailedToArchiveChat"));
8137
+ setActionError(display);
8138
+ toast.error(display.title);
7934
8139
  } finally {
7935
8140
  setShowConfirm(false);
7936
8141
  setSessionToArchive(null);
@@ -7946,7 +8151,9 @@ function Sidebar2({
7946
8151
  setRefreshKey((k) => k + 1);
7947
8152
  } catch (err) {
7948
8153
  console.error("Failed to toggle pin:", err);
7949
- toast.error(t("BiChat.Sidebar.FailedToTogglePin"));
8154
+ const display = toErrorDisplay(err, t("BiChat.Sidebar.FailedToTogglePin"));
8155
+ setActionError(display);
8156
+ toast.error(display.title);
7950
8157
  }
7951
8158
  };
7952
8159
  const handleRenameSession = async (sessionId, newTitle) => {
@@ -7956,7 +8163,9 @@ function Sidebar2({
7956
8163
  setRefreshKey((k) => k + 1);
7957
8164
  } catch (err) {
7958
8165
  console.error("Failed to update session title:", err);
7959
- toast.error(t("BiChat.Sidebar.FailedToRenameChat"));
8166
+ const display = toErrorDisplay(err, t("BiChat.Sidebar.FailedToRenameChat"));
8167
+ setActionError(display);
8168
+ toast.error(display.title);
7960
8169
  }
7961
8170
  };
7962
8171
  const handleRegenerateTitle = async (sessionId) => {
@@ -7966,7 +8175,9 @@ function Sidebar2({
7966
8175
  setRefreshKey((k) => k + 1);
7967
8176
  } catch (err) {
7968
8177
  console.error("Failed to regenerate title:", err);
7969
- toast.error(t("BiChat.Sidebar.FailedToRegenerateTitle"));
8178
+ const display = toErrorDisplay(err, t("BiChat.Sidebar.FailedToRegenerateTitle"));
8179
+ setActionError(display);
8180
+ toast.error(display.title);
7970
8181
  }
7971
8182
  };
7972
8183
  const filteredSessions = useMemo(() => {
@@ -8029,176 +8240,311 @@ function Sidebar2({
8029
8240
  /* @__PURE__ */ jsxs(
8030
8241
  "aside",
8031
8242
  {
8032
- className: `w-64 bg-surface-300 dark:bg-gray-900 border-r border-gray-200 dark:border-gray-700 h-full min-h-0 flex flex-col overflow-hidden ${className}`,
8243
+ onClick: collapsible ? handleSidebarClick : void 0,
8244
+ className: `relative bg-surface-300 dark:bg-gray-900 border-r border-gray-200 dark:border-gray-700 h-full min-h-0 flex flex-col overflow-hidden transition-[width] duration-300 ease-in-out ${showCollapsed ? "w-16 cursor-e-resize" : collapsible ? "w-64 cursor-w-resize" : "w-64"} ${className}`,
8245
+ style: { willChange: "width" },
8033
8246
  role: "navigation",
8034
8247
  "aria-label": t("BiChat.Sidebar.ChatSessions"),
8035
8248
  children: [
8036
- (headerSlot || onClose) && /* @__PURE__ */ jsxs("div", { className: "p-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between", children: [
8037
- headerSlot,
8038
- onClose && /* @__PURE__ */ jsx(
8039
- motion.button,
8040
- {
8041
- onClick: onClose,
8042
- className: "cursor-pointer p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-smooth text-gray-600 dark:text-gray-400",
8043
- title: t("BiChat.Sidebar.CloseSidebar"),
8044
- "aria-label": t("BiChat.Sidebar.CloseSidebar"),
8045
- whileHover: "hover",
8046
- whileTap: "tap",
8047
- variants: buttonVariants,
8048
- children: /* @__PURE__ */ jsx(X, { size: 20, className: "w-5 h-5" })
8049
- }
8050
- )
8051
- ] }),
8052
- showAllChatsTab && /* @__PURE__ */ jsx(
8053
- TabBar_default,
8249
+ collapsible && /* @__PURE__ */ jsx(
8250
+ "div",
8054
8251
  {
8055
- tabs,
8056
- activeTab,
8057
- onTabChange: (id) => setActiveTab(id)
8252
+ className: `absolute inset-x-0 top-0 bottom-0 z-10 flex flex-col items-center pt-3 gap-3 transition-opacity ${showCollapsed ? "opacity-100 duration-150 delay-100" : "opacity-0 pointer-events-none duration-100"}`,
8253
+ children: /* @__PURE__ */ jsxs("div", { className: "group/tooltip relative", children: [
8254
+ /* @__PURE__ */ jsx(
8255
+ motion.button,
8256
+ {
8257
+ onClick: (e) => {
8258
+ e.stopPropagation();
8259
+ onNewChat();
8260
+ },
8261
+ disabled: creating || loading || accessDenied,
8262
+ className: "w-10 h-10 rounded-lg bg-primary-600 hover:bg-primary-700 active:bg-primary-800 text-white shadow-sm flex items-center justify-center disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer transition-colors focus-visible:ring-2 focus-visible:ring-primary-400/50",
8263
+ title: t("BiChat.Chat.NewChat"),
8264
+ "aria-label": t("BiChat.Sidebar.CreateNewChat"),
8265
+ whileTap: { scale: 0.95 },
8266
+ children: creating ? /* @__PURE__ */ jsx("div", { className: "w-4 h-4 border-2 border-white/50 border-t-transparent rounded-full animate-spin" }) : /* @__PURE__ */ jsx(Plus, { size: 18, weight: "bold" })
8267
+ }
8268
+ ),
8269
+ /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute left-full ml-2 top-1/2 -translate-y-1/2 rounded-md bg-gray-900 dark:bg-gray-100 px-2 py-1 text-xs font-medium text-white dark:text-gray-900 opacity-0 group-hover/tooltip:opacity-100 transition-opacity whitespace-nowrap shadow-lg", children: t("BiChat.Chat.NewChat") })
8270
+ ] })
8058
8271
  }
8059
8272
  ),
8060
- activeTab === "all-chats" && showAllChatsTab ? /* @__PURE__ */ jsx(
8061
- AllChatsList,
8273
+ /* @__PURE__ */ jsxs(
8274
+ "div",
8062
8275
  {
8063
- dataSource,
8064
- onSessionSelect,
8065
- activeSessionId
8066
- }
8067
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
8068
- /* @__PURE__ */ jsx("div", { className: "mt-3 px-4", children: /* @__PURE__ */ jsx(
8069
- SearchInput_default,
8070
- {
8071
- value: searchQuery,
8072
- onChange: setSearchQuery,
8073
- placeholder: t("BiChat.Sidebar.SearchChats")
8074
- }
8075
- ) }),
8076
- /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx(
8077
- motion.button,
8078
- {
8079
- onClick: onNewChat,
8080
- disabled: creating || loading,
8081
- className: "cursor-pointer w-full px-4 py-3 bg-primary-600 dark:bg-primary-700 text-white rounded-lg hover:bg-primary-700 dark:hover:bg-primary-800 transition-smooth font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center",
8082
- title: t("BiChat.Chat.NewChat"),
8083
- "aria-label": t("BiChat.Sidebar.CreateNewChat"),
8084
- whileHover: shouldReduceMotion ? {} : { scale: 1.02 },
8085
- whileTap: shouldReduceMotion ? {} : { scale: 0.95 },
8086
- children: creating ? /* @__PURE__ */ jsx(LoadingSpinner_default, { variant: "spinner", size: "sm" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
8087
- /* @__PURE__ */ jsx(Plus, { size: 20, className: "w-5 h-5" }),
8088
- /* @__PURE__ */ jsx("span", { className: "ml-2", children: t("BiChat.Chat.NewChat") })
8089
- ] })
8090
- }
8091
- ) }),
8092
- onArchivedView && /* @__PURE__ */ jsx("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsxs(
8093
- "button",
8094
- {
8095
- onClick: onArchivedView,
8096
- className: "cursor-pointer flex items-center gap-2 px-3 py-2 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 transition-smooth text-sm font-medium w-full",
8097
- title: t("BiChat.Sidebar.ArchivedChats"),
8098
- children: [
8099
- /* @__PURE__ */ jsx(Archive, { size: 18, className: "w-4.5 h-4.5" }),
8100
- /* @__PURE__ */ jsx("span", { children: t("BiChat.Sidebar.ArchivedChats") })
8101
- ]
8102
- }
8103
- ) }),
8104
- /* @__PURE__ */ jsxs(
8105
- "nav",
8106
- {
8107
- ref: sessionListRef,
8108
- className: "flex-1 overflow-y-auto px-2 pb-4 hide-scrollbar",
8109
- "aria-label": t("BiChat.Sidebar.ChatHistory"),
8110
- onKeyDown: handleSessionListKeyDown,
8111
- children: [
8112
- loading && sessions.length === 0 ? /* @__PURE__ */ jsx(SessionSkeleton, { count: 5 }) : /* @__PURE__ */ jsxs(Fragment, { children: [
8113
- pinnedSessions.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
8114
- /* @__PURE__ */ jsx(
8115
- DateGroupHeader,
8116
- {
8117
- groupName: t("BiChat.Common.Pinned"),
8118
- count: pinnedSessions.length
8119
- }
8120
- ),
8121
- /* @__PURE__ */ jsx(
8122
- motion.div,
8123
- {
8124
- className: "space-y-1 mt-2",
8125
- variants: staggerContainerVariants,
8126
- initial: "hidden",
8127
- animate: "visible",
8128
- role: "list",
8129
- "aria-label": t("BiChat.Sidebar.PinnedChats"),
8130
- children: pinnedSessions.map((session) => /* @__PURE__ */ jsx(
8131
- SessionItem_default,
8132
- {
8133
- session,
8134
- isActive: session.id === activeSessionId,
8135
- onSelect: () => onSessionSelect(session.id),
8136
- onArchive: () => handleArchiveRequest(session.id),
8137
- onPin: () => handleTogglePin(session.id, session.pinned),
8138
- onRename: (newTitle) => handleRenameSession(session.id, newTitle),
8139
- onRegenerateTitle: () => handleRegenerateTitle(session.id)
8140
- },
8141
- session.id
8142
- ))
8143
- }
8144
- ),
8145
- /* @__PURE__ */ jsx("div", { className: "border-b border-gray-200 dark:border-gray-700 my-3" })
8146
- ] }),
8147
- sessionGroups.map((group) => /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
8148
- /* @__PURE__ */ jsx(
8149
- DateGroupHeader,
8150
- {
8151
- groupName: group.name,
8152
- count: group.sessions.length
8153
- }
8154
- ),
8155
- /* @__PURE__ */ jsx(
8156
- motion.div,
8157
- {
8158
- className: "space-y-1 mt-2",
8159
- variants: staggerContainerVariants,
8160
- initial: "hidden",
8161
- animate: "visible",
8162
- role: "list",
8163
- "aria-label": `${group.name} chats`,
8164
- children: group.sessions.map((session) => /* @__PURE__ */ jsx(
8165
- SessionItem_default,
8276
+ className: `flex flex-col flex-1 min-h-0 w-64 shrink-0 transition-opacity ${showCollapsed ? "opacity-0 pointer-events-none duration-100" : collapsible ? "opacity-100 duration-150 delay-[200ms]" : ""}`,
8277
+ children: [
8278
+ (headerSlot || onClose) && /* @__PURE__ */ jsxs("div", { className: "p-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between", children: [
8279
+ headerSlot,
8280
+ onClose && /* @__PURE__ */ jsx(
8281
+ motion.button,
8282
+ {
8283
+ onClick: onClose,
8284
+ className: "cursor-pointer p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-smooth text-gray-600 dark:text-gray-400",
8285
+ title: t("BiChat.Sidebar.CloseSidebar"),
8286
+ "aria-label": t("BiChat.Sidebar.CloseSidebar"),
8287
+ whileHover: "hover",
8288
+ whileTap: "tap",
8289
+ variants: buttonVariants,
8290
+ children: /* @__PURE__ */ jsx(X, { size: 20, className: "w-5 h-5" })
8291
+ }
8292
+ )
8293
+ ] }),
8294
+ showAllChatsTab && /* @__PURE__ */ jsx(
8295
+ TabBar_default,
8296
+ {
8297
+ tabs,
8298
+ activeTab,
8299
+ onTabChange: (id) => setActiveTab(id)
8300
+ }
8301
+ ),
8302
+ activeTab === "all-chats" && showAllChatsTab ? /* @__PURE__ */ jsx(
8303
+ AllChatsList,
8304
+ {
8305
+ dataSource,
8306
+ onSessionSelect,
8307
+ activeSessionId
8308
+ }
8309
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
8310
+ /* @__PURE__ */ jsx("div", { ref: searchContainerRef, className: "mt-3 px-4", children: /* @__PURE__ */ jsx(
8311
+ SearchInput_default,
8312
+ {
8313
+ value: searchQuery,
8314
+ onChange: setSearchQuery,
8315
+ placeholder: t("BiChat.Sidebar.SearchChats")
8316
+ }
8317
+ ) }),
8318
+ /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx(
8319
+ motion.button,
8320
+ {
8321
+ onClick: (e) => {
8322
+ e.stopPropagation();
8323
+ onNewChat();
8324
+ },
8325
+ disabled: creating || loading || accessDenied,
8326
+ className: "cursor-pointer w-full px-4 py-2.5 bg-primary-600 dark:bg-primary-700 text-white rounded-lg hover:bg-primary-700 hover:-translate-y-0.5 active:bg-primary-800 transition-all duration-150 font-medium shadow-sm disabled:opacity-40 disabled:cursor-not-allowed flex items-center justify-center gap-2 focus-visible:ring-2 focus-visible:ring-primary-400/50 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-900",
8327
+ title: accessDenied ? t("BiChat.Sidebar.MissingPermission") : t("BiChat.Chat.NewChat"),
8328
+ "aria-label": t("BiChat.Sidebar.CreateNewChat"),
8329
+ whileHover: shouldReduceMotion ? {} : { y: -1 },
8330
+ whileTap: shouldReduceMotion ? {} : { scale: 0.98 },
8331
+ children: creating ? /* @__PURE__ */ jsxs(Fragment, { children: [
8332
+ /* @__PURE__ */ jsx(LoadingSpinner_default, { variant: "spinner", size: "sm" }),
8333
+ /* @__PURE__ */ jsx("span", { children: t("BiChat.Common.Creating") })
8334
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
8335
+ /* @__PURE__ */ jsx(Plus, { size: 16, weight: "bold" }),
8336
+ /* @__PURE__ */ jsx("span", { children: t("BiChat.Chat.NewChat") })
8337
+ ] })
8338
+ }
8339
+ ) }),
8340
+ /* @__PURE__ */ jsxs(
8341
+ "nav",
8342
+ {
8343
+ ref: sessionListRef,
8344
+ className: "flex-1 overflow-y-auto px-2 pb-4 hide-scrollbar",
8345
+ "aria-label": t("BiChat.Sidebar.ChatHistory"),
8346
+ onKeyDown: handleSessionListKeyDown,
8347
+ children: [
8348
+ loading && sessions.length === 0 ? /* @__PURE__ */ jsx(SessionSkeleton, { count: 5 }) : /* @__PURE__ */ jsxs(Fragment, { children: [
8349
+ pinnedSessions.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
8350
+ /* @__PURE__ */ jsx(
8351
+ DateGroupHeader,
8352
+ {
8353
+ groupName: t("BiChat.Common.Pinned"),
8354
+ count: pinnedSessions.length
8355
+ }
8356
+ ),
8357
+ /* @__PURE__ */ jsx(
8358
+ motion.div,
8359
+ {
8360
+ className: "space-y-1 mt-2",
8361
+ variants: staggerContainerVariants,
8362
+ initial: "hidden",
8363
+ animate: "visible",
8364
+ role: "list",
8365
+ "aria-label": t("BiChat.Sidebar.PinnedChats"),
8366
+ children: pinnedSessions.map((session) => /* @__PURE__ */ jsx(
8367
+ SessionItem_default,
8368
+ {
8369
+ session,
8370
+ isActive: session.id === activeSessionId,
8371
+ onSelect: () => onSessionSelect(session.id),
8372
+ onArchive: () => handleArchiveRequest(session.id),
8373
+ onPin: () => handleTogglePin(session.id, session.pinned),
8374
+ onRename: (newTitle) => handleRenameSession(session.id, newTitle),
8375
+ onRegenerateTitle: () => handleRegenerateTitle(session.id)
8376
+ },
8377
+ session.id
8378
+ ))
8379
+ }
8380
+ ),
8381
+ /* @__PURE__ */ jsx("div", { className: "border-b border-gray-200 dark:border-gray-700 my-3" })
8382
+ ] }),
8383
+ sessionGroups.map((group) => /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
8384
+ /* @__PURE__ */ jsx(
8385
+ DateGroupHeader,
8386
+ {
8387
+ groupName: group.name,
8388
+ count: group.sessions.length
8389
+ }
8390
+ ),
8391
+ /* @__PURE__ */ jsx(
8392
+ motion.div,
8393
+ {
8394
+ className: "space-y-1 mt-2",
8395
+ variants: staggerContainerVariants,
8396
+ initial: "hidden",
8397
+ animate: "visible",
8398
+ role: "list",
8399
+ "aria-label": `${group.name} chats`,
8400
+ children: group.sessions.map((session) => /* @__PURE__ */ jsx(
8401
+ SessionItem_default,
8402
+ {
8403
+ session,
8404
+ isActive: session.id === activeSessionId,
8405
+ onSelect: () => onSessionSelect(session.id),
8406
+ onArchive: () => handleArchiveRequest(session.id),
8407
+ onPin: () => handleTogglePin(session.id, session.pinned),
8408
+ onRename: (newTitle) => handleRenameSession(session.id, newTitle),
8409
+ onRegenerateTitle: () => handleRegenerateTitle(session.id)
8410
+ },
8411
+ session.id
8412
+ ))
8413
+ }
8414
+ )
8415
+ ] }, group.name)),
8416
+ filteredSessions.length === 0 && !loading && /* @__PURE__ */ jsx(
8417
+ MemoizedEmptyState,
8166
8418
  {
8167
- session,
8168
- isActive: session.id === activeSessionId,
8169
- onSelect: () => onSessionSelect(session.id),
8170
- onArchive: () => handleArchiveRequest(session.id),
8171
- onPin: () => handleTogglePin(session.id, session.pinned),
8172
- onRename: (newTitle) => handleRenameSession(session.id, newTitle),
8173
- onRegenerateTitle: () => handleRegenerateTitle(session.id)
8174
- },
8175
- session.id
8176
- ))
8177
- }
8178
- )
8179
- ] }, group.name)),
8180
- filteredSessions.length === 0 && !loading && /* @__PURE__ */ jsx(
8181
- MemoizedEmptyState,
8419
+ title: searchQuery ? t("BiChat.Sidebar.NoChatsFound", { query: searchQuery }) : t("BiChat.Sidebar.NoChatsYet"),
8420
+ description: searchQuery ? void 0 : t("BiChat.Sidebar.CreateOneToGetStarted"),
8421
+ action: searchQuery ? /* @__PURE__ */ jsx(
8422
+ "button",
8423
+ {
8424
+ onClick: () => setSearchQuery(""),
8425
+ className: "cursor-pointer text-sm text-primary-600 dark:text-primary-400 hover:underline",
8426
+ children: t("BiChat.Common.Clear")
8427
+ }
8428
+ ) : void 0
8429
+ }
8430
+ )
8431
+ ] }),
8432
+ loadError && /* @__PURE__ */ jsx(ErrorAlert, { error: loadError }),
8433
+ actionError && !loadError && /* @__PURE__ */ jsx(ErrorAlert, { error: actionError })
8434
+ ]
8435
+ }
8436
+ ),
8437
+ footerSlot
8438
+ ] }),
8439
+ collapsible && /* @__PURE__ */ jsxs("div", { className: "mt-auto border-t border-gray-100 dark:border-gray-800/80 px-4 py-3 flex items-center justify-between", children: [
8440
+ onArchivedView || showAllChatsTab ? /* @__PURE__ */ jsxs(Menu, { children: [
8441
+ /* @__PURE__ */ jsx(
8442
+ MenuButton,
8182
8443
  {
8183
- title: searchQuery ? t("BiChat.Sidebar.NoChatsFound", { query: searchQuery }) : t("BiChat.Sidebar.NoChatsYet"),
8184
- description: searchQuery ? void 0 : t("BiChat.Sidebar.CreateOneToGetStarted"),
8185
- action: searchQuery ? /* @__PURE__ */ jsx(
8186
- "button",
8187
- {
8188
- onClick: () => setSearchQuery(""),
8189
- className: "cursor-pointer text-sm text-primary-600 dark:text-primary-400 hover:underline",
8190
- children: t("BiChat.Common.Clear")
8191
- }
8192
- ) : void 0
8444
+ onClick: (e) => {
8445
+ e.stopPropagation();
8446
+ },
8447
+ disabled: loading || accessDenied,
8448
+ className: "flex items-center justify-center rounded-lg text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 p-2",
8449
+ "aria-label": t("BiChat.Sidebar.Settings"),
8450
+ title: t("BiChat.Sidebar.Settings"),
8451
+ children: /* @__PURE__ */ jsx(Gear, { size: 20 })
8452
+ }
8453
+ ),
8454
+ /* @__PURE__ */ jsxs(
8455
+ MenuItems,
8456
+ {
8457
+ anchor: "top start",
8458
+ className: "w-48 bg-white/95 dark:bg-gray-900/95 backdrop-blur-lg rounded-xl shadow-lg border border-gray-200/80 dark:border-gray-700/60 z-30 [--anchor-gap:8px] mb-1 p-1.5",
8459
+ children: [
8460
+ onArchivedView && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxs(
8461
+ "button",
8462
+ {
8463
+ onClick: (e) => {
8464
+ e.preventDefault();
8465
+ e.stopPropagation();
8466
+ onArchivedView();
8467
+ },
8468
+ className: `cursor-pointer flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-[13px] text-gray-600 dark:text-gray-300 transition-colors ${focus ? "bg-gray-100 dark:bg-gray-800/70" : ""}`,
8469
+ "aria-label": t("BiChat.Sidebar.ArchivedChats"),
8470
+ children: [
8471
+ /* @__PURE__ */ jsx(Archive, { size: 16, className: "text-gray-400 dark:text-gray-500" }),
8472
+ t("BiChat.Sidebar.ArchivedChats")
8473
+ ]
8474
+ }
8475
+ ) }),
8476
+ showAllChatsTab && activeTab !== "all-chats" && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxs(
8477
+ "button",
8478
+ {
8479
+ onClick: (e) => {
8480
+ e.preventDefault();
8481
+ e.stopPropagation();
8482
+ setActiveTab("all-chats");
8483
+ },
8484
+ className: `cursor-pointer flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-[13px] text-gray-600 dark:text-gray-300 transition-colors ${focus ? "bg-gray-100 dark:bg-gray-800/70" : ""}`,
8485
+ "aria-label": t("BiChat.Sidebar.AllChats"),
8486
+ children: [
8487
+ /* @__PURE__ */ jsx(Users, { size: 16, className: "text-gray-400 dark:text-gray-500" }),
8488
+ t("BiChat.Sidebar.AllChats")
8489
+ ]
8490
+ }
8491
+ ) }),
8492
+ showAllChatsTab && activeTab === "all-chats" && /* @__PURE__ */ jsx(MenuItem, { children: ({ focus }) => /* @__PURE__ */ jsxs(
8493
+ "button",
8494
+ {
8495
+ onClick: (e) => {
8496
+ e.preventDefault();
8497
+ e.stopPropagation();
8498
+ setActiveTab("my-chats");
8499
+ },
8500
+ className: `cursor-pointer flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-[13px] text-gray-600 dark:text-gray-300 transition-colors ${focus ? "bg-gray-100 dark:bg-gray-800/70" : ""}`,
8501
+ "aria-label": t("BiChat.Sidebar.MyChats"),
8502
+ children: [
8503
+ /* @__PURE__ */ jsx(List, { size: 16, className: "text-gray-400 dark:text-gray-500" }),
8504
+ t("BiChat.Sidebar.MyChats")
8505
+ ]
8506
+ }
8507
+ ) })
8508
+ ]
8193
8509
  }
8194
8510
  )
8195
- ] }),
8196
- error && /* @__PURE__ */ jsx("div", { className: "mx-2 mt-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600 dark:text-red-400", children: error }) })
8197
- ]
8511
+ ] }) : /* @__PURE__ */ jsx("div", {}),
8512
+ /* @__PURE__ */ jsxs(
8513
+ "button",
8514
+ {
8515
+ onClick: (e) => {
8516
+ e.stopPropagation();
8517
+ toggle();
8518
+ },
8519
+ className: "flex items-center gap-2 rounded-lg px-3 py-2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50",
8520
+ title: t("BiChat.Sidebar.CollapseSidebar"),
8521
+ "aria-label": t("BiChat.Sidebar.CollapseSidebar"),
8522
+ children: [
8523
+ /* @__PURE__ */ jsx(CaretLineLeft, { size: 16 }),
8524
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: t("BiChat.Sidebar.Collapse") })
8525
+ ]
8526
+ }
8527
+ )
8528
+ ] })
8529
+ ]
8530
+ }
8531
+ ),
8532
+ collapsible && showCollapsed && /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 inset-x-0 z-10 border-t border-gray-100 dark:border-gray-800/80 py-3 flex justify-center", children: /* @__PURE__ */ jsxs("div", { className: "group/tooltip relative", children: [
8533
+ /* @__PURE__ */ jsx(
8534
+ "button",
8535
+ {
8536
+ onClick: (e) => {
8537
+ e.stopPropagation();
8538
+ toggle();
8539
+ },
8540
+ className: "w-10 h-10 flex items-center justify-center rounded-lg text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50",
8541
+ title: t("BiChat.Sidebar.ExpandSidebar"),
8542
+ "aria-label": t("BiChat.Sidebar.ExpandSidebar"),
8543
+ children: /* @__PURE__ */ jsx(CaretLineRight, { size: 16 })
8198
8544
  }
8199
8545
  ),
8200
- footerSlot
8201
- ] })
8546
+ /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute left-full ml-2 top-1/2 -translate-y-1/2 rounded-md bg-gray-900 dark:bg-gray-100 px-2 py-1 text-xs font-medium text-white dark:text-gray-900 opacity-0 group-hover/tooltip:opacity-100 transition-opacity whitespace-nowrap shadow-lg", children: t("BiChat.Sidebar.Expand") })
8547
+ ] }) })
8202
8548
  ]
8203
8549
  }
8204
8550
  ),
@@ -8415,6 +8761,231 @@ function ArchivedChatList({
8415
8761
  }
8416
8762
  );
8417
8763
  }
8764
+
8765
+ // ui/src/bichat/components/SkipLink.tsx
8766
+ init_useTranslation();
8767
+ function SkipLink() {
8768
+ const { t } = useTranslation();
8769
+ return /* @__PURE__ */ jsx(
8770
+ "a",
8771
+ {
8772
+ href: "#main-content",
8773
+ className: "sr-only focus-visible:not-sr-only focus-visible:absolute focus-visible:top-4 focus-visible:left-4 focus-visible:z-50 focus-visible:bg-primary-600 focus-visible:text-white focus-visible:px-4 focus-visible:py-2 focus-visible:rounded-lg focus-visible:shadow-lg",
8774
+ children: t("BiChat.SkipLink.Label")
8775
+ }
8776
+ );
8777
+ }
8778
+ var MOBILE_QUERY = "(max-width: 767px)";
8779
+ function getIsMobile() {
8780
+ if (typeof window === "undefined") return false;
8781
+ return window.matchMedia(MOBILE_QUERY).matches;
8782
+ }
8783
+ function useSidebarState() {
8784
+ const [isMobile, setIsMobile] = useState(getIsMobile);
8785
+ const [isMobileOpen, setIsMobileOpen] = useState(false);
8786
+ useEffect(() => {
8787
+ if (typeof window === "undefined") return;
8788
+ const mql = window.matchMedia(MOBILE_QUERY);
8789
+ const handler = (e) => {
8790
+ setIsMobile(e.matches);
8791
+ if (!e.matches) setIsMobileOpen(false);
8792
+ };
8793
+ if (mql.addEventListener) {
8794
+ mql.addEventListener("change", handler);
8795
+ } else if (mql.addListener) {
8796
+ mql.addListener(handler);
8797
+ }
8798
+ return () => {
8799
+ if (mql.removeEventListener) {
8800
+ mql.removeEventListener("change", handler);
8801
+ } else if (mql.removeListener) {
8802
+ mql.removeListener(handler);
8803
+ }
8804
+ };
8805
+ }, []);
8806
+ const openMobile = useCallback(() => setIsMobileOpen(true), []);
8807
+ const closeMobile = useCallback(() => setIsMobileOpen(false), []);
8808
+ const toggleMobile = useCallback(() => setIsMobileOpen((v) => !v), []);
8809
+ return { isMobile, isMobileOpen, openMobile, closeMobile, toggleMobile };
8810
+ }
8811
+ function useFocusTrap(containerRef, isActive, restoreFocusOnDeactivate) {
8812
+ useEffect(() => {
8813
+ if (!isActive || !containerRef.current) return;
8814
+ const container = containerRef.current;
8815
+ const previouslyFocused = document.activeElement;
8816
+ const getFocusableElements = () => {
8817
+ const selector = [
8818
+ "button:not([disabled])",
8819
+ "[href]",
8820
+ "input:not([disabled])",
8821
+ "select:not([disabled])",
8822
+ "textarea:not([disabled])",
8823
+ '[tabindex]:not([tabindex="-1"])'
8824
+ ].join(", ");
8825
+ return Array.from(container.querySelectorAll(selector));
8826
+ };
8827
+ const focusableElements = getFocusableElements();
8828
+ if (focusableElements.length > 0) {
8829
+ focusableElements[0].focus();
8830
+ }
8831
+ const handleTabKey = (e) => {
8832
+ if (e.key !== "Tab") return;
8833
+ const focusableElements2 = getFocusableElements();
8834
+ if (focusableElements2.length === 0) return;
8835
+ const firstElement = focusableElements2[0];
8836
+ const lastElement = focusableElements2[focusableElements2.length - 1];
8837
+ if (e.shiftKey) {
8838
+ if (document.activeElement === firstElement) {
8839
+ e.preventDefault();
8840
+ lastElement.focus();
8841
+ }
8842
+ } else {
8843
+ if (document.activeElement === lastElement) {
8844
+ e.preventDefault();
8845
+ firstElement.focus();
8846
+ }
8847
+ }
8848
+ };
8849
+ container.addEventListener("keydown", handleTabKey);
8850
+ return () => {
8851
+ container.removeEventListener("keydown", handleTabKey);
8852
+ if (restoreFocusOnDeactivate) {
8853
+ restoreFocusOnDeactivate.focus();
8854
+ } else if (previouslyFocused instanceof HTMLElement) {
8855
+ previouslyFocused.focus();
8856
+ }
8857
+ };
8858
+ }, [containerRef, isActive, restoreFocusOnDeactivate]);
8859
+ }
8860
+ function useKeyboardShortcuts(shortcuts) {
8861
+ useEffect(() => {
8862
+ const handleKeyDown = (e) => {
8863
+ const target = e.target;
8864
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target.isContentEditable) {
8865
+ const allowInInput = shortcuts.find(
8866
+ (s) => s.key.toLowerCase() === e.key.toLowerCase() && s.key.toLowerCase() === "escape"
8867
+ );
8868
+ if (!allowInInput) {
8869
+ return;
8870
+ }
8871
+ }
8872
+ const matchingShortcut = shortcuts.find((s) => {
8873
+ const keyMatches = e.key.toLowerCase() === s.key.toLowerCase();
8874
+ const modMatches = s.meta ? e.metaKey && !e.ctrlKey : s.ctrl ? e.ctrlKey || e.metaKey : !e.ctrlKey && !e.metaKey;
8875
+ const shiftMatches = s.shift ? e.shiftKey : !e.shiftKey;
8876
+ const altMatches = s.alt ? e.altKey : !e.altKey;
8877
+ return keyMatches && modMatches && shiftMatches && altMatches;
8878
+ });
8879
+ if (matchingShortcut) {
8880
+ if (matchingShortcut.preventDefault !== false) {
8881
+ e.preventDefault();
8882
+ }
8883
+ matchingShortcut.callback();
8884
+ }
8885
+ };
8886
+ document.addEventListener("keydown", handleKeyDown);
8887
+ return () => document.removeEventListener("keydown", handleKeyDown);
8888
+ }, [shortcuts]);
8889
+ }
8890
+
8891
+ // ui/src/bichat/components/BiChatLayout.tsx
8892
+ init_useTranslation();
8893
+ function BiChatLayout({
8894
+ renderSidebar,
8895
+ children,
8896
+ onNewChat,
8897
+ routeKey,
8898
+ className = ""
8899
+ }) {
8900
+ const { t } = useTranslation();
8901
+ const { isMobile, isMobileOpen, openMobile, closeMobile } = useSidebarState();
8902
+ const drawerRef = useRef(null);
8903
+ const menuButtonRef = useRef(null);
8904
+ useFocusTrap(drawerRef, isMobile && isMobileOpen, menuButtonRef.current);
8905
+ const shortcuts = useMemo(() => {
8906
+ if (!onNewChat) return [];
8907
+ return [{ key: "n", ctrl: true, callback: onNewChat, description: "New chat" }];
8908
+ }, [onNewChat]);
8909
+ useKeyboardShortcuts(shortcuts);
8910
+ useEffect(() => {
8911
+ if (!isMobile || !isMobileOpen) return;
8912
+ const onKeyDown = (e) => {
8913
+ if (e.key === "Escape") {
8914
+ e.preventDefault();
8915
+ closeMobile();
8916
+ }
8917
+ };
8918
+ document.addEventListener("keydown", onKeyDown);
8919
+ return () => document.removeEventListener("keydown", onKeyDown);
8920
+ }, [closeMobile, isMobile, isMobileOpen]);
8921
+ const handleDrawerDragEnd = (_, info) => {
8922
+ if (info.offset.x < -80) {
8923
+ closeMobile();
8924
+ }
8925
+ };
8926
+ const content = routeKey ? /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", initial: false, children: /* @__PURE__ */ jsx(
8927
+ motion.div,
8928
+ {
8929
+ className: "flex flex-1 min-h-0",
8930
+ initial: { opacity: 0, y: 4 },
8931
+ animate: { opacity: 1, y: 0 },
8932
+ exit: { opacity: 0, y: -4 },
8933
+ transition: { duration: 0.15, ease: "easeOut" },
8934
+ children
8935
+ },
8936
+ routeKey
8937
+ ) }) : /* @__PURE__ */ jsx("div", { className: "flex flex-1 min-h-0", children });
8938
+ return /* @__PURE__ */ jsxs("div", { className: `relative flex flex-1 w-full h-full min-h-0 overflow-hidden ${className}`, children: [
8939
+ /* @__PURE__ */ jsx(SkipLink, {}),
8940
+ /* @__PURE__ */ jsx("div", { className: "hidden md:block", children: renderSidebar({}) }),
8941
+ /* @__PURE__ */ jsx(AnimatePresence, { children: isMobile && isMobileOpen && /* @__PURE__ */ jsxs(Fragment, { children: [
8942
+ /* @__PURE__ */ jsx(
8943
+ motion.div,
8944
+ {
8945
+ className: "fixed inset-0 z-40 bg-black/40",
8946
+ initial: { opacity: 0 },
8947
+ animate: { opacity: 1 },
8948
+ exit: { opacity: 0 },
8949
+ onClick: closeMobile,
8950
+ "aria-hidden": "true"
8951
+ },
8952
+ "sidebar-backdrop"
8953
+ ),
8954
+ /* @__PURE__ */ jsx(
8955
+ motion.div,
8956
+ {
8957
+ className: "fixed inset-y-0 left-0 z-50 w-[18rem] max-w-[85vw] shadow-2xl",
8958
+ initial: { x: "-100%" },
8959
+ animate: { x: 0 },
8960
+ exit: { x: "-100%" },
8961
+ transition: { type: "spring", stiffness: 320, damping: 32 },
8962
+ drag: "x",
8963
+ dragDirectionLock: true,
8964
+ dragConstraints: { left: -120, right: 0 },
8965
+ dragElastic: { left: 0.2, right: 0 },
8966
+ onDragEnd: handleDrawerDragEnd,
8967
+ onClick: (e) => e.stopPropagation(),
8968
+ children: /* @__PURE__ */ jsx("div", { ref: drawerRef, className: "h-full bg-white dark:bg-gray-900", children: renderSidebar({ onClose: closeMobile }) })
8969
+ },
8970
+ "sidebar-drawer"
8971
+ )
8972
+ ] }) }),
8973
+ /* @__PURE__ */ jsxs("main", { id: "main-content", className: "relative flex-1 flex flex-col min-h-0 overflow-hidden", children: [
8974
+ isMobile && !isMobileOpen && /* @__PURE__ */ jsx(
8975
+ "button",
8976
+ {
8977
+ ref: menuButtonRef,
8978
+ onClick: openMobile,
8979
+ className: "md:hidden absolute top-3 left-3 z-30 w-10 h-10 rounded-xl bg-white/90 dark:bg-gray-900/90 text-gray-700 dark:text-gray-200 border border-gray-200/60 dark:border-gray-800/80 shadow-sm flex items-center justify-center hover:bg-white dark:hover:bg-gray-900 transition-colors cursor-pointer focus-visible:ring-2 focus-visible:ring-primary-400/50",
8980
+ "aria-label": t("BiChat.Layout.OpenSidebar"),
8981
+ title: t("BiChat.Layout.OpenSidebar"),
8982
+ children: /* @__PURE__ */ jsx(List, { size: 20, weight: "bold" })
8983
+ }
8984
+ ),
8985
+ content
8986
+ ] })
8987
+ ] });
8988
+ }
8418
8989
  init_useTranslation();
8419
8990
  var variantStyles = {
8420
8991
  error: {
@@ -8968,20 +9539,6 @@ function ScreenReaderAnnouncer({
8968
9539
  );
8969
9540
  }
8970
9541
 
8971
- // ui/src/bichat/components/SkipLink.tsx
8972
- init_useTranslation();
8973
- function SkipLink() {
8974
- const { t } = useTranslation();
8975
- return /* @__PURE__ */ jsx(
8976
- "a",
8977
- {
8978
- href: "#main-content",
8979
- className: "sr-only focus-visible:not-sr-only focus-visible:absolute focus-visible:top-4 focus-visible:left-4 focus-visible:z-50 focus-visible:bg-primary-600 focus-visible:text-white focus-visible:px-4 focus-visible:py-2 focus-visible:rounded-lg focus-visible:shadow-lg",
8980
- children: t("BiChat.SkipLink.Label")
8981
- }
8982
- );
8983
- }
8984
-
8985
9542
  // ui/src/bichat/components/QuestionForm.tsx
8986
9543
  init_useTranslation();
8987
9544
 
@@ -9680,55 +10237,6 @@ function useModalLock(isOpen) {
9680
10237
  };
9681
10238
  }, [isOpen]);
9682
10239
  }
9683
- function useFocusTrap(containerRef, isActive, restoreFocusOnDeactivate) {
9684
- useEffect(() => {
9685
- if (!isActive || !containerRef.current) return;
9686
- const container = containerRef.current;
9687
- const previouslyFocused = document.activeElement;
9688
- const getFocusableElements = () => {
9689
- const selector = [
9690
- "button:not([disabled])",
9691
- "[href]",
9692
- "input:not([disabled])",
9693
- "select:not([disabled])",
9694
- "textarea:not([disabled])",
9695
- '[tabindex]:not([tabindex="-1"])'
9696
- ].join(", ");
9697
- return Array.from(container.querySelectorAll(selector));
9698
- };
9699
- const focusableElements = getFocusableElements();
9700
- if (focusableElements.length > 0) {
9701
- focusableElements[0].focus();
9702
- }
9703
- const handleTabKey = (e) => {
9704
- if (e.key !== "Tab") return;
9705
- const focusableElements2 = getFocusableElements();
9706
- if (focusableElements2.length === 0) return;
9707
- const firstElement = focusableElements2[0];
9708
- const lastElement = focusableElements2[focusableElements2.length - 1];
9709
- if (e.shiftKey) {
9710
- if (document.activeElement === firstElement) {
9711
- e.preventDefault();
9712
- lastElement.focus();
9713
- }
9714
- } else {
9715
- if (document.activeElement === lastElement) {
9716
- e.preventDefault();
9717
- firstElement.focus();
9718
- }
9719
- }
9720
- };
9721
- container.addEventListener("keydown", handleTabKey);
9722
- return () => {
9723
- container.removeEventListener("keydown", handleTabKey);
9724
- if (restoreFocusOnDeactivate) {
9725
- restoreFocusOnDeactivate.focus();
9726
- } else if (previouslyFocused instanceof HTMLElement) {
9727
- previouslyFocused.focus();
9728
- }
9729
- };
9730
- }, [containerRef, isActive, restoreFocusOnDeactivate]);
9731
- }
9732
10240
  function useImageGallery(options = {}) {
9733
10241
  const { images: initialImages = [], wrap = false, onOpen, onClose, onNavigate } = options;
9734
10242
  const [isOpen, setIsOpen] = useState(false);
@@ -10264,36 +10772,6 @@ function useScrollToBottom(items) {
10264
10772
  scrollToBottom
10265
10773
  };
10266
10774
  }
10267
- function useKeyboardShortcuts(shortcuts) {
10268
- useEffect(() => {
10269
- const handleKeyDown = (e) => {
10270
- const target = e.target;
10271
- if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target.isContentEditable) {
10272
- const allowInInput = shortcuts.find(
10273
- (s) => s.key.toLowerCase() === e.key.toLowerCase() && s.key.toLowerCase() === "escape"
10274
- );
10275
- if (!allowInInput) {
10276
- return;
10277
- }
10278
- }
10279
- const matchingShortcut = shortcuts.find((s) => {
10280
- const keyMatches = e.key.toLowerCase() === s.key.toLowerCase();
10281
- const modMatches = s.meta ? e.metaKey && !e.ctrlKey : s.ctrl ? e.ctrlKey || e.metaKey : !e.ctrlKey && !e.metaKey;
10282
- const shiftMatches = s.shift ? e.shiftKey : !e.shiftKey;
10283
- const altMatches = s.alt ? e.altKey : !e.altKey;
10284
- return keyMatches && modMatches && shiftMatches && altMatches;
10285
- });
10286
- if (matchingShortcut) {
10287
- if (matchingShortcut.preventDefault !== false) {
10288
- e.preventDefault();
10289
- }
10290
- matchingShortcut.callback();
10291
- }
10292
- };
10293
- document.addEventListener("keydown", handleKeyDown);
10294
- return () => document.removeEventListener("keydown", handleKeyDown);
10295
- }, [shortcuts]);
10296
- }
10297
10775
 
10298
10776
  // ui/src/bichat/index.ts
10299
10777
  init_IotaContext();
@@ -11203,6 +11681,6 @@ function createHttpDataSource(config) {
11203
11681
  return new HttpDataSource(config);
11204
11682
  }
11205
11683
 
11206
- export { ATTACHMENT_ACCEPT_ATTRIBUTE, ActionButton, Alert_default as Alert, AllChatsList, ArchiveBanner_default as ArchiveBanner, ArchivedChatList, AssistantMessage, AssistantTurnView, MemoizedAttachmentGrid as AttachmentGrid, AttachmentPreview_default as AttachmentPreview, AttachmentUpload_default as AttachmentUpload, Avatar, Bubble, CHART_VISUAL, ChartCard, ChatHeader, ChatSession, ChatSessionProvider, MemoizedCodeBlock as CodeBlock, CodeOutputsPanel, CompactionDoodle, ConfigProvider, ConfirmModal, ConfirmationStep, DateGroupHeader, DebugPanel, DefaultErrorContent, DownloadCard, MemoizedEditableText as EditableText, MemoizedEmptyState as EmptyState, ErrorBoundary, HttpDataSource, ImageModal, InlineQuestionForm, IotaContextProvider, ListItemSkeleton, MemoizedLoadingSpinner as LoadingSpinner, MemoizedMarkdownRenderer as MarkdownRenderer, MessageActions, MessageInput, MessageList, MessageRole, PermissionGuard, QuestionForm, QuestionStep, RateLimiter, RetryActionArea, ScreenReaderAnnouncer, ScrollToBottomButton, MemoizedSearchInput as SearchInput, SessionArtifactList, SessionArtifactPreview, SessionArtifactsPanel, SessionItem_default as SessionItem, SessionSkeleton, Sidebar2 as Sidebar, MemoizedSkeleton as Skeleton, SkeletonAvatar, SkeletonCard, SkeletonGroup, SkeletonText, SkipLink, Slot, SourcesPanel, StreamError, StreamingCursor, SystemMessage, MemoizedTabBar as TabBar, TableExportButton, TableWithExport, ThemeProvider, Toast, ToastContainer, TouchContextMenu, Turn, TurnBubble, MemoizedTypingIndicator as TypingIndicator, MemoizedUserAvatar as UserAvatar, MemoizedUserFilter as UserFilter, UserMessage, UserTurnView, WelcomeContent, addCSRFHeader, backdropVariants, buttonVariants, convertToBase64, createDataUrl, createHeadersWithCSRF, createHttpDataSource, darkTheme, dropdownVariants, errorMessageVariants, fadeInUpVariants, fadeInVariants, floatingButtonVariants, formatFileSize, getCSRFToken, getFileVisual, getValidChildren, groupSessionsByDate, hasPermission, isImageMimeType, lightTheme, listItemVariants, messageContainerVariants, messageVariants, scaleFadeVariants, sessionItemVariants, staggerContainerVariants, toastVariants, typingDotVariants, useActionButtonContext, useAttachments, useAutoScroll, useAvatarContext, useBubbleContext, useChatInput, useChatMessaging, useChatSession, useConfig, useFocusTrap, useImageGallery, useIotaContext, useKeyboardShortcuts, useLongPress, useMarkdownCopy, useMessageActions, useModalLock, useOptionalChatMessaging, useRequiredConfig, useScrollToBottom, useStreaming, useTheme, useToast, useTranslation, useTurnContext, validateAttachmentFile, validateFileCount, validateImageFile, verbTransitionVariants };
11684
+ export { ATTACHMENT_ACCEPT_ATTRIBUTE, ActionButton, Alert_default as Alert, AllChatsList, ArchiveBanner_default as ArchiveBanner, ArchivedChatList, AssistantMessage, AssistantTurnView, MemoizedAttachmentGrid as AttachmentGrid, AttachmentPreview_default as AttachmentPreview, AttachmentUpload_default as AttachmentUpload, Avatar, BiChatLayout, Bubble, CHART_VISUAL, ChartCard, ChatHeader, ChatSession, ChatSessionProvider, MemoizedCodeBlock as CodeBlock, CodeOutputsPanel, CompactionDoodle, ConfigProvider, ConfirmModal, ConfirmationStep, DateGroupHeader, DebugPanel, DefaultErrorContent, DownloadCard, MemoizedEditableText as EditableText, MemoizedEmptyState as EmptyState, ErrorBoundary, HttpDataSource, ImageModal, InlineQuestionForm, IotaContextProvider, ListItemSkeleton, MemoizedLoadingSpinner as LoadingSpinner, MemoizedMarkdownRenderer as MarkdownRenderer, MessageActions, MessageInput, MessageList, MessageRole, PermissionGuard, QuestionForm, QuestionStep, RateLimiter, RetryActionArea, ScreenReaderAnnouncer, ScrollToBottomButton, MemoizedSearchInput as SearchInput, SessionArtifactList, SessionArtifactPreview, SessionArtifactsPanel, SessionItem_default as SessionItem, SessionSkeleton, Sidebar2 as Sidebar, MemoizedSkeleton as Skeleton, SkeletonAvatar, SkeletonCard, SkeletonGroup, SkeletonText, SkipLink, Slot, SourcesPanel, StreamError, StreamingCursor, SystemMessage, MemoizedTabBar as TabBar, TableExportButton, TableWithExport, ThemeProvider, Toast, ToastContainer, TouchContextMenu, Turn, TurnBubble, MemoizedTypingIndicator as TypingIndicator, MemoizedUserAvatar as UserAvatar, MemoizedUserFilter as UserFilter, UserMessage, UserTurnView, WelcomeContent, addCSRFHeader, backdropVariants, buttonVariants, convertToBase64, createDataUrl, createHeadersWithCSRF, createHttpDataSource, darkTheme, dropdownVariants, errorMessageVariants, fadeInUpVariants, fadeInVariants, floatingButtonVariants, formatFileSize, getCSRFToken, getFileVisual, getValidChildren, groupSessionsByDate, hasPermission, isImageMimeType, isPermissionDeniedError, lightTheme, listItemVariants, messageContainerVariants, messageVariants, scaleFadeVariants, sessionItemVariants, staggerContainerVariants, toErrorDisplay, toastVariants, typingDotVariants, useActionButtonContext, useAttachments, useAutoScroll, useAvatarContext, useBubbleContext, useChatInput, useChatMessaging, useChatSession, useConfig, useFocusTrap, useImageGallery, useIotaContext, useKeyboardShortcuts, useLongPress, useMarkdownCopy, useMessageActions, useModalLock, useOptionalChatMessaging, useRequiredConfig, useScrollToBottom, useSidebarState, useStreaming, useTheme, useToast, useTranslation, useTurnContext, validateAttachmentFile, validateFileCount, validateImageFile, verbTransitionVariants };
11207
11685
  //# sourceMappingURL=index.mjs.map
11208
11686
  //# sourceMappingURL=index.mjs.map