@xopcai/xopc 0.0.21 → 0.0.22

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.
Files changed (34) hide show
  1. package/dist/extensions/telegram/xopc.extension.json +1 -1
  2. package/dist/gateway/static/root/assets/{agents-MbH57-L9.js → agents-BcLv59-r.js} +2 -2
  3. package/dist/gateway/static/root/assets/{agents-MbH57-L9.js.map → agents-BcLv59-r.js.map} +1 -1
  4. package/dist/gateway/static/root/assets/{apps-page-3i3DvI7i.js → apps-page-Bl-yxbQo.js} +2 -2
  5. package/dist/gateway/static/root/assets/{apps-page-3i3DvI7i.js.map → apps-page-Bl-yxbQo.js.map} +1 -1
  6. package/dist/gateway/static/root/assets/{channels-settings-CcuSzoB6.js → channels-settings-BGueHxMv.js} +2 -2
  7. package/dist/gateway/static/root/assets/{channels-settings-CcuSzoB6.js.map → channels-settings-BGueHxMv.js.map} +1 -1
  8. package/dist/gateway/static/root/assets/{cron-page-Be1h9Yub.js → cron-page-DsVZzPqv.js} +2 -2
  9. package/dist/gateway/static/root/assets/{cron-page-Be1h9Yub.js.map → cron-page-DsVZzPqv.js.map} +1 -1
  10. package/dist/gateway/static/root/assets/{cron-utils-CR97EvZS.js → cron-utils-zbRs2yND.js} +2 -2
  11. package/dist/gateway/static/root/assets/{cron-utils-CR97EvZS.js.map → cron-utils-zbRs2yND.js.map} +1 -1
  12. package/dist/gateway/static/root/assets/{dist-r_Gy-XJv.js → dist-CDA7gR_M.js} +2 -2
  13. package/dist/gateway/static/root/assets/{dist-r_Gy-XJv.js.map → dist-CDA7gR_M.js.map} +1 -1
  14. package/dist/gateway/static/root/assets/{extension-debug-page-QfYEYruq.js → extension-debug-page-CDLp4DAs.js} +2 -2
  15. package/dist/gateway/static/root/assets/{extension-debug-page-QfYEYruq.js.map → extension-debug-page-CDLp4DAs.js.map} +1 -1
  16. package/dist/gateway/static/root/assets/{extension-page-4FW-BmKG.js → extension-page-DwSCjzHO.js} +2 -2
  17. package/dist/gateway/static/root/assets/{extension-page-4FW-BmKG.js.map → extension-page-DwSCjzHO.js.map} +1 -1
  18. package/dist/gateway/static/root/assets/{extension-settings-page-E_Wq9LL8.js → extension-settings-page-Rdmxe24_.js} +2 -2
  19. package/dist/gateway/static/root/assets/{extension-settings-page-E_Wq9LL8.js.map → extension-settings-page-Rdmxe24_.js.map} +1 -1
  20. package/dist/gateway/static/root/assets/index-DG8WvMbu.js +150 -0
  21. package/dist/gateway/static/root/assets/index-DG8WvMbu.js.map +1 -0
  22. package/dist/gateway/static/root/assets/{logs-page-DFhTU-kG.js → logs-page-ChJ0nsPh.js} +2 -2
  23. package/dist/gateway/static/root/assets/{logs-page-DFhTU-kG.js.map → logs-page-ChJ0nsPh.js.map} +1 -1
  24. package/dist/gateway/static/root/assets/{sessions-page-wmnnIj6Z.js → sessions-page-Cle4fPla.js} +2 -2
  25. package/dist/gateway/static/root/assets/{sessions-page-wmnnIj6Z.js.map → sessions-page-Cle4fPla.js.map} +1 -1
  26. package/dist/gateway/static/root/assets/{settings-page-BTmUXY4s.js → settings-page-Dyo2NYdy.js} +2 -2
  27. package/dist/gateway/static/root/assets/{settings-page-BTmUXY4s.js.map → settings-page-Dyo2NYdy.js.map} +1 -1
  28. package/dist/gateway/static/root/assets/{skills-page-D-fRbJG0.js → skills-page-B-smhcB2.js} +2 -2
  29. package/dist/gateway/static/root/assets/{skills-page-D-fRbJG0.js.map → skills-page-B-smhcB2.js.map} +1 -1
  30. package/dist/gateway/static/root/index.html +1 -1
  31. package/dist/package.js +1 -1
  32. package/package.json +1 -1
  33. package/dist/gateway/static/root/assets/index-CcQtNJKo.js +0 -150
  34. package/dist/gateway/static/root/assets/index-CcQtNJKo.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"sessions-page-wmnnIj6Z.js","names":[],"sources":["../../../../../web/src/features/sessions/session-card.tsx","../../../../../web/src/features/sessions/session-detail-drawer.tsx","../../../../../web/src/features/sessions/sessions-page.tsx"],"sourcesContent":["import {\n Archive,\n ArchiveRestore,\n Download,\n MessageSquare,\n Pin,\n PinOff,\n Trash2,\n Zap,\n} from 'lucide-react';\n\nimport type { SessionMetadata } from '@/features/sessions/session.types';\nimport { ghostIconButton } from '@/lib/interaction';\nimport { cn } from '@/lib/cn';\n\nexport type SessionCardAction =\n | 'continue'\n | 'delete'\n | 'archive'\n | 'unarchive'\n | 'pin'\n | 'unpin'\n | 'export';\n\nfunction formatRelativeDate(dateStr: string): string {\n const date = new Date(dateStr);\n const now = new Date();\n const diffDays = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));\n if (diffDays === 0) {\n return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n }\n if (diffDays === 1) return 'Yesterday';\n if (diffDays < 7) return date.toLocaleDateString([], { weekday: 'short' });\n return date.toLocaleDateString([], { month: 'short', day: 'numeric' });\n}\n\nfunction formatTokens(tokens: number): string {\n if (tokens >= 1000) return `${(tokens / 1000).toFixed(1)}k`;\n return String(tokens);\n}\n\nexport function SessionCard({\n session,\n variant,\n labels,\n onOpen,\n onAction,\n}: {\n session: SessionMetadata;\n variant: 'grid' | 'list';\n labels: {\n continueChat: string;\n archive: string;\n unarchive: string;\n pin: string;\n unpin: string;\n export: string;\n delete: string;\n /** Shown when `session.name` is empty (e.g. new chat before auto-title). */\n unnamedSession: string;\n };\n onOpen: () => void;\n onAction: (action: SessionCardAction) => void;\n}) {\n const displayName = session.name?.trim() || labels.unnamedSession;\n const showKeySubtitle = Boolean(session.name?.trim());\n const isArchived = session.status === 'archived';\n const isPinned = session.status === 'pinned';\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n className={cn(\n // min-w-0: grid/flex children default to min-width:auto — long unbroken titles (URLs) otherwise expand the track\n 'group flex min-w-0 w-full max-w-full cursor-pointer flex-col rounded-xl bg-surface-base text-left transition-colors duration-150 ease-out',\n 'hover:bg-surface-hover active:scale-[0.99]',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-surface-panel',\n variant === 'list' && 'sm:flex-row sm:items-center sm:gap-4',\n )}\n onClick={onOpen}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onOpen();\n }\n }}\n >\n <div\n className={cn(\n 'flex min-w-0 items-start justify-between gap-2 bg-surface-hover/35 px-3 py-2 dark:bg-surface-hover/25',\n variant === 'list' && 'sm:py-3',\n )}\n >\n <div className=\"flex min-w-0 items-center gap-2\">\n <span className=\"truncate text-[11px] font-medium uppercase tracking-wide text-fg-subtle\">\n {session.sourceChannel}\n </span>\n </div>\n <div className=\"flex shrink-0 items-center gap-1.5 text-xs text-fg-muted\">\n {isPinned ? <Pin className=\"size-3.5 text-accent-fg\" strokeWidth={1.75} aria-hidden /> : null}\n <span>{formatRelativeDate(session.updatedAt)}</span>\n </div>\n </div>\n\n <div className={cn('min-w-0 flex-1 px-3 py-2', variant === 'list' && 'sm:py-3')}>\n <div className=\"min-w-0 max-w-full truncate text-sm font-semibold text-fg\" title={displayName}>\n {displayName}\n </div>\n {showKeySubtitle ? (\n <div\n className=\"mt-0.5 min-w-0 max-w-full truncate font-mono text-[11px] text-fg-subtle\"\n title={session.key}\n >\n {session.key}\n </div>\n ) : null}\n <div className=\"mt-2 flex flex-wrap items-center gap-3 text-xs text-fg-muted\">\n <span className=\"inline-flex items-center gap-1\">\n <MessageSquare className=\"size-3.5\" strokeWidth={1.75} aria-hidden />\n {session.messageCount}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Zap className=\"size-3.5\" strokeWidth={1.75} aria-hidden />\n {formatTokens(session.estimatedTokens)}\n </span>\n </div>\n {session.tags.length > 0 ? (\n <div className=\"mt-2 flex flex-wrap gap-1\">\n {session.tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n className=\"max-w-full break-words rounded-md bg-surface-hover px-1.5 py-0.5 text-[11px] text-fg-muted\"\n >\n {tag}\n </span>\n ))}\n {session.tags.length > 3 ? (\n <span className=\"text-[11px] text-fg-disabled\">+{session.tags.length - 3}</span>\n ) : null}\n </div>\n ) : null}\n </div>\n\n <div\n className=\"flex flex-wrap items-center gap-0.5 border-t border-edge-subtle/80 bg-surface-hover/25 px-2 py-2 dark:border-edge-subtle\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n <button\n type=\"button\"\n className={ghostIconButton}\n title={labels.continueChat}\n aria-label={labels.continueChat}\n onClick={() => onAction('continue')}\n >\n <MessageSquare className=\"size-4\" strokeWidth={1.75} />\n </button>\n {isArchived ? (\n <button\n type=\"button\"\n className={ghostIconButton}\n title={labels.unarchive}\n aria-label={labels.unarchive}\n onClick={() => onAction('unarchive')}\n >\n <ArchiveRestore className=\"size-4\" strokeWidth={1.75} />\n </button>\n ) : (\n <button\n type=\"button\"\n className={ghostIconButton}\n title={labels.archive}\n aria-label={labels.archive}\n onClick={() => onAction('archive')}\n >\n <Archive className=\"size-4\" strokeWidth={1.75} />\n </button>\n )}\n {isPinned ? (\n <button\n type=\"button\"\n className={ghostIconButton}\n title={labels.unpin}\n aria-label={labels.unpin}\n onClick={() => onAction('unpin')}\n >\n <PinOff className=\"size-4\" strokeWidth={1.75} />\n </button>\n ) : (\n <button\n type=\"button\"\n className={ghostIconButton}\n title={labels.pin}\n aria-label={labels.pin}\n onClick={() => onAction('pin')}\n >\n <Pin className=\"size-4\" strokeWidth={1.75} />\n </button>\n )}\n <button\n type=\"button\"\n className={ghostIconButton}\n title={labels.export}\n aria-label={labels.export}\n onClick={() => onAction('export')}\n >\n <Download className=\"size-4\" strokeWidth={1.75} />\n </button>\n <button\n type=\"button\"\n className={cn(\n ghostIconButton,\n 'text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950/40',\n )}\n title={labels.delete}\n aria-label={labels.delete}\n onClick={() => onAction('delete')}\n >\n <Trash2 className=\"size-4\" strokeWidth={1.75} />\n </button>\n </div>\n </div>\n );\n}\n","import * as Dialog from '@radix-ui/react-dialog';\nimport { X } from 'lucide-react';\n\nimport type { SessionDetail } from '@/features/sessions/session.types';\nimport { Button } from '@/components/ui/button';\nimport { cn } from '@/lib/cn';\n\nfunction previewContent(content: string | unknown[]): string {\n if (typeof content === 'string') {\n return content.length > 2000 ? `${content.slice(0, 2000)}…` : content;\n }\n try {\n const s = JSON.stringify(content, null, 2);\n return s.length > 2000 ? `${s.slice(0, 2000)}…` : s;\n } catch {\n return String(content);\n }\n}\n\nexport function SessionDetailDrawer({\n open,\n loading,\n session,\n labels,\n onClose,\n onArchive,\n onUnarchive,\n onPin,\n onUnpin,\n onExport,\n onDelete,\n}: {\n open: boolean;\n loading: boolean;\n session: SessionDetail | null;\n labels: {\n close: string;\n detailLoading: string;\n detailMessages: string;\n detailExport: string;\n archive: string;\n unarchive: string;\n pin: string;\n unpin: string;\n delete: string;\n unnamedSession: string;\n };\n onClose: () => void;\n onArchive: () => void;\n onUnarchive: () => void;\n onPin: () => void;\n onUnpin: () => void;\n onExport: () => void;\n onDelete: () => void;\n}) {\n const isArchived = session?.status === 'archived';\n const isPinned = session?.status === 'pinned';\n\n return (\n <Dialog.Root open={open} onOpenChange={(v) => !v && onClose()}>\n <Dialog.Portal>\n <Dialog.Overlay className=\"xopc-dialog-overlay fixed inset-0 z-50 bg-scrim\" />\n <Dialog.Content\n className={cn(\n 'xopc-drawer-right fixed right-0 top-0 z-50 flex h-full w-full max-w-lg flex-col border-l border-edge bg-surface-panel shadow-popover outline-none',\n 'dark:border-edge',\n )}\n aria-describedby={undefined}\n >\n <div className=\"flex min-w-0 shrink-0 items-center justify-between gap-2 border-b border-edge px-4 py-3 dark:border-edge\">\n <Dialog.Title className=\"min-w-0 flex-1 truncate text-base font-semibold tracking-tight text-fg\">\n {session?.name?.trim() || labels.unnamedSession}\n </Dialog.Title>\n <Dialog.Close asChild>\n <Button type=\"button\" variant=\"ghost\" className=\"h-9 w-9 shrink-0 p-0\" aria-label={labels.close}>\n <X className=\"size-5\" strokeWidth={1.75} />\n </Button>\n </Dialog.Close>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto px-4 py-3\">\n {loading ? (\n <p className=\"text-sm text-fg-muted\">{labels.detailLoading}</p>\n ) : session ? (\n <>\n <dl className=\"mb-4 grid gap-2 text-xs text-fg-muted\">\n <div>\n <dt className=\"text-fg-disabled\">Key</dt>\n <dd className=\"mt-0.5 break-all font-mono text-fg\">{session.key}</dd>\n </div>\n <div className=\"flex flex-wrap gap-4\">\n <span>\n {session.messageCount} msgs · {session.estimatedTokens} tok\n </span>\n </div>\n </dl>\n <h3 className=\"mb-2 text-xs font-medium uppercase tracking-wide text-fg-subtle\">\n {labels.detailMessages}\n </h3>\n <ul className=\"space-y-3\">\n {session.messages.map((msg, i) => (\n <li\n key={`${msg.timestamp ?? i}-${i}`}\n className=\"rounded-lg border border-edge-subtle bg-surface-hover/50 p-2 dark:border-edge\"\n >\n <div className=\"mb-1 text-[10px] font-medium uppercase text-fg-subtle\">{msg.role}</div>\n <pre className=\"max-h-40 overflow-auto whitespace-pre-wrap break-words font-mono text-[11px] leading-relaxed text-fg-muted\">\n {previewContent(msg.content)}\n </pre>\n </li>\n ))}\n </ul>\n </>\n ) : null}\n </div>\n\n <div className=\"shrink-0 border-t border-edge px-4 py-3 dark:border-edge\">\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" variant=\"secondary\" className=\"text-sm\" onClick={onExport}>\n {labels.detailExport}\n </Button>\n {isArchived ? (\n <Button type=\"button\" variant=\"secondary\" className=\"text-sm\" onClick={onUnarchive}>\n {labels.unarchive}\n </Button>\n ) : (\n <Button type=\"button\" variant=\"secondary\" className=\"text-sm\" onClick={onArchive}>\n {labels.archive}\n </Button>\n )}\n {isPinned ? (\n <Button type=\"button\" variant=\"secondary\" className=\"text-sm\" onClick={onUnpin}>\n {labels.unpin}\n </Button>\n ) : (\n <Button type=\"button\" variant=\"secondary\" className=\"text-sm\" onClick={onPin}>\n {labels.pin}\n </Button>\n )}\n <Button\n type=\"button\"\n variant=\"secondary\"\n className=\"text-sm text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950/40\"\n onClick={onDelete}\n >\n {labels.delete}\n </Button>\n </div>\n </div>\n </Dialog.Content>\n </Dialog.Portal>\n </Dialog.Root>\n );\n}\n","import * as Dialog from '@radix-ui/react-dialog';\nimport {\n Archive,\n Circle,\n FolderOpen,\n Layers,\n LayoutGrid,\n LayoutList,\n Pin,\n Search,\n} from 'lucide-react';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\n\nimport { SessionCard, type SessionCardAction } from '@/features/sessions/session-card';\nimport { SessionDetailDrawer } from '@/features/sessions/session-detail-drawer';\nimport {\n archiveSession,\n deleteSession,\n exportSessionJson,\n getSessionDetail,\n getSessionStats,\n listSessions,\n pinSession,\n unarchiveSession,\n unpinSession,\n} from '@/features/sessions/session-api';\nimport type { SessionDetail, SessionMetadata, SessionStats } from '@/features/sessions/session.types';\nimport { Button } from '@/components/ui/button';\nimport {\n segmentedThumbActiveClassName,\n segmentedThumbBaseClassName,\n segmentedTrackClassName,\n} from '@/components/ui/segmented-styles';\nimport { cn } from '@/lib/cn';\nimport { interaction } from '@/lib/interaction';\nimport { messages } from '@/i18n/messages';\nimport { useGatewayStore } from '@/stores/gateway-store';\nimport { useLocaleStore } from '@/stores/locale-store';\n\nconst PAGE_LIMIT = 20;\n\nfunction interpolate(template: string, params: Record<string, string | number>): string {\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key) => String(params[key] ?? ''));\n}\n\ntype StatusFilter = 'all' | 'active' | 'pinned' | 'archived';\ntype SessionsViewMode = 'grid' | 'list';\n\nconst SESSION_STATUS_FILTER_SET = new Set<StatusFilter>(['all', 'active', 'pinned', 'archived']);\nconst SESSION_VIEW_MODE_SET = new Set<SessionsViewMode>(['grid', 'list']);\n\nexport function SessionsPage() {\n const language = useLocaleStore((s) => s.language);\n const m = messages(language);\n const s = m.sessions;\n const token = useGatewayStore((st) => st.token);\n const hasToken = Boolean(token);\n const [searchParams, setSearchParams] = useSearchParams();\n\n const initialSearch = searchParams.get('q') ?? '';\n const initialStatus = searchParams.get('status');\n const initialView = searchParams.get('view');\n const initialChannel = searchParams.get('channel') ?? '';\n const initialStatusFilter: StatusFilter = SESSION_STATUS_FILTER_SET.has(initialStatus as StatusFilter)\n ? (initialStatus as StatusFilter)\n : 'all';\n const initialViewMode: SessionsViewMode = SESSION_VIEW_MODE_SET.has(initialView as SessionsViewMode)\n ? (initialView as SessionsViewMode)\n : 'grid';\n\n const [searchInput, setSearchInput] = useState(initialSearch);\n const [debouncedSearch, setDebouncedSearch] = useState(initialSearch.trim());\n const [statusFilter, setStatusFilter] = useState<StatusFilter>(initialStatusFilter);\n const [viewMode, setViewMode] = useState<SessionsViewMode>(initialViewMode);\n const [channelFilter, setChannelFilter] = useState(initialChannel.trim());\n\n const [sessions, setSessions] = useState<SessionMetadata[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [hasMore, setHasMore] = useState(false);\n const [stats, setStats] = useState<SessionStats | null>(null);\n\n const [detailOpen, setDetailOpen] = useState(false);\n const [detailLoading, setDetailLoading] = useState(false);\n const [detailSession, setDetailSession] = useState<SessionDetail | null>(null);\n\n const [confirmOpen, setConfirmOpen] = useState(false);\n const [confirmKey, setConfirmKey] = useState<string | null>(null);\n\n useEffect(() => {\n const t = setTimeout(() => setDebouncedSearch(searchInput.trim()), 300);\n return () => clearTimeout(t);\n }, [searchInput]);\n\n useEffect(() => {\n const nextQ = searchParams.get('q') ?? '';\n const nextStatusRaw = searchParams.get('status');\n const nextViewRaw = searchParams.get('view');\n const nextChannel = (searchParams.get('channel') ?? '').trim();\n const nextStatus: StatusFilter = SESSION_STATUS_FILTER_SET.has(nextStatusRaw as StatusFilter)\n ? (nextStatusRaw as StatusFilter)\n : 'all';\n const nextView: SessionsViewMode = SESSION_VIEW_MODE_SET.has(nextViewRaw as SessionsViewMode)\n ? (nextViewRaw as SessionsViewMode)\n : 'grid';\n const nextDebouncedQ = nextQ.trim();\n\n setSearchInput((prev) => (prev === nextQ ? prev : nextQ));\n setDebouncedSearch((prev) => (prev === nextDebouncedQ ? prev : nextDebouncedQ));\n setStatusFilter((prev) => (prev === nextStatus ? prev : nextStatus));\n setViewMode((prev) => (prev === nextView ? prev : nextView));\n setChannelFilter((prev) => (prev === nextChannel ? prev : nextChannel));\n }, [searchParams]);\n\n useEffect(() => {\n const params = new URLSearchParams(searchParams);\n const nextQ = debouncedSearch.trim();\n if (nextQ) params.set('q', nextQ);\n else params.delete('q');\n if (statusFilter !== 'all') params.set('status', statusFilter);\n else params.delete('status');\n if (viewMode !== 'grid') params.set('view', viewMode);\n else params.delete('view');\n if (channelFilter) params.set('channel', channelFilter);\n else params.delete('channel');\n const next = params.toString();\n if (next !== searchParams.toString()) {\n setSearchParams(params, { replace: true });\n }\n }, [debouncedSearch, searchParams, setSearchParams, statusFilter, viewMode, channelFilter]);\n\n useEffect(() => {\n if (!hasToken) return;\n let cancelled = false;\n (async () => {\n setLoading(true);\n setError(null);\n try {\n const result = await listSessions({\n limit: PAGE_LIMIT,\n offset: 0,\n ...(debouncedSearch ? { search: debouncedSearch } : {}),\n ...(statusFilter !== 'all' ? { status: statusFilter } : {}),\n ...(channelFilter ? { channel: channelFilter } : {}),\n });\n if (cancelled) return;\n setSessions(result.items);\n setHasMore(result.hasMore);\n } catch (e) {\n if (!cancelled) setError(e instanceof Error ? e.message : s.loadError);\n } finally {\n if (!cancelled) setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [hasToken, debouncedSearch, statusFilter, s.loadError]);\n\n useEffect(() => {\n if (!hasToken) return;\n void getSessionStats()\n .then(setStats)\n .catch(() => {});\n }, [hasToken]);\n\n useEffect(() => {\n if (!hasToken) return;\n const handler = (e: Event) => {\n const detail = (e as CustomEvent<{ key?: string; name?: string }>).detail;\n if (!detail?.key || detail.name === undefined) return;\n setSessions((prev) =>\n prev.map((row) => (row.key === detail.key ? { ...row, name: detail.name } : row)),\n );\n setDetailSession((prev) =>\n prev && prev.key === detail.key ? { ...prev, name: detail.name } : prev,\n );\n };\n window.addEventListener('session-updated', handler);\n return () => {\n window.removeEventListener('session-updated', handler);\n };\n }, [hasToken]);\n\n const loadMore = useCallback(async () => {\n if (!hasToken || loading || !hasMore) return;\n setLoading(true);\n setError(null);\n try {\n const result = await listSessions({\n limit: PAGE_LIMIT,\n offset: sessions.length,\n ...(debouncedSearch ? { search: debouncedSearch } : {}),\n ...(statusFilter !== 'all' ? { status: statusFilter } : {}),\n ...(channelFilter ? { channel: channelFilter } : {}),\n });\n setSessions((prev) => [...prev, ...result.items]);\n setHasMore(result.hasMore);\n } catch (e) {\n setError(e instanceof Error ? e.message : s.loadError);\n } finally {\n setLoading(false);\n }\n }, [\n hasToken,\n loading,\n hasMore,\n sessions.length,\n debouncedSearch,\n statusFilter,\n channelFilter,\n s.loadError,\n ]);\n\n const updateSessionStatus = useCallback((key: string, status: SessionMetadata['status']) => {\n setSessions((prev) => prev.map((row) => (row.key === key ? { ...row, status } : row)));\n setDetailSession((prev) => (prev && prev.key === key ? { ...prev, status } : prev));\n }, []);\n\n const openDetail = useCallback(async (key: string) => {\n setDetailOpen(true);\n setDetailLoading(true);\n setDetailSession(null);\n try {\n const session = await getSessionDetail(key);\n setDetailSession(session);\n } catch {\n setDetailOpen(false);\n } finally {\n setDetailLoading(false);\n }\n }, []);\n\n const handleCardOpen = (key: string) => {\n void openDetail(key);\n };\n\n const handleCardAction = async (key: string, action: SessionCardAction) => {\n if (action === 'continue') {\n window.dispatchEvent(\n new CustomEvent('navigate-to-chat', { detail: { sessionKey: key }, bubbles: true }),\n );\n return;\n }\n if (action === 'delete') {\n setConfirmKey(key);\n setConfirmOpen(true);\n return;\n }\n try {\n switch (action) {\n case 'archive':\n await archiveSession(key);\n updateSessionStatus(key, 'archived');\n break;\n case 'unarchive':\n await unarchiveSession(key);\n updateSessionStatus(key, 'active');\n break;\n case 'pin':\n await pinSession(key);\n updateSessionStatus(key, 'pinned');\n break;\n case 'unpin':\n await unpinSession(key);\n updateSessionStatus(key, 'active');\n break;\n case 'export': {\n const content = await exportSessionJson(key);\n const blob = new Blob([content], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `session-${key.replace(/[^a-z0-9]/gi, '_')}.json`;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n break;\n }\n default:\n break;\n }\n void getSessionStats().then(setStats).catch(() => {});\n } catch {\n /* toast optional */\n }\n };\n\n const runDelete = async (key: string) => {\n try {\n await deleteSession(key);\n setSessions((prev) => prev.filter((row) => row.key !== key));\n setDetailSession((prev) => (prev?.key === key ? null : prev));\n if (detailSession?.key === key) setDetailOpen(false);\n void getSessionStats().then(setStats).catch(() => {});\n } catch {\n /* ignore */\n }\n };\n\n const cardLabels = {\n continueChat: s.continueChat,\n archive: s.archive,\n unarchive: s.unarchive,\n pin: s.pin,\n unpin: s.unpin,\n export: s.export,\n delete: s.delete,\n unnamedSession: m.chat.newSession,\n };\n\n const detailLabels = {\n close: s.close,\n detailLoading: s.detailLoading,\n detailMessages: s.detailMessages,\n detailExport: s.detailExport,\n archive: s.archive,\n unarchive: s.unarchive,\n pin: s.pin,\n unpin: s.unpin,\n delete: s.delete,\n unnamedSession: m.chat.newSession,\n };\n\n const filters: { key: StatusFilter; label: string; icon: typeof Layers }[] = [\n { key: 'all', label: s.filterAll, icon: Layers },\n { key: 'active', label: s.filterActive, icon: Circle },\n { key: 'pinned', label: s.filterPinned, icon: Pin },\n { key: 'archived', label: s.filterArchived, icon: Archive },\n ];\n\n const channelChips = (() => {\n const entries = Object.entries(stats?.byChannel ?? {}).sort((a, b) => b[1] - a[1]);\n const top = entries.slice(0, 6).map(([id]) => id);\n // Keep a few common channels visible even if no stats yet.\n for (const c of ['telegram', 'weixin', 'feishu']) {\n if (!top.includes(c)) top.push(c);\n }\n return top.slice(0, 8);\n })();\n\n if (!hasToken) {\n return (\n <div className=\"mx-auto w-full max-w-2xl px-4 py-16 text-center text-sm text-fg-muted sm:px-8 lg:max-w-app-main\">\n {s.needToken}\n </div>\n );\n }\n\n return (\n <div className=\"flex min-h-0 min-w-0 flex-1 flex-col overflow-y-auto overflow-x-hidden bg-surface-panel\">\n <div className=\"mx-auto flex w-full min-w-0 max-w-2xl flex-col gap-4 px-4 py-6 sm:px-6 lg:max-w-app-main lg:px-8\">\n <header className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <h1 className=\"flex items-center gap-2 text-xl font-semibold tracking-tight text-fg\">\n <FolderOpen className=\"size-5 shrink-0 text-fg-muted\" strokeWidth={1.75} aria-hidden />\n {s.title}\n </h1>\n <div className=\"flex w-full min-w-0 items-center gap-2 rounded-xl bg-surface-base px-3 py-2 transition-colors sm:max-w-md dark:bg-surface-hover/40\">\n <Search className=\"size-4 shrink-0 text-fg-disabled\" strokeWidth={1.75} aria-hidden />\n <input\n type=\"search\"\n value={searchInput}\n onChange={(e) => setSearchInput(e.target.value)}\n placeholder={s.searchPlaceholder}\n className=\"min-w-0 flex-1 border-0 bg-transparent text-sm text-fg placeholder:text-fg-disabled focus:outline-none focus:ring-0\"\n />\n </div>\n </header>\n\n <div className=\"flex flex-wrap gap-2\">\n {filters.map(({ key, label, icon: Icon }) => (\n <button\n key={key}\n type=\"button\"\n aria-pressed={statusFilter === key}\n onClick={() => setStatusFilter(key)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-xl px-3 py-2 text-sm font-medium',\n interaction.transition,\n /* Filter chips (selection): no press scale. */\n interaction.focusRingPanel,\n statusFilter === key\n ? 'bg-accent-soft text-accent-fg'\n : 'bg-surface-base text-fg-muted hover:bg-surface-hover hover:text-fg dark:bg-surface-hover/35',\n )}\n >\n <Icon className=\"size-4\" strokeWidth={1.75} aria-hidden />\n {label}\n </button>\n ))}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-xs font-medium text-fg-subtle\">{s.filterChannelLabel}</span>\n <button\n type=\"button\"\n aria-pressed={!channelFilter}\n onClick={() => setChannelFilter('')}\n className={cn(\n 'inline-flex items-center rounded-xl px-3 py-2 text-sm font-medium',\n interaction.transition,\n interaction.focusRingPanel,\n !channelFilter\n ? 'bg-accent-soft text-accent-fg'\n : 'bg-surface-base text-fg-muted hover:bg-surface-hover hover:text-fg dark:bg-surface-hover/35',\n )}\n >\n {s.filterChannelAll}\n </button>\n {channelChips.map((chId) => (\n <button\n key={chId}\n type=\"button\"\n aria-pressed={channelFilter === chId}\n onClick={() => setChannelFilter(chId)}\n className={cn(\n 'inline-flex items-center rounded-xl px-3 py-2 text-sm font-medium',\n interaction.transition,\n interaction.focusRingPanel,\n channelFilter === chId\n ? 'bg-accent-soft text-accent-fg'\n : 'bg-surface-base text-fg-muted hover:bg-surface-hover hover:text-fg dark:bg-surface-hover/35',\n )}\n >\n {chId}\n {stats?.byChannel?.[chId] != null ? (\n <span className=\"ml-2 rounded-full bg-surface-hover px-2 py-0.5 text-[11px] text-fg-subtle\">\n {stats.byChannel[chId]}\n </span>\n ) : null}\n </button>\n ))}\n </div>\n\n {stats ? (\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n {[\n [stats.totalSessions, s.totalSessions],\n [stats.activeSessions, s.activeSessions],\n [stats.pinnedSessions, s.pinnedSessions],\n [stats.archivedSessions, s.archivedSessions],\n ].map(([value, label]) => (\n <div\n key={label}\n className=\"rounded-xl bg-surface-base px-3 py-3 dark:bg-surface-hover/30\"\n >\n <div className=\"text-lg font-semibold tabular-nums text-fg\">{value}</div>\n <div className=\"text-xs text-fg-muted\">{label}</div>\n </div>\n ))}\n </div>\n ) : null}\n\n {error ? (\n <div className=\"rounded-lg border border-edge bg-red-50 px-3 py-2 text-sm text-red-700 dark:border-edge dark:bg-red-950/40 dark:text-red-300\">\n {error}\n </div>\n ) : null}\n\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"text-xs text-fg-muted\">{interpolate(s.sessionCount, { count: sessions.length })}</p>\n <div className={segmentedTrackClassName} role=\"group\" aria-label={s.layoutToggleGroup}>\n <Button\n type=\"button\"\n variant=\"segmented\"\n title={s.gridView}\n aria-pressed={viewMode === 'grid'}\n onClick={() => setViewMode('grid')}\n className={cn(\n segmentedThumbBaseClassName,\n 'size-7 p-0',\n viewMode === 'grid' && segmentedThumbActiveClassName,\n viewMode === 'grid' && 'text-accent-fg',\n )}\n >\n <LayoutGrid className=\"size-3.5\" strokeWidth={1.5} />\n </Button>\n <Button\n type=\"button\"\n variant=\"segmented\"\n title={s.listView}\n aria-pressed={viewMode === 'list'}\n onClick={() => setViewMode('list')}\n className={cn(\n segmentedThumbBaseClassName,\n 'size-9 p-0',\n viewMode === 'list' && segmentedThumbActiveClassName,\n viewMode === 'list' && 'text-accent-fg',\n )}\n >\n <LayoutList className=\"size-3.5\" strokeWidth={1.5} />\n </Button>\n </div>\n </div>\n\n {loading && sessions.length === 0 ? (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div\n key={i}\n className=\"h-40 animate-pulse rounded-xl bg-surface-hover/60 dark:bg-surface-hover/40\"\n />\n ))}\n </div>\n ) : sessions.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center rounded-2xl bg-surface-base py-16 text-center dark:bg-surface-hover/25\">\n <FolderOpen className=\"mb-3 size-12 text-fg-disabled\" strokeWidth={1.25} aria-hidden />\n <p className=\"text-base font-semibold text-fg\">{s.noSessions}</p>\n <p className=\"mt-1 max-w-sm text-sm text-fg-muted\">{s.noSessionsDescription}</p>\n <Button\n variant=\"primary\"\n className=\"mt-6\"\n onClick={() => {\n window.dispatchEvent(new CustomEvent('navigate-to-chat', { detail: { sessionKey: '' }, bubbles: true }));\n }}\n >\n {s.startNewChat}\n </Button>\n </div>\n ) : (\n <>\n <div\n className={cn(\n 'grid min-w-0 gap-3',\n viewMode === 'grid' ? 'sm:grid-cols-2 lg:grid-cols-3' : 'grid-cols-1',\n )}\n >\n {sessions.map((session) => (\n <SessionCard\n key={session.key}\n session={session}\n variant={viewMode}\n labels={cardLabels}\n onOpen={() => handleCardOpen(session.key)}\n onAction={(action) => void handleCardAction(session.key, action)}\n />\n ))}\n </div>\n {hasMore ? (\n <div className=\"flex justify-center pt-2\">\n <Button type=\"button\" variant=\"secondary\" disabled={loading} onClick={() => void loadMore()}>\n {s.loadMore}\n </Button>\n </div>\n ) : null}\n </>\n )}\n </div>\n\n <SessionDetailDrawer\n open={detailOpen}\n loading={detailLoading}\n session={detailSession}\n labels={detailLabels}\n onClose={() => {\n setDetailOpen(false);\n setDetailSession(null);\n }}\n onArchive={() => detailSession && void handleCardAction(detailSession.key, 'archive')}\n onUnarchive={() => detailSession && void handleCardAction(detailSession.key, 'unarchive')}\n onPin={() => detailSession && void handleCardAction(detailSession.key, 'pin')}\n onUnpin={() => detailSession && void handleCardAction(detailSession.key, 'unpin')}\n onExport={() => detailSession && void handleCardAction(detailSession.key, 'export')}\n onDelete={() => detailSession && (setConfirmKey(detailSession.key), setConfirmOpen(true))}\n />\n\n <Dialog.Root open={confirmOpen} onOpenChange={setConfirmOpen}>\n <Dialog.Portal>\n <Dialog.Overlay className=\"xopc-dialog-overlay fixed inset-0 z-[60] bg-scrim\" />\n <Dialog.Content className=\"xopc-dialog-content fixed left-1/2 top-1/2 z-[60] w-[min(100%-2rem,24rem)] -translate-x-1/2 -translate-y-1/2 rounded-xl border border-edge bg-surface-panel p-4 shadow-popover dark:border-edge\">\n <Dialog.Title className=\"text-base font-semibold text-fg\">{s.deleteSessionTitle}</Dialog.Title>\n <p className=\"mt-2 text-sm text-fg-muted\">\n {confirmKey\n ? interpolate(s.deleteSessionMessage, {\n name:\n sessions.find((x) => x.key === confirmKey)?.name?.trim() || m.chat.newSession,\n })\n : ''}\n </p>\n <div className=\"mt-4 flex justify-end gap-2\">\n <Button type=\"button\" variant=\"secondary\" onClick={() => setConfirmOpen(false)}>\n {s.cancel}\n </Button>\n <Button\n type=\"button\"\n variant=\"primary\"\n className=\"bg-red-600 hover:bg-red-700\"\n onClick={() => {\n if (confirmKey) void runDelete(confirmKey);\n setConfirmOpen(false);\n setConfirmKey(null);\n }}\n >\n {s.delete}\n </Button>\n </div>\n </Dialog.Content>\n </Dialog.Portal>\n </Dialog.Root>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sessions-page-Cle4fPla.js","names":[],"sources":["../../../../../web/src/features/sessions/session-card.tsx","../../../../../web/src/features/sessions/session-detail-drawer.tsx","../../../../../web/src/features/sessions/sessions-page.tsx"],"sourcesContent":["import {\n Archive,\n ArchiveRestore,\n Download,\n MessageSquare,\n Pin,\n PinOff,\n Trash2,\n Zap,\n} from 'lucide-react';\n\nimport type { SessionMetadata } from '@/features/sessions/session.types';\nimport { ghostIconButton } from '@/lib/interaction';\nimport { cn } from '@/lib/cn';\n\nexport type SessionCardAction =\n | 'continue'\n | 'delete'\n | 'archive'\n | 'unarchive'\n | 'pin'\n | 'unpin'\n | 'export';\n\nfunction formatRelativeDate(dateStr: string): string {\n const date = new Date(dateStr);\n const now = new Date();\n const diffDays = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));\n if (diffDays === 0) {\n return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n }\n if (diffDays === 1) return 'Yesterday';\n if (diffDays < 7) return date.toLocaleDateString([], { weekday: 'short' });\n return date.toLocaleDateString([], { month: 'short', day: 'numeric' });\n}\n\nfunction formatTokens(tokens: number): string {\n if (tokens >= 1000) return `${(tokens / 1000).toFixed(1)}k`;\n return String(tokens);\n}\n\nexport function SessionCard({\n session,\n variant,\n labels,\n onOpen,\n onAction,\n}: {\n session: SessionMetadata;\n variant: 'grid' | 'list';\n labels: {\n continueChat: string;\n archive: string;\n unarchive: string;\n pin: string;\n unpin: string;\n export: string;\n delete: string;\n /** Shown when `session.name` is empty (e.g. new chat before auto-title). */\n unnamedSession: string;\n };\n onOpen: () => void;\n onAction: (action: SessionCardAction) => void;\n}) {\n const displayName = session.name?.trim() || labels.unnamedSession;\n const showKeySubtitle = Boolean(session.name?.trim());\n const isArchived = session.status === 'archived';\n const isPinned = session.status === 'pinned';\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n className={cn(\n // min-w-0: grid/flex children default to min-width:auto — long unbroken titles (URLs) otherwise expand the track\n 'group flex min-w-0 w-full max-w-full cursor-pointer flex-col rounded-xl bg-surface-base text-left transition-colors duration-150 ease-out',\n 'hover:bg-surface-hover active:scale-[0.99]',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-surface-panel',\n variant === 'list' && 'sm:flex-row sm:items-center sm:gap-4',\n )}\n onClick={onOpen}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onOpen();\n }\n }}\n >\n <div\n className={cn(\n 'flex min-w-0 items-start justify-between gap-2 bg-surface-hover/35 px-3 py-2 dark:bg-surface-hover/25',\n variant === 'list' && 'sm:py-3',\n )}\n >\n <div className=\"flex min-w-0 items-center gap-2\">\n <span className=\"truncate text-[11px] font-medium uppercase tracking-wide text-fg-subtle\">\n {session.sourceChannel}\n </span>\n </div>\n <div className=\"flex shrink-0 items-center gap-1.5 text-xs text-fg-muted\">\n {isPinned ? <Pin className=\"size-3.5 text-accent-fg\" strokeWidth={1.75} aria-hidden /> : null}\n <span>{formatRelativeDate(session.updatedAt)}</span>\n </div>\n </div>\n\n <div className={cn('min-w-0 flex-1 px-3 py-2', variant === 'list' && 'sm:py-3')}>\n <div className=\"min-w-0 max-w-full truncate text-sm font-semibold text-fg\" title={displayName}>\n {displayName}\n </div>\n {showKeySubtitle ? (\n <div\n className=\"mt-0.5 min-w-0 max-w-full truncate font-mono text-[11px] text-fg-subtle\"\n title={session.key}\n >\n {session.key}\n </div>\n ) : null}\n <div className=\"mt-2 flex flex-wrap items-center gap-3 text-xs text-fg-muted\">\n <span className=\"inline-flex items-center gap-1\">\n <MessageSquare className=\"size-3.5\" strokeWidth={1.75} aria-hidden />\n {session.messageCount}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Zap className=\"size-3.5\" strokeWidth={1.75} aria-hidden />\n {formatTokens(session.estimatedTokens)}\n </span>\n </div>\n {session.tags.length > 0 ? (\n <div className=\"mt-2 flex flex-wrap gap-1\">\n {session.tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n className=\"max-w-full break-words rounded-md bg-surface-hover px-1.5 py-0.5 text-[11px] text-fg-muted\"\n >\n {tag}\n </span>\n ))}\n {session.tags.length > 3 ? (\n <span className=\"text-[11px] text-fg-disabled\">+{session.tags.length - 3}</span>\n ) : null}\n </div>\n ) : null}\n </div>\n\n <div\n className=\"flex flex-wrap items-center gap-0.5 border-t border-edge-subtle/80 bg-surface-hover/25 px-2 py-2 dark:border-edge-subtle\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n <button\n type=\"button\"\n className={ghostIconButton}\n title={labels.continueChat}\n aria-label={labels.continueChat}\n onClick={() => onAction('continue')}\n >\n <MessageSquare className=\"size-4\" strokeWidth={1.75} />\n </button>\n {isArchived ? (\n <button\n type=\"button\"\n className={ghostIconButton}\n title={labels.unarchive}\n aria-label={labels.unarchive}\n onClick={() => onAction('unarchive')}\n >\n <ArchiveRestore className=\"size-4\" strokeWidth={1.75} />\n </button>\n ) : (\n <button\n type=\"button\"\n className={ghostIconButton}\n title={labels.archive}\n aria-label={labels.archive}\n onClick={() => onAction('archive')}\n >\n <Archive className=\"size-4\" strokeWidth={1.75} />\n </button>\n )}\n {isPinned ? (\n <button\n type=\"button\"\n className={ghostIconButton}\n title={labels.unpin}\n aria-label={labels.unpin}\n onClick={() => onAction('unpin')}\n >\n <PinOff className=\"size-4\" strokeWidth={1.75} />\n </button>\n ) : (\n <button\n type=\"button\"\n className={ghostIconButton}\n title={labels.pin}\n aria-label={labels.pin}\n onClick={() => onAction('pin')}\n >\n <Pin className=\"size-4\" strokeWidth={1.75} />\n </button>\n )}\n <button\n type=\"button\"\n className={ghostIconButton}\n title={labels.export}\n aria-label={labels.export}\n onClick={() => onAction('export')}\n >\n <Download className=\"size-4\" strokeWidth={1.75} />\n </button>\n <button\n type=\"button\"\n className={cn(\n ghostIconButton,\n 'text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950/40',\n )}\n title={labels.delete}\n aria-label={labels.delete}\n onClick={() => onAction('delete')}\n >\n <Trash2 className=\"size-4\" strokeWidth={1.75} />\n </button>\n </div>\n </div>\n );\n}\n","import * as Dialog from '@radix-ui/react-dialog';\nimport { X } from 'lucide-react';\n\nimport type { SessionDetail } from '@/features/sessions/session.types';\nimport { Button } from '@/components/ui/button';\nimport { cn } from '@/lib/cn';\n\nfunction previewContent(content: string | unknown[]): string {\n if (typeof content === 'string') {\n return content.length > 2000 ? `${content.slice(0, 2000)}…` : content;\n }\n try {\n const s = JSON.stringify(content, null, 2);\n return s.length > 2000 ? `${s.slice(0, 2000)}…` : s;\n } catch {\n return String(content);\n }\n}\n\nexport function SessionDetailDrawer({\n open,\n loading,\n session,\n labels,\n onClose,\n onArchive,\n onUnarchive,\n onPin,\n onUnpin,\n onExport,\n onDelete,\n}: {\n open: boolean;\n loading: boolean;\n session: SessionDetail | null;\n labels: {\n close: string;\n detailLoading: string;\n detailMessages: string;\n detailExport: string;\n archive: string;\n unarchive: string;\n pin: string;\n unpin: string;\n delete: string;\n unnamedSession: string;\n };\n onClose: () => void;\n onArchive: () => void;\n onUnarchive: () => void;\n onPin: () => void;\n onUnpin: () => void;\n onExport: () => void;\n onDelete: () => void;\n}) {\n const isArchived = session?.status === 'archived';\n const isPinned = session?.status === 'pinned';\n\n return (\n <Dialog.Root open={open} onOpenChange={(v) => !v && onClose()}>\n <Dialog.Portal>\n <Dialog.Overlay className=\"xopc-dialog-overlay fixed inset-0 z-50 bg-scrim\" />\n <Dialog.Content\n className={cn(\n 'xopc-drawer-right fixed right-0 top-0 z-50 flex h-full w-full max-w-lg flex-col border-l border-edge bg-surface-panel shadow-popover outline-none',\n 'dark:border-edge',\n )}\n aria-describedby={undefined}\n >\n <div className=\"flex min-w-0 shrink-0 items-center justify-between gap-2 border-b border-edge px-4 py-3 dark:border-edge\">\n <Dialog.Title className=\"min-w-0 flex-1 truncate text-base font-semibold tracking-tight text-fg\">\n {session?.name?.trim() || labels.unnamedSession}\n </Dialog.Title>\n <Dialog.Close asChild>\n <Button type=\"button\" variant=\"ghost\" className=\"h-9 w-9 shrink-0 p-0\" aria-label={labels.close}>\n <X className=\"size-5\" strokeWidth={1.75} />\n </Button>\n </Dialog.Close>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto px-4 py-3\">\n {loading ? (\n <p className=\"text-sm text-fg-muted\">{labels.detailLoading}</p>\n ) : session ? (\n <>\n <dl className=\"mb-4 grid gap-2 text-xs text-fg-muted\">\n <div>\n <dt className=\"text-fg-disabled\">Key</dt>\n <dd className=\"mt-0.5 break-all font-mono text-fg\">{session.key}</dd>\n </div>\n <div className=\"flex flex-wrap gap-4\">\n <span>\n {session.messageCount} msgs · {session.estimatedTokens} tok\n </span>\n </div>\n </dl>\n <h3 className=\"mb-2 text-xs font-medium uppercase tracking-wide text-fg-subtle\">\n {labels.detailMessages}\n </h3>\n <ul className=\"space-y-3\">\n {session.messages.map((msg, i) => (\n <li\n key={`${msg.timestamp ?? i}-${i}`}\n className=\"rounded-lg border border-edge-subtle bg-surface-hover/50 p-2 dark:border-edge\"\n >\n <div className=\"mb-1 text-[10px] font-medium uppercase text-fg-subtle\">{msg.role}</div>\n <pre className=\"max-h-40 overflow-auto whitespace-pre-wrap break-words font-mono text-[11px] leading-relaxed text-fg-muted\">\n {previewContent(msg.content)}\n </pre>\n </li>\n ))}\n </ul>\n </>\n ) : null}\n </div>\n\n <div className=\"shrink-0 border-t border-edge px-4 py-3 dark:border-edge\">\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" variant=\"secondary\" className=\"text-sm\" onClick={onExport}>\n {labels.detailExport}\n </Button>\n {isArchived ? (\n <Button type=\"button\" variant=\"secondary\" className=\"text-sm\" onClick={onUnarchive}>\n {labels.unarchive}\n </Button>\n ) : (\n <Button type=\"button\" variant=\"secondary\" className=\"text-sm\" onClick={onArchive}>\n {labels.archive}\n </Button>\n )}\n {isPinned ? (\n <Button type=\"button\" variant=\"secondary\" className=\"text-sm\" onClick={onUnpin}>\n {labels.unpin}\n </Button>\n ) : (\n <Button type=\"button\" variant=\"secondary\" className=\"text-sm\" onClick={onPin}>\n {labels.pin}\n </Button>\n )}\n <Button\n type=\"button\"\n variant=\"secondary\"\n className=\"text-sm text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950/40\"\n onClick={onDelete}\n >\n {labels.delete}\n </Button>\n </div>\n </div>\n </Dialog.Content>\n </Dialog.Portal>\n </Dialog.Root>\n );\n}\n","import * as Dialog from '@radix-ui/react-dialog';\nimport {\n Archive,\n Circle,\n FolderOpen,\n Layers,\n LayoutGrid,\n LayoutList,\n Pin,\n Search,\n} from 'lucide-react';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\n\nimport { SessionCard, type SessionCardAction } from '@/features/sessions/session-card';\nimport { SessionDetailDrawer } from '@/features/sessions/session-detail-drawer';\nimport {\n archiveSession,\n deleteSession,\n exportSessionJson,\n getSessionDetail,\n getSessionStats,\n listSessions,\n pinSession,\n unarchiveSession,\n unpinSession,\n} from '@/features/sessions/session-api';\nimport type { SessionDetail, SessionMetadata, SessionStats } from '@/features/sessions/session.types';\nimport { Button } from '@/components/ui/button';\nimport {\n segmentedThumbActiveClassName,\n segmentedThumbBaseClassName,\n segmentedTrackClassName,\n} from '@/components/ui/segmented-styles';\nimport { cn } from '@/lib/cn';\nimport { interaction } from '@/lib/interaction';\nimport { messages } from '@/i18n/messages';\nimport { useGatewayStore } from '@/stores/gateway-store';\nimport { useLocaleStore } from '@/stores/locale-store';\n\nconst PAGE_LIMIT = 20;\n\nfunction interpolate(template: string, params: Record<string, string | number>): string {\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key) => String(params[key] ?? ''));\n}\n\ntype StatusFilter = 'all' | 'active' | 'pinned' | 'archived';\ntype SessionsViewMode = 'grid' | 'list';\n\nconst SESSION_STATUS_FILTER_SET = new Set<StatusFilter>(['all', 'active', 'pinned', 'archived']);\nconst SESSION_VIEW_MODE_SET = new Set<SessionsViewMode>(['grid', 'list']);\n\nexport function SessionsPage() {\n const language = useLocaleStore((s) => s.language);\n const m = messages(language);\n const s = m.sessions;\n const token = useGatewayStore((st) => st.token);\n const hasToken = Boolean(token);\n const [searchParams, setSearchParams] = useSearchParams();\n\n const initialSearch = searchParams.get('q') ?? '';\n const initialStatus = searchParams.get('status');\n const initialView = searchParams.get('view');\n const initialChannel = searchParams.get('channel') ?? '';\n const initialStatusFilter: StatusFilter = SESSION_STATUS_FILTER_SET.has(initialStatus as StatusFilter)\n ? (initialStatus as StatusFilter)\n : 'all';\n const initialViewMode: SessionsViewMode = SESSION_VIEW_MODE_SET.has(initialView as SessionsViewMode)\n ? (initialView as SessionsViewMode)\n : 'grid';\n\n const [searchInput, setSearchInput] = useState(initialSearch);\n const [debouncedSearch, setDebouncedSearch] = useState(initialSearch.trim());\n const [statusFilter, setStatusFilter] = useState<StatusFilter>(initialStatusFilter);\n const [viewMode, setViewMode] = useState<SessionsViewMode>(initialViewMode);\n const [channelFilter, setChannelFilter] = useState(initialChannel.trim());\n\n const [sessions, setSessions] = useState<SessionMetadata[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [hasMore, setHasMore] = useState(false);\n const [stats, setStats] = useState<SessionStats | null>(null);\n\n const [detailOpen, setDetailOpen] = useState(false);\n const [detailLoading, setDetailLoading] = useState(false);\n const [detailSession, setDetailSession] = useState<SessionDetail | null>(null);\n\n const [confirmOpen, setConfirmOpen] = useState(false);\n const [confirmKey, setConfirmKey] = useState<string | null>(null);\n\n useEffect(() => {\n const t = setTimeout(() => setDebouncedSearch(searchInput.trim()), 300);\n return () => clearTimeout(t);\n }, [searchInput]);\n\n useEffect(() => {\n const nextQ = searchParams.get('q') ?? '';\n const nextStatusRaw = searchParams.get('status');\n const nextViewRaw = searchParams.get('view');\n const nextChannel = (searchParams.get('channel') ?? '').trim();\n const nextStatus: StatusFilter = SESSION_STATUS_FILTER_SET.has(nextStatusRaw as StatusFilter)\n ? (nextStatusRaw as StatusFilter)\n : 'all';\n const nextView: SessionsViewMode = SESSION_VIEW_MODE_SET.has(nextViewRaw as SessionsViewMode)\n ? (nextViewRaw as SessionsViewMode)\n : 'grid';\n const nextDebouncedQ = nextQ.trim();\n\n setSearchInput((prev) => (prev === nextQ ? prev : nextQ));\n setDebouncedSearch((prev) => (prev === nextDebouncedQ ? prev : nextDebouncedQ));\n setStatusFilter((prev) => (prev === nextStatus ? prev : nextStatus));\n setViewMode((prev) => (prev === nextView ? prev : nextView));\n setChannelFilter((prev) => (prev === nextChannel ? prev : nextChannel));\n }, [searchParams]);\n\n useEffect(() => {\n const params = new URLSearchParams(searchParams);\n const nextQ = debouncedSearch.trim();\n if (nextQ) params.set('q', nextQ);\n else params.delete('q');\n if (statusFilter !== 'all') params.set('status', statusFilter);\n else params.delete('status');\n if (viewMode !== 'grid') params.set('view', viewMode);\n else params.delete('view');\n if (channelFilter) params.set('channel', channelFilter);\n else params.delete('channel');\n const next = params.toString();\n if (next !== searchParams.toString()) {\n setSearchParams(params, { replace: true });\n }\n }, [debouncedSearch, searchParams, setSearchParams, statusFilter, viewMode, channelFilter]);\n\n useEffect(() => {\n if (!hasToken) return;\n let cancelled = false;\n (async () => {\n setLoading(true);\n setError(null);\n try {\n const result = await listSessions({\n limit: PAGE_LIMIT,\n offset: 0,\n ...(debouncedSearch ? { search: debouncedSearch } : {}),\n ...(statusFilter !== 'all' ? { status: statusFilter } : {}),\n ...(channelFilter ? { channel: channelFilter } : {}),\n });\n if (cancelled) return;\n setSessions(result.items);\n setHasMore(result.hasMore);\n } catch (e) {\n if (!cancelled) setError(e instanceof Error ? e.message : s.loadError);\n } finally {\n if (!cancelled) setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [hasToken, debouncedSearch, statusFilter, s.loadError]);\n\n useEffect(() => {\n if (!hasToken) return;\n void getSessionStats()\n .then(setStats)\n .catch(() => {});\n }, [hasToken]);\n\n useEffect(() => {\n if (!hasToken) return;\n const handler = (e: Event) => {\n const detail = (e as CustomEvent<{ key?: string; name?: string }>).detail;\n if (!detail?.key || detail.name === undefined) return;\n setSessions((prev) =>\n prev.map((row) => (row.key === detail.key ? { ...row, name: detail.name } : row)),\n );\n setDetailSession((prev) =>\n prev && prev.key === detail.key ? { ...prev, name: detail.name } : prev,\n );\n };\n window.addEventListener('session-updated', handler);\n return () => {\n window.removeEventListener('session-updated', handler);\n };\n }, [hasToken]);\n\n const loadMore = useCallback(async () => {\n if (!hasToken || loading || !hasMore) return;\n setLoading(true);\n setError(null);\n try {\n const result = await listSessions({\n limit: PAGE_LIMIT,\n offset: sessions.length,\n ...(debouncedSearch ? { search: debouncedSearch } : {}),\n ...(statusFilter !== 'all' ? { status: statusFilter } : {}),\n ...(channelFilter ? { channel: channelFilter } : {}),\n });\n setSessions((prev) => [...prev, ...result.items]);\n setHasMore(result.hasMore);\n } catch (e) {\n setError(e instanceof Error ? e.message : s.loadError);\n } finally {\n setLoading(false);\n }\n }, [\n hasToken,\n loading,\n hasMore,\n sessions.length,\n debouncedSearch,\n statusFilter,\n channelFilter,\n s.loadError,\n ]);\n\n const updateSessionStatus = useCallback((key: string, status: SessionMetadata['status']) => {\n setSessions((prev) => prev.map((row) => (row.key === key ? { ...row, status } : row)));\n setDetailSession((prev) => (prev && prev.key === key ? { ...prev, status } : prev));\n }, []);\n\n const openDetail = useCallback(async (key: string) => {\n setDetailOpen(true);\n setDetailLoading(true);\n setDetailSession(null);\n try {\n const session = await getSessionDetail(key);\n setDetailSession(session);\n } catch {\n setDetailOpen(false);\n } finally {\n setDetailLoading(false);\n }\n }, []);\n\n const handleCardOpen = (key: string) => {\n void openDetail(key);\n };\n\n const handleCardAction = async (key: string, action: SessionCardAction) => {\n if (action === 'continue') {\n window.dispatchEvent(\n new CustomEvent('navigate-to-chat', { detail: { sessionKey: key }, bubbles: true }),\n );\n return;\n }\n if (action === 'delete') {\n setConfirmKey(key);\n setConfirmOpen(true);\n return;\n }\n try {\n switch (action) {\n case 'archive':\n await archiveSession(key);\n updateSessionStatus(key, 'archived');\n break;\n case 'unarchive':\n await unarchiveSession(key);\n updateSessionStatus(key, 'active');\n break;\n case 'pin':\n await pinSession(key);\n updateSessionStatus(key, 'pinned');\n break;\n case 'unpin':\n await unpinSession(key);\n updateSessionStatus(key, 'active');\n break;\n case 'export': {\n const content = await exportSessionJson(key);\n const blob = new Blob([content], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `session-${key.replace(/[^a-z0-9]/gi, '_')}.json`;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n break;\n }\n default:\n break;\n }\n void getSessionStats().then(setStats).catch(() => {});\n } catch {\n /* toast optional */\n }\n };\n\n const runDelete = async (key: string) => {\n try {\n await deleteSession(key);\n setSessions((prev) => prev.filter((row) => row.key !== key));\n setDetailSession((prev) => (prev?.key === key ? null : prev));\n if (detailSession?.key === key) setDetailOpen(false);\n void getSessionStats().then(setStats).catch(() => {});\n } catch {\n /* ignore */\n }\n };\n\n const cardLabels = {\n continueChat: s.continueChat,\n archive: s.archive,\n unarchive: s.unarchive,\n pin: s.pin,\n unpin: s.unpin,\n export: s.export,\n delete: s.delete,\n unnamedSession: m.chat.newSession,\n };\n\n const detailLabels = {\n close: s.close,\n detailLoading: s.detailLoading,\n detailMessages: s.detailMessages,\n detailExport: s.detailExport,\n archive: s.archive,\n unarchive: s.unarchive,\n pin: s.pin,\n unpin: s.unpin,\n delete: s.delete,\n unnamedSession: m.chat.newSession,\n };\n\n const filters: { key: StatusFilter; label: string; icon: typeof Layers }[] = [\n { key: 'all', label: s.filterAll, icon: Layers },\n { key: 'active', label: s.filterActive, icon: Circle },\n { key: 'pinned', label: s.filterPinned, icon: Pin },\n { key: 'archived', label: s.filterArchived, icon: Archive },\n ];\n\n const channelChips = (() => {\n const entries = Object.entries(stats?.byChannel ?? {}).sort((a, b) => b[1] - a[1]);\n const top = entries.slice(0, 6).map(([id]) => id);\n // Keep a few common channels visible even if no stats yet.\n for (const c of ['telegram', 'weixin', 'feishu']) {\n if (!top.includes(c)) top.push(c);\n }\n return top.slice(0, 8);\n })();\n\n if (!hasToken) {\n return (\n <div className=\"mx-auto w-full max-w-2xl px-4 py-16 text-center text-sm text-fg-muted sm:px-8 lg:max-w-app-main\">\n {s.needToken}\n </div>\n );\n }\n\n return (\n <div className=\"flex min-h-0 min-w-0 flex-1 flex-col overflow-y-auto overflow-x-hidden bg-surface-panel\">\n <div className=\"mx-auto flex w-full min-w-0 max-w-2xl flex-col gap-4 px-4 py-6 sm:px-6 lg:max-w-app-main lg:px-8\">\n <header className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <h1 className=\"flex items-center gap-2 text-xl font-semibold tracking-tight text-fg\">\n <FolderOpen className=\"size-5 shrink-0 text-fg-muted\" strokeWidth={1.75} aria-hidden />\n {s.title}\n </h1>\n <div className=\"flex w-full min-w-0 items-center gap-2 rounded-xl bg-surface-base px-3 py-2 transition-colors sm:max-w-md dark:bg-surface-hover/40\">\n <Search className=\"size-4 shrink-0 text-fg-disabled\" strokeWidth={1.75} aria-hidden />\n <input\n type=\"search\"\n value={searchInput}\n onChange={(e) => setSearchInput(e.target.value)}\n placeholder={s.searchPlaceholder}\n className=\"min-w-0 flex-1 border-0 bg-transparent text-sm text-fg placeholder:text-fg-disabled focus:outline-none focus:ring-0\"\n />\n </div>\n </header>\n\n <div className=\"flex flex-wrap gap-2\">\n {filters.map(({ key, label, icon: Icon }) => (\n <button\n key={key}\n type=\"button\"\n aria-pressed={statusFilter === key}\n onClick={() => setStatusFilter(key)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-xl px-3 py-2 text-sm font-medium',\n interaction.transition,\n /* Filter chips (selection): no press scale. */\n interaction.focusRingPanel,\n statusFilter === key\n ? 'bg-accent-soft text-accent-fg'\n : 'bg-surface-base text-fg-muted hover:bg-surface-hover hover:text-fg dark:bg-surface-hover/35',\n )}\n >\n <Icon className=\"size-4\" strokeWidth={1.75} aria-hidden />\n {label}\n </button>\n ))}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-xs font-medium text-fg-subtle\">{s.filterChannelLabel}</span>\n <button\n type=\"button\"\n aria-pressed={!channelFilter}\n onClick={() => setChannelFilter('')}\n className={cn(\n 'inline-flex items-center rounded-xl px-3 py-2 text-sm font-medium',\n interaction.transition,\n interaction.focusRingPanel,\n !channelFilter\n ? 'bg-accent-soft text-accent-fg'\n : 'bg-surface-base text-fg-muted hover:bg-surface-hover hover:text-fg dark:bg-surface-hover/35',\n )}\n >\n {s.filterChannelAll}\n </button>\n {channelChips.map((chId) => (\n <button\n key={chId}\n type=\"button\"\n aria-pressed={channelFilter === chId}\n onClick={() => setChannelFilter(chId)}\n className={cn(\n 'inline-flex items-center rounded-xl px-3 py-2 text-sm font-medium',\n interaction.transition,\n interaction.focusRingPanel,\n channelFilter === chId\n ? 'bg-accent-soft text-accent-fg'\n : 'bg-surface-base text-fg-muted hover:bg-surface-hover hover:text-fg dark:bg-surface-hover/35',\n )}\n >\n {chId}\n {stats?.byChannel?.[chId] != null ? (\n <span className=\"ml-2 rounded-full bg-surface-hover px-2 py-0.5 text-[11px] text-fg-subtle\">\n {stats.byChannel[chId]}\n </span>\n ) : null}\n </button>\n ))}\n </div>\n\n {stats ? (\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n {[\n [stats.totalSessions, s.totalSessions],\n [stats.activeSessions, s.activeSessions],\n [stats.pinnedSessions, s.pinnedSessions],\n [stats.archivedSessions, s.archivedSessions],\n ].map(([value, label]) => (\n <div\n key={label}\n className=\"rounded-xl bg-surface-base px-3 py-3 dark:bg-surface-hover/30\"\n >\n <div className=\"text-lg font-semibold tabular-nums text-fg\">{value}</div>\n <div className=\"text-xs text-fg-muted\">{label}</div>\n </div>\n ))}\n </div>\n ) : null}\n\n {error ? (\n <div className=\"rounded-lg border border-edge bg-red-50 px-3 py-2 text-sm text-red-700 dark:border-edge dark:bg-red-950/40 dark:text-red-300\">\n {error}\n </div>\n ) : null}\n\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"text-xs text-fg-muted\">{interpolate(s.sessionCount, { count: sessions.length })}</p>\n <div className={segmentedTrackClassName} role=\"group\" aria-label={s.layoutToggleGroup}>\n <Button\n type=\"button\"\n variant=\"segmented\"\n title={s.gridView}\n aria-pressed={viewMode === 'grid'}\n onClick={() => setViewMode('grid')}\n className={cn(\n segmentedThumbBaseClassName,\n 'size-7 p-0',\n viewMode === 'grid' && segmentedThumbActiveClassName,\n viewMode === 'grid' && 'text-accent-fg',\n )}\n >\n <LayoutGrid className=\"size-3.5\" strokeWidth={1.5} />\n </Button>\n <Button\n type=\"button\"\n variant=\"segmented\"\n title={s.listView}\n aria-pressed={viewMode === 'list'}\n onClick={() => setViewMode('list')}\n className={cn(\n segmentedThumbBaseClassName,\n 'size-9 p-0',\n viewMode === 'list' && segmentedThumbActiveClassName,\n viewMode === 'list' && 'text-accent-fg',\n )}\n >\n <LayoutList className=\"size-3.5\" strokeWidth={1.5} />\n </Button>\n </div>\n </div>\n\n {loading && sessions.length === 0 ? (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div\n key={i}\n className=\"h-40 animate-pulse rounded-xl bg-surface-hover/60 dark:bg-surface-hover/40\"\n />\n ))}\n </div>\n ) : sessions.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center rounded-2xl bg-surface-base py-16 text-center dark:bg-surface-hover/25\">\n <FolderOpen className=\"mb-3 size-12 text-fg-disabled\" strokeWidth={1.25} aria-hidden />\n <p className=\"text-base font-semibold text-fg\">{s.noSessions}</p>\n <p className=\"mt-1 max-w-sm text-sm text-fg-muted\">{s.noSessionsDescription}</p>\n <Button\n variant=\"primary\"\n className=\"mt-6\"\n onClick={() => {\n window.dispatchEvent(new CustomEvent('navigate-to-chat', { detail: { sessionKey: '' }, bubbles: true }));\n }}\n >\n {s.startNewChat}\n </Button>\n </div>\n ) : (\n <>\n <div\n className={cn(\n 'grid min-w-0 gap-3',\n viewMode === 'grid' ? 'sm:grid-cols-2 lg:grid-cols-3' : 'grid-cols-1',\n )}\n >\n {sessions.map((session) => (\n <SessionCard\n key={session.key}\n session={session}\n variant={viewMode}\n labels={cardLabels}\n onOpen={() => handleCardOpen(session.key)}\n onAction={(action) => void handleCardAction(session.key, action)}\n />\n ))}\n </div>\n {hasMore ? (\n <div className=\"flex justify-center pt-2\">\n <Button type=\"button\" variant=\"secondary\" disabled={loading} onClick={() => void loadMore()}>\n {s.loadMore}\n </Button>\n </div>\n ) : null}\n </>\n )}\n </div>\n\n <SessionDetailDrawer\n open={detailOpen}\n loading={detailLoading}\n session={detailSession}\n labels={detailLabels}\n onClose={() => {\n setDetailOpen(false);\n setDetailSession(null);\n }}\n onArchive={() => detailSession && void handleCardAction(detailSession.key, 'archive')}\n onUnarchive={() => detailSession && void handleCardAction(detailSession.key, 'unarchive')}\n onPin={() => detailSession && void handleCardAction(detailSession.key, 'pin')}\n onUnpin={() => detailSession && void handleCardAction(detailSession.key, 'unpin')}\n onExport={() => detailSession && void handleCardAction(detailSession.key, 'export')}\n onDelete={() => detailSession && (setConfirmKey(detailSession.key), setConfirmOpen(true))}\n />\n\n <Dialog.Root open={confirmOpen} onOpenChange={setConfirmOpen}>\n <Dialog.Portal>\n <Dialog.Overlay className=\"xopc-dialog-overlay fixed inset-0 z-[60] bg-scrim\" />\n <Dialog.Content className=\"xopc-dialog-content fixed left-1/2 top-1/2 z-[60] w-[min(100%-2rem,24rem)] -translate-x-1/2 -translate-y-1/2 rounded-xl border border-edge bg-surface-panel p-4 shadow-popover dark:border-edge\">\n <Dialog.Title className=\"text-base font-semibold text-fg\">{s.deleteSessionTitle}</Dialog.Title>\n <p className=\"mt-2 text-sm text-fg-muted\">\n {confirmKey\n ? interpolate(s.deleteSessionMessage, {\n name:\n sessions.find((x) => x.key === confirmKey)?.name?.trim() || m.chat.newSession,\n })\n : ''}\n </p>\n <div className=\"mt-4 flex justify-end gap-2\">\n <Button type=\"button\" variant=\"secondary\" onClick={() => setConfirmOpen(false)}>\n {s.cancel}\n </Button>\n <Button\n type=\"button\"\n variant=\"primary\"\n className=\"bg-red-600 hover:bg-red-700\"\n onClick={() => {\n if (confirmKey) void runDelete(confirmKey);\n setConfirmOpen(false);\n setConfirmKey(null);\n }}\n >\n {s.delete}\n </Button>\n </div>\n </Dialog.Content>\n </Dialog.Portal>\n </Dialog.Root>\n </div>\n );\n}\n"],"mappings":"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"}