From 629cc3422412e95a6b19a5bbef88764af66ce712 Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Tue, 17 Feb 2026 14:43:37 +0100 Subject: [PATCH 1/8] webui: pre-MCP UI and architecture cleanup foundation --- .../ChatAttachmentPreview.svelte | 5 +- .../ChatMessages/ChatMessageActions.svelte | 19 ++- .../ChatMessages/ChatMessageSystem.svelte | 65 ++++--- .../ChatMessageThinkingBlock.svelte | 68 -------- .../chat/ChatMessages/ChatMessageUser.svelte | 73 ++------ .../app/chat/ChatMessages/ChatMessages.svelte | 159 +++++++++--------- .../app/chat/ChatScreen/ChatScreen.svelte | 112 ++++-------- .../app/models/ModelsSelector.svelte | 6 +- .../src/lib/constants/default-context.ts | 1 - .../webui/src/lib/constants/input-classes.ts | 1 - .../use-model-change-validation.svelte.ts | 104 ------------ .../webui/src/lib/stores/models.svelte.ts | 123 +++++++++----- 12 files changed, 268 insertions(+), 468 deletions(-) delete mode 100644 tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageThinkingBlock.svelte delete mode 100644 tools/server/webui/src/lib/constants/default-context.ts delete mode 100644 tools/server/webui/src/lib/constants/input-classes.ts delete mode 100644 tools/server/webui/src/lib/hooks/use-model-change-validation.svelte.ts diff --git a/tools/server/webui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentPreview.svelte b/tools/server/webui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentPreview.svelte index 0b0bf52ad9..f05bdd8a03 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentPreview.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentPreview.svelte @@ -8,7 +8,8 @@ isImageFile, isPdfFile, isAudioFile, - getLanguageFromFilename + getLanguageFromFilename, + createBase64DataUrl } from '$lib/utils'; import { convertPDFToImage } from '$lib/utils/browser-only'; import { modelsStore } from '$lib/stores/models.svelte'; @@ -255,7 +256,7 @@ diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageActions.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageActions.svelte index dbd9b98228..97b34e92cc 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageActions.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageActions.svelte @@ -1,14 +1,15 @@ - - - - -
- - - - {isStreaming ? 'Reasoning...' : 'Reasoning'} - -
- -
- - - Toggle reasoning content -
-
- - -
-
-
- {reasoningContent ?? ''} -
-
-
-
-
-
diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageUser.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageUser.svelte index 041c6bd251..05a02e2728 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageUser.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageUser.svelte @@ -1,67 +1,48 @@ -
- {#each displayMessages as { message, siblingInfo } (message.id)} +
+ {#each displayMessages as { message, isLastAssistantMessage, siblingInfo } (message.id)} {/each}
diff --git a/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte b/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte index 3d432e26bc..ceecf03e54 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte @@ -1,7 +1,7 @@ @@ -406,11 +363,8 @@ class="mb-16 md:mb-24" messages={activeMessages()} onUserAction={() => { - if (!disableAutoScroll) { - userScrolledUp = false; - autoScrollEnabled = true; - scrollChatToBottom(); - } + autoScroll.enable(); + autoScroll.scrollToBottom(); }} /> @@ -444,7 +398,7 @@ {/if}
-
-

llama.cpp

+

llama.cpp

{serverStore.props?.modalities?.audio @@ -504,7 +458,7 @@ {/if}

- diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte index c216ea690b..0f0e53b81b 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte @@ -1,79 +1,26 @@ - - -
- { - if (fileId.startsWith('attachment-')) { - const index = parseInt(fileId.replace('attachment-', ''), 10); - if (!isNaN(index) && index >= 0 && index < editedExtras.length) { - handleRemoveExistingAttachment(index); - } - } else { - handleRemoveUploadedFile(fileId); - } - }} - limitToSingleRow - class="py-5" - style="scroll-padding: 1rem;" +
+ - -
- - -
- - -
- - {#if isRouter} - - {/if} - - -
-
- {#if showSaveOnlyOption && onSaveEditOnly} + {#if editCtx.showSaveOnlyOption}
@@ -386,6 +137,6 @@ cancelText="Keep editing" variant="destructive" icon={AlertTriangle} - onConfirm={onCancelEdit} + onConfirm={editCtx.cancel} onCancel={() => (showDiscardDialog = false)} /> diff --git a/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreenForm.svelte b/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreenForm.svelte index 6a0c91346f..eb6f0d0f04 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreenForm.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreenForm.svelte @@ -1,5 +1,7 @@
+ + diff --git a/tools/server/webui/src/lib/components/app/chat/index.ts b/tools/server/webui/src/lib/components/app/chat/index.ts new file mode 100644 index 0000000000..1a2aeaa7be --- /dev/null +++ b/tools/server/webui/src/lib/components/app/chat/index.ts @@ -0,0 +1,766 @@ +/** + * + * ATTACHMENTS + * + * Components for displaying and managing different attachment types in chat messages. + * Supports two operational modes: + * - **Readonly mode**: For displaying stored attachments in sent messages (DatabaseMessageExtra[]) + * - **Editable mode**: For managing pending uploads in the input form (ChatUploadedFile[]) + * + * The attachment system uses `getAttachmentDisplayItems()` utility to normalize both + * data sources into a unified display format, enabling consistent rendering regardless + * of the attachment origin. + * + */ + +/** + * **ChatAttachmentsList** - Unified display for file attachments in chat + * + * Central component for rendering file attachments in both ChatMessage (readonly) + * and ChatForm (editable) contexts. + * + * **Architecture:** + * - Delegates rendering to specialized thumbnail components based on attachment type + * - Manages scroll state and navigation arrows for horizontal overflow + * - Integrates with DialogChatAttachmentPreview for full-size viewing + * - Validates vision modality support via `activeModelId` prop + * + * **Features:** + * - Horizontal scroll with smooth navigation arrows + * - Image thumbnails with lazy loading and error fallback + * - File type icons for non-image files (PDF, text, audio, etc.) + * - MCP prompt attachments with expandable content preview + * - Click-to-preview with full-size dialog and download option + * - "View All" button when `limitToSingleRow` is enabled and content overflows + * - Vision modality validation to warn about unsupported image uploads + * - Customizable thumbnail dimensions via `imageHeight`/`imageWidth` props + * + * @example + * ```svelte + * + * + * + * + * removeFile(id)} + * limitToSingleRow + * activeModelId={selectedModel} + * /> + * ``` + */ +export { default as ChatAttachmentsList } from './ChatAttachments/ChatAttachmentsList.svelte'; + +/** + * Displays MCP Prompt attachment with expandable content preview. + * Shows server name, prompt name, and allows expanding to view full prompt arguments + * and content. Used when user selects a prompt from ChatFormPromptPicker. + */ +export { default as ChatAttachmentMcpPrompt } from './ChatAttachments/ChatAttachmentMcpPrompt.svelte'; + +/** + * Displays a single MCP Resource attachment with icon, name, and server info. + * Shows loading/error states and supports remove action. + * Used within ChatAttachmentMcpResources for individual resource display. + */ +export { default as ChatAttachmentMcpResource } from './ChatAttachments/ChatAttachmentMcpResource.svelte'; + +/** + * Full-size attachment preview component for dialog display. Handles different file types: + * images (full-size display), text files (syntax highlighted), PDFs (text extraction or image preview), + * audio (placeholder with download), and generic files (download option). + */ +export { default as ChatAttachmentPreview } from './ChatAttachments/ChatAttachmentPreview.svelte'; + +/** + * Displays MCP Resource attachments as a horizontal carousel. + * Shows resource name, URI, and allows clicking to view resource content. + */ +export { default as ChatAttachmentMcpResources } from './ChatAttachments/ChatAttachmentMcpResources.svelte'; + +/** + * Thumbnail for non-image file attachments. Displays file type icon based on extension, + * file name (truncated), and file size. + * Handles text files, PDFs, audio, and other document types. + */ +export { default as ChatAttachmentThumbnailFile } from './ChatAttachments/ChatAttachmentThumbnailFile.svelte'; + +/** + * Thumbnail for image attachments with lazy loading and error fallback. + * Displays image preview with configurable dimensions. Falls back to placeholder + * on load error. + */ +export { default as ChatAttachmentThumbnailImage } from './ChatAttachments/ChatAttachmentThumbnailImage.svelte'; + +/** + * Grid view of all attachments for "View All" dialog. Displays all attachments + * in a responsive grid layout when there are too many to show inline. + * Triggered by "+X more" button in ChatAttachmentsList. + */ +export { default as ChatAttachmentsViewAll } from './ChatAttachments/ChatAttachmentsViewAll.svelte'; + +/** + * + * FORM + * + * Components for the chat input area. The form handles user input, file attachments, + * audio recording, and MCP prompts & resources selection. It integrates with multiple stores: + * - `chatStore` for message submission and generation control + * - `modelsStore` for model selection and validation + * - `mcpStore` for MCP prompt browsing and loading + * + * The form exposes a public API for programmatic control from parent components + * (focus, height reset, model selector, validation). + * + */ + +/** + * **ChatForm** - Main chat input component with rich features + * + * The primary input interface for composing and sending chat messages. + * Orchestrates text input, file attachments, audio recording, and MCP prompts. + * Used by ChatScreenForm and ChatMessageEditForm for both new conversations and message editing. + * + * **Architecture:** + * - Composes ChatFormTextarea, ChatFormActions, and ChatFormPromptPicker + * - Manages file upload state via `uploadedFiles` bindable prop + * - Integrates with ModelsSelector for model selection in router mode + * - Communicates with parent via callbacks (onSubmit, onFilesAdd, onStop, etc.) + * + * **Input Handling:** + * - IME-safe Enter key handling (waits for composition end) + * - Shift+Enter for newline, Enter for submit + * - Paste handler for files and long text (> {pasteLongTextToFileLen} chars → file conversion) + * - Keyboard shortcut `/` triggers MCP prompt picker + * + * **Features:** + * - Auto-resizing textarea with placeholder + * - File upload via button dropdown (images/text/PDF), drag-drop, or paste + * - Audio recording with WAV conversion (when model supports audio) + * - MCP prompt picker with search and argument forms + * - MCP reource picker with component to list attached resources at the bottom of Chat Form + * - Model selector integration (router mode) + * - Loading state with stop button, disabled state for errors + * + * **Exported API:** + * - `focus()` - Focus the textarea programmatically + * - `resetTextareaHeight()` - Reset textarea to default height after submit + * - `openModelSelector()` - Open model selection dropdown + * - `checkModelSelected(): boolean` - Validate model selection, show error if none + * + * @example + * ```svelte + * + * ``` + */ +export { default as ChatForm } from './ChatForm/ChatForm.svelte'; + +/** + * Dropdown button for file attachment selection. Opens a menu with options for + * Images, Text Files, and PDF Files. Each option filters the file picker to + * appropriate types. Images option is disabled when model lacks vision modality. + */ +export { default as ChatFormActionAttachmentsDropdown } from './ChatForm/ChatFormActions/ChatFormActionAttachmentsDropdown.svelte'; + +/** + * Audio recording button with real-time recording indicator. Records audio + * and converts to WAV format for upload. Only visible when the active model + * supports audio modality and setting for automatic audio input is enabled. Shows recording duration while active. + */ +export { default as ChatFormActionRecord } from './ChatForm/ChatFormActions/ChatFormActionRecord.svelte'; + +/** + * Container for chat form action buttons. Arranges file attachment, audio record, + * and submit/stop buttons in a horizontal layout. Handles conditional visibility + * based on model capabilities and loading state. + */ +export { default as ChatFormActions } from './ChatForm/ChatFormActions/ChatFormActions.svelte'; + +/** + * Submit/stop button with loading state. Shows send icon normally, transforms + * to stop icon during generation. Disabled when input is empty or form is disabled. + * Triggers onSubmit or onStop callbacks based on current state. + */ +export { default as ChatFormActionSubmit } from './ChatForm/ChatFormActions/ChatFormActionSubmit.svelte'; + +/** + * Hidden file input element for programmatic file selection. + */ +export { default as ChatFormFileInputInvisible } from './ChatForm/ChatFormFileInputInvisible.svelte'; + +/** + * Helper text display below chat. + */ +export { default as ChatFormHelperText } from './ChatForm/ChatFormHelperText.svelte'; + +/** + * Auto-resizing textarea with IME composition support. Automatically adjusts + * height based on content. Handles IME input correctly (waits for composition + * end before processing Enter key). Exposes focus() and resetHeight() methods. + */ +export { default as ChatFormTextarea } from './ChatForm/ChatFormTextarea.svelte'; + +/** + * **ChatFormPromptPicker** - MCP prompt selection interface + * + * Floating picker for browsing and selecting MCP Server Prompts. + * Triggered by typing `/` in the chat input or choosing `MCP Prompt` option in ChatFormActionAttachmentsDropdown. + * Loads prompts from connected MCP servers and allows users to select and configure them. + * + * **Architecture:** + * - Fetches available prompts from mcpStore + * - Manages selection state and keyboard navigation internally + * - Delegates argument input to ChatFormPromptPickerArgumentForm + * - Communicates prompt loading lifecycle via callbacks + * + * **Prompt Loading Flow:** + * 1. User selects prompt → `onPromptLoadStart` called with placeholder ID + * 2. Prompt content fetched from MCP server asynchronously + * 3. On success → `onPromptLoadComplete` with full prompt data + * 4. On failure → `onPromptLoadError` with error details + * + * **Features:** + * - Search/filter prompts by name across all connected servers + * - Keyboard navigation (↑/↓ to navigate, Enter to select, Esc to close) + * - Argument input forms for prompts with required parameters + * - Autocomplete suggestions for argument values + * - Loading states with skeleton placeholders + * - Server information header per prompt for visual identification + * + * **Exported API:** + * - `handleKeydown(event): boolean` - Process keyboard events, returns true if handled + * + * @example + * ```svelte + * showPicker = false} + * onPromptLoadStart={(id, info) => addPlaceholder(id, info)} + * onPromptLoadComplete={(id, result) => replacePlaceholder(id, result)} + * onPromptLoadError={(id, error) => handleError(id, error)} + * /> + * ``` + */ +export { default as ChatFormPromptPicker } from './ChatForm/ChatFormPromptPicker/ChatFormPromptPicker.svelte'; + +/** + * Form for entering MCP prompt arguments. Displays input fields for each + * required argument defined by the prompt. Validates input and submits + * when all required fields are filled. Shows argument descriptions as hints. + */ +export { default as ChatFormPromptPickerArgumentForm } from './ChatForm/ChatFormPromptPicker/ChatFormPromptPickerArgumentForm.svelte'; + +/** + * Single argument input field with autocomplete suggestions. Fetches suggestions + * from MCP server based on argument type. Supports keyboard navigation through + * suggestions list. Used within ChatFormPromptPickerArgumentForm. + */ +export { default as ChatFormPromptPickerArgumentInput } from './ChatForm/ChatFormPromptPicker/ChatFormPromptPickerArgumentInput.svelte'; + +/** + * Header for prompt picker with search input and close button. Contains the + * search field for filtering prompts and X button to dismiss the picker. + * Search input is auto-focused when picker opens. + */ +export { default as ChatFormPromptPickerHeader } from './ChatForm/ChatFormPromptPicker/ChatFormPromptPickerHeader.svelte'; + +/** + * Scrollable list of available MCP prompts. Renders ChatFormPromptPickerListItem + * for each prompt, grouped by server. Handles empty state when no prompts match + * search query. Manages scroll position for keyboard navigation. + */ +export { default as ChatFormPromptPickerList } from './ChatForm/ChatFormPromptPicker/ChatFormPromptPickerList.svelte'; + +/** + * Single prompt item in the picker list. Displays server avatar, prompt name, + * and description. Highlights on hover/keyboard focus. Triggers selection + * callback on click or Enter key. + */ +export { default as ChatFormPromptPickerListItem } from './ChatForm/ChatFormPromptPicker/ChatFormPromptPickerListItem.svelte'; + +/** + * Skeleton loading placeholder for prompt picker items. Displays animated + * placeholder while prompts are being fetched from MCP servers. + * Matches dimensions of ChatFormPromptPickerListItem. + */ +export { default as ChatFormPromptPickerListItemSkeleton } from './ChatForm/ChatFormPromptPicker/ChatFormPromptPickerListItemSkeleton.svelte'; + +/** + * + * MESSAGES + * + * Components for displaying chat messages. The message system supports: + * - **Conversation branching**: Messages can have siblings (alternative versions) + * created by editing or regenerating. Users can navigate between branches. + * - **Role-based rendering**: Different layouts for user, assistant, and system messages + * - **Streaming support**: Real-time display of assistant responses as they generate + * - **Agentic workflows**: Special rendering for tool calls and reasoning blocks + * + * The branching system uses `getMessageSiblings()` utility to compute sibling info + * for each message based on the full conversation tree stored in the database. + * + */ + +/** + * **ChatMessages** - Message list container with branching support + * + * Container component that renders the list of messages in a conversation. + * Computes sibling information for each message to enable branch navigation. + * Integrates with conversationsStore for message operations. + * + * **Architecture:** + * - Fetches all conversation messages to compute sibling relationships + * - Filters system messages based on user config (`showSystemMessage`) + * - Delegates rendering to ChatMessage for each message + * - Propagates all message operations to chatStore via callbacks + * + * **Branching Logic:** + * - Uses `getMessageSiblings()` to find all messages with same parent + * - Computes `siblingInfo: { currentIndex, totalSiblings, siblingIds }` + * - Enables navigation between alternative message versions + * + * **Message Operations (delegated to chatStore):** + * - Edit with branching: Creates new message branch, preserves original + * - Edit with replacement: Modifies message in place + * - Regenerate: Creates new assistant response as sibling + * - Delete: Removes message and all descendants (cascade) + * - Continue: Appends to incomplete assistant message + * + * @example + * ```svelte + * + * ``` + */ +export { default as ChatMessages } from './ChatMessages/ChatMessages.svelte'; + +/** + * **ChatMessage** - Single message display with actions + * + * Renders a single chat message with role-specific styling and full action + * support. Delegates to specialized components based on message role: + * ChatMessageUser, ChatMessageAssistant, or ChatMessageSystem. + * + * **Architecture:** + * - Routes to role-specific component based on `message.type` + * - Manages edit mode state and inline editing UI + * - Handles action callbacks (copy, edit, delete, regenerate) + * - Displays branching controls when message has siblings + * + * **User Messages:** + * - Shows attachments via ChatAttachmentsList + * - Displays MCP prompts if present + * - Edit creates new branch or preserves responses + * + * **Assistant Messages:** + * - Renders content via MarkdownContent or ChatMessageAgenticContent + * - Shows model info badge (when enabled) + * - Regenerate creates sibling with optional model override + * - Continue action for incomplete responses + * + * **Features:** + * - Inline editing with file attachments support + * - Copy formatted content to clipboard + * - Delete with confirmation (shows cascade delete count) + * - Branching controls for sibling navigation + * - Statistics display (tokens, timing) + * + * @example + * ```svelte + * + * ``` + */ +export { default as ChatMessage } from './ChatMessages/ChatMessage.svelte'; + +/** + * **ChatMessageAgenticContent** - Agentic workflow output display + * + * Specialized renderer for assistant messages containing agentic workflow markers. + * Parses structured content and displays tool calls and reasoning blocks as + * interactive collapsible sections with real-time streaming support. + * + * **Architecture:** + * - Uses `parseAgenticContent()` from `$lib/utils/agentic` to parse markers + * - Renders sections as CollapsibleContentBlock components + * - Handles streaming state for progressive content display + * - Falls back to MarkdownContent for plain text sections + * + * **Marker Format:** + * - Tool calls: in constants/agentic.ts (AGENTIC_TAGS) + * - Reasoning: in constants/agentic.ts (REASONING_TAGS) + * - Partial markers handled gracefully during streaming + * + * **Execution States:** + * - **Streaming**: Animated spinner, block expanded, auto-scroll enabled + * - **Pending**: Waiting indicator for queued tool calls + * - **Completed**: Static display, block collapsed by default + * + * **Features:** + * - JSON arguments syntax highlighting via SyntaxHighlightedCode + * - Tool results display with formatting + * - Plain text sections between markers rendered as markdown + * - Smart collapse defaults (expanded while streaming, collapsed when done) + * + * @example + * ```svelte + * + * ``` + */ +export { default as ChatMessageAgenticContent } from './ChatMessages/ChatMessageAgenticContent.svelte'; + +/** + * Action buttons toolbar for messages. Displays copy, edit, delete, and regenerate + * buttons based on message role. Includes branching controls when message has siblings. + * Shows delete confirmation dialog with cascade delete count. Handles raw output toggle + * for assistant messages. + */ +export { default as ChatMessageActions } from './ChatMessages/ChatMessageActions.svelte'; + +/** + * Navigation controls for message siblings (conversation branches). Displays + * prev/next arrows with current position counter (e.g., "2/5"). Enables users + * to navigate between alternative versions of a message created by editing + * or regenerating. Uses `conversationsStore.navigateToSibling()` for navigation. + */ +export { default as ChatMessageBranchingControls } from './ChatMessages/ChatMessageBranchingControls.svelte'; + +/** + * Statistics display for assistant messages. Shows token counts (prompt/completion), + * generation timing, tokens per second, and model name (when enabled in settings). + * Data sourced from message.timings stored during generation. + */ +export { default as ChatMessageStatistics } from './ChatMessages/ChatMessageStatistics.svelte'; + +/** + * MCP prompt display in user messages. Shows when user selected an MCP prompt + * via ChatFormPromptPicker. Displays server name, prompt name, and expandable + * content preview. Stored in message.extra as DatabaseMessageExtraMcpPrompt. + */ +export { default as ChatMessageMcpPrompt } from './ChatMessages/ChatMessageMcpPrompt.svelte'; + +/** + * Formatted content display for MCP prompt messages. Renders the full prompt + * content with arguments in a readable format. Used within ChatMessageMcpPrompt + * for the expanded view. + */ +export { default as ChatMessageMcpPromptContent } from './ChatMessages/ChatMessageMcpPromptContent.svelte'; + +/** + * System message display component. Renders system messages with distinct styling. + * Visibility controlled by `showSystemMessage` config setting. + */ +export { default as ChatMessageSystem } from './ChatMessages/ChatMessageSystem.svelte'; + +/** + * User message display component. Renders user messages with right-aligned bubble styling. + * Shows message content, attachments via ChatAttachmentsList, and MCP prompts if present. + * Supports inline editing mode with ChatMessageEditForm integration. + */ +export { default as ChatMessageUser } from './ChatMessages/ChatMessageUser.svelte'; + +/** + * Assistant message display component. Renders assistant responses with left-aligned styling. + * Supports both plain markdown content (via MarkdownContent) and agentic content with tool calls + * (via ChatMessageAgenticContent). Shows model info badge, statistics, and action buttons. + * Handles streaming state with real-time content updates. + */ +export { default as ChatMessageAssistant } from './ChatMessages/ChatMessageAssistant.svelte'; + +/** + * Inline message editing form. Provides textarea for editing message content with + * attachment management. Shows save/cancel buttons and optional "Save only" button + * for editing without regenerating responses. Used within ChatMessage components + * when user enters edit mode. + */ +export { default as ChatMessageEditForm } from './ChatMessages/ChatMessageEditForm.svelte'; + +/** + * + * SCREEN + * + * Top-level chat interface components. ChatScreen is the main container that + * orchestrates all chat functionality. It integrates with multiple stores: + * - `chatStore` for message operations and generation control + * - `conversationsStore` for conversation management + * - `serverStore` for server connection state + * - `modelsStore` for model capabilities (vision, audio modalities) + * + * The screen handles the complete chat lifecycle from empty state to active + * conversation with streaming responses. + * + */ + +/** + * **ChatScreen** - Main chat interface container + * + * Top-level component that orchestrates the entire chat interface. Manages + * messages display, input form, file handling, auto-scroll, error dialogs, + * and server state. Used as the main content area in chat routes. + * + * **Architecture:** + * - Composes ChatMessages, ChatScreenForm, ChatScreenHeader, and dialogs + * - Manages auto-scroll via `createAutoScrollController()` hook + * - Handles file upload pipeline (validation → processing → state update) + * - Integrates with serverStore for loading/error/warning states + * - Tracks active model for modality validation (vision, audio) + * + * **File Upload Pipeline:** + * 1. Files received via drag-drop, paste, or file picker + * 2. Validated against supported types (`isFileTypeSupported()`) + * 3. Filtered by model modalities (`filterFilesByModalities()`) + * 4. Empty files detected and reported via DialogEmptyFileAlert + * 5. Valid files processed to ChatUploadedFile[] format + * 6. Unsupported files shown in error dialog with reasons + * + * **State Management:** + * - `isEmpty`: Shows centered welcome UI when no conversation active + * - `isCurrentConversationLoading`: Tracks generation state for current chat + * - `activeModelId`: Determines available modalities for file validation + * - `uploadedFiles`: Pending file attachments for next message + * + * **Features:** + * - Messages display with smart auto-scroll (pauses on user scroll up) + * - File drag-drop with visual overlay indicator + * - File validation with detailed error messages + * - Error dialog management (chat errors, model unavailable) + * - Server loading/error/warning states with appropriate UI + * - Conversation deletion with confirmation dialog + * - Processing info display (tokens/sec, timing) during generation + * - Keyboard shortcuts (Ctrl+Shift+Backspace to delete conversation) + * + * @example + * ```svelte + * + * + * + * + * + * ``` + */ +export { default as ChatScreen } from './ChatScreen/ChatScreen.svelte'; + +/** + * Visual overlay displayed when user drags files over the chat screen. + * Shows drop zone indicator to guide users where to release files. + * Integrated with ChatScreen's drag-drop file upload handling. + */ +export { default as ChatScreenDragOverlay } from './ChatScreen/ChatScreenDragOverlay.svelte'; + +/** + * Chat form wrapper within ChatScreen. Positions the ChatForm component at the + * bottom of the screen with proper padding and max-width constraints. Handles + * the visual container styling for the input area. + */ +export { default as ChatScreenForm } from './ChatScreen/ChatScreenForm.svelte'; + +/** + * Header bar for chat screen. Displays conversation title (or "New Chat"), + * model selector (in router mode), and action buttons (delete conversation). + * Sticky positioned at the top of the chat area. + */ +export { default as ChatScreenHeader } from './ChatScreen/ChatScreenHeader.svelte'; + +/** + * Processing info display during generation. Shows real-time statistics: + * tokens per second, prompt/completion token counts, and elapsed time. + * Data sourced from slotsService polling during active generation. + * Only visible when `isCurrentConversationLoading` is true. + */ +export { default as ChatScreenProcessingInfo } from './ChatScreen/ChatScreenProcessingInfo.svelte'; + +/** + * + * SETTINGS + * + * Application settings components. Settings are persisted to localStorage via + * the config store and synchronized with server `/props` endpoint for sampling + * parameters. The settings panel uses a tabbed interface with mobile-responsive + * horizontal scrolling tabs. + * + * **Parameter Sync System:** + * Sampling parameters (temperature, top_p, etc.) can come from three sources: + * 1. **Server Props**: Default values from `/props` endpoint + * 2. **User Custom**: Values explicitly set by user (overrides server) + * 3. **App Default**: Fallback when server props unavailable + * + * The `ChatSettingsParameterSourceIndicator` badge shows which source is active. + * + */ + +/** + * **ChatSettings** - Application settings panel + * + * Comprehensive settings interface with categorized sections. Manages all + * user preferences and sampling parameters. Integrates with config store + * for persistence and ParameterSyncService for server synchronization. + * + * **Architecture:** + * - Uses tabbed navigation with category sections + * - Maintains local form state, commits on save + * - Tracks user overrides vs server defaults for sampling params + * - Exposes reset() method for dialog close without save + * + * **Categories:** + * - **General**: API key, system message, show system messages toggle + * - **Display**: Theme selection, message actions visibility, model info badge + * - **Sampling**: Temperature, top_p, top_k, min_p, repeat_penalty, etc. + * - **Penalties**: Frequency penalty, presence penalty, repeat last N + * - **Import/Export**: Conversation backup and restore + * - **MCP**: MCP server management (opens DialogChatSettings with MCP tab) + * - **Developer**: Debug options, disable auto-scroll + * + * **Parameter Sync:** + * - Fetches defaults from server `/props` endpoint + * - Shows source indicator badge (Custom/Server Props/Default) + * - Real-time badge updates as user types + * - Tracks which parameters user has explicitly overridden + * + * **Features:** + * - Mobile-responsive layout with horizontal scrolling tabs + * - Form validation with error messages + * - Secure API key storage (masked input) + * - Import/export conversations as JSON + * - Reset to defaults option per parameter + * + * **Exported API:** + * - `reset()` - Reset form fields to currently saved values (for cancel action) + * + * @example + * ```svelte + * dialogOpen = false} + * onCancel={() => { settingsRef.reset(); dialogOpen = false; }} + * /> + * ``` + */ +export { default as ChatSettings } from './ChatSettings/ChatSettings.svelte'; + +/** + * Footer with save/cancel buttons for settings panel. Positioned at bottom + * of settings dialog. Save button commits form state to config store, + * cancel button triggers reset and close. + */ +export { default as ChatSettingsFooter } from './ChatSettings/ChatSettingsFooter.svelte'; + +/** + * Form fields renderer for individual settings. Generates appropriate input + * components based on field type (text, number, select, checkbox, textarea). + * Handles validation, help text display, and parameter source indicators. + */ +export { default as ChatSettingsFields } from './ChatSettings/ChatSettingsFields.svelte'; + +/** + * Import/export tab content for conversation data management. Provides buttons + * to export all conversations as JSON file and import from JSON file. + * Handles file download/upload and data validation. + */ +export { default as ChatSettingsImportExportTab } from './ChatSettings/ChatSettingsImportExportTab.svelte'; + +/** + * Badge indicating parameter source for sampling settings. Shows one of: + * - **Custom**: User has explicitly set this value (orange badge) + * - **Server Props**: Using default from `/props` endpoint (blue badge) + * - **Default**: Using app default, server props unavailable (gray badge) + * Updates in real-time as user types to show immediate feedback. + */ +export { default as ChatSettingsParameterSourceIndicator } from './ChatSettings/ChatSettingsParameterSourceIndicator.svelte'; + +/** + * + * SIDEBAR + * + * The sidebar integrates with ShadCN's sidebar component system + * for consistent styling and mobile responsiveness. + * Conversations are loaded from conversationsStore and displayed in reverse + * chronological order (most recent first). + * + */ + +/** + * **ChatSidebar** - Chat Sidebar with actions menu and conversation list + * + * Collapsible sidebar displaying conversation history with search and + * management actions. Integrates with ShadCN sidebar component for + * consistent styling and mobile responsiveness. + * + * **Architecture:** + * - Uses ShadCN Sidebar.* components for structure + * - Fetches conversations from conversationsStore + * - Manages search state and filtered results locally + * - Handles conversation CRUD operations via conversationsStore + * + * **Navigation:** + * - Click conversation to navigate to `/chat/[id]` + * - New chat button navigates to `/` (root) + * - Active conversation highlighted based on route params + * + * **Conversation Management:** + * - Right-click or menu button for context menu + * - Rename: Opens inline edit dialog + * - Delete: Shows confirmation with conversation preview + * - Delete All: Removes all conversations with confirmation + * + * **Features:** + * - Search/filter conversations by title + * - Conversation list with message previews (first message truncated) + * - Active conversation highlighting + * - Mobile-responsive collapse/expand via ShadCN sidebar + * - New chat button in header + * - Settings button opens DialogChatSettings + * + * **Exported API:** + * - `handleMobileSidebarItemClick()` - Close sidebar on mobile after item selection + * - `activateSearchMode()` - Focus search input programmatically + * - `editActiveConversation()` - Open rename dialog for current conversation + * + * @example + * ```svelte + * + * ``` + */ +export { default as ChatSidebar } from './ChatSidebar/ChatSidebar.svelte'; + +/** + * Action buttons for sidebar header. Contains new chat button, settings button, + * and delete all conversations button. Manages dialog states for settings and + * delete confirmation. + */ +export { default as ChatSidebarActions } from './ChatSidebar/ChatSidebarActions.svelte'; + +/** + * Single conversation item in sidebar. Displays conversation title (truncated), + * last message preview, and timestamp. Shows context menu on right-click with + * rename and delete options. Highlights when active (matches current route). + * Handles click to navigate and keyboard accessibility. + */ +export { default as ChatSidebarConversationItem } from './ChatSidebar/ChatSidebarConversationItem.svelte'; + +/** + * Search input for filtering conversations in sidebar. Filters conversation + * list by title as user types. Shows clear button when query is not empty. + * Integrated into sidebar header with proper styling. + */ +export { default as ChatSidebarSearch } from './ChatSidebar/ChatSidebarSearch.svelte'; diff --git a/tools/server/webui/src/lib/components/app/index.ts b/tools/server/webui/src/lib/components/app/index.ts index 142622ef0a..56bd8a4852 100644 --- a/tools/server/webui/src/lib/components/app/index.ts +++ b/tools/server/webui/src/lib/components/app/index.ts @@ -1,68 +1,11 @@ export * from './actions'; export * from './badges'; +export * from './chat'; export * from './content'; +export * from './dialogs'; export * from './forms'; +export * from './mcp'; export * from './misc'; export * from './models'; export * from './navigation'; export * from './server'; - -// Chat -export { default as ChatAttachmentPreview } from './chat/ChatAttachments/ChatAttachmentPreview.svelte'; -export { default as ChatAttachmentThumbnailFile } from './chat/ChatAttachments/ChatAttachmentThumbnailFile.svelte'; -export { default as ChatAttachmentThumbnailImage } from './chat/ChatAttachments/ChatAttachmentThumbnailImage.svelte'; -export { default as ChatAttachmentsList } from './chat/ChatAttachments/ChatAttachmentsList.svelte'; -export { default as ChatAttachmentsViewAll } from './chat/ChatAttachments/ChatAttachmentsViewAll.svelte'; -export { default as ChatForm } from './chat/ChatForm/ChatForm.svelte'; -export { default as ChatFormActionAttachmentsDropdown } from './chat/ChatForm/ChatFormActions/ChatFormActionAttachmentsDropdown.svelte'; -export { default as ChatFormActionFileAttachments } from './chat/ChatForm/ChatFormActions/ChatFormActionFileAttachments.svelte'; -export { default as ChatFormActionRecord } from './chat/ChatForm/ChatFormActions/ChatFormActionRecord.svelte'; -export { default as ChatFormActions } from './chat/ChatForm/ChatFormActions/ChatFormActions.svelte'; -export { default as ChatFormActionSubmit } from './chat/ChatForm/ChatFormActions/ChatFormActionSubmit.svelte'; -export { default as ChatFormFileInputInvisible } from './chat/ChatForm/ChatFormFileInputInvisible.svelte'; -export { default as ChatFormHelperText } from './chat/ChatForm/ChatFormHelperText.svelte'; -export { default as ChatFormTextarea } from './chat/ChatForm/ChatFormTextarea.svelte'; -export { default as ChatMessage } from './chat/ChatMessages/ChatMessage.svelte'; -export { default as ChatMessageActions } from './chat/ChatMessages/ChatMessageActions.svelte'; -export { default as ChatMessageAssistant } from './chat/ChatMessages/ChatMessageAssistant.svelte'; -export { default as ChatMessageBranchingControls } from './chat/ChatMessages/ChatMessageBranchingControls.svelte'; -export { default as ChatMessageEditForm } from './chat/ChatMessages/ChatMessageEditForm.svelte'; -export { default as ChatMessageStatistics } from './chat/ChatMessages/ChatMessageStatistics.svelte'; -export { default as ChatMessageSystem } from './chat/ChatMessages/ChatMessageSystem.svelte'; -export { default as ChatMessageThinkingBlock } from './chat/ChatMessages/ChatMessageThinkingBlock.svelte'; -export { default as ChatMessageUser } from './chat/ChatMessages/ChatMessageUser.svelte'; -export { default as ChatMessages } from './chat/ChatMessages/ChatMessages.svelte'; -export { default as MessageBranchingControls } from './chat/ChatMessages/ChatMessageBranchingControls.svelte'; -export { default as ChatScreen } from './chat/ChatScreen/ChatScreen.svelte'; -export { default as ChatScreenDragOverlay } from './chat/ChatScreen/ChatScreenDragOverlay.svelte'; -export { default as ChatScreenForm } from './chat/ChatScreen/ChatScreenForm.svelte'; -export { default as ChatScreenHeader } from './chat/ChatScreen/ChatScreenHeader.svelte'; -export { default as ChatScreenProcessingInfo } from './chat/ChatScreen/ChatScreenProcessingInfo.svelte'; -export { default as ChatSettings } from './chat/ChatSettings/ChatSettings.svelte'; -export { default as ChatSettingsFooter } from './chat/ChatSettings/ChatSettingsFooter.svelte'; -export { default as ChatSettingsFields } from './chat/ChatSettings/ChatSettingsFields.svelte'; -export { default as ChatSettingsImportExportTab } from './chat/ChatSettings/ChatSettingsImportExportTab.svelte'; -export { default as ChatSettingsParameterSourceIndicator } from './chat/ChatSettings/ChatSettingsParameterSourceIndicator.svelte'; -export { default as ChatSidebar } from './chat/ChatSidebar/ChatSidebar.svelte'; -export { default as ChatSidebarActions } from './chat/ChatSidebar/ChatSidebarActions.svelte'; -export { default as ChatSidebarConversationItem } from './chat/ChatSidebar/ChatSidebarConversationItem.svelte'; -export { default as ChatSidebarSearch } from './chat/ChatSidebar/ChatSidebarSearch.svelte'; - -// Dialogs -export { default as DialogChatAttachmentPreview } from './dialogs/DialogChatAttachmentPreview.svelte'; -export { default as DialogChatAttachmentsViewAll } from './dialogs/DialogChatAttachmentsViewAll.svelte'; -export { default as DialogChatError } from './dialogs/DialogChatError.svelte'; -export { default as DialogChatSettings } from './dialogs/DialogChatSettings.svelte'; -export { default as DialogCodePreview } from './dialogs/DialogCodePreview.svelte'; -export { default as DialogConfirmation } from './dialogs/DialogConfirmation.svelte'; -export { default as DialogConversationSelection } from './dialogs/DialogConversationSelection.svelte'; -export { default as DialogConversationTitleUpdate } from './dialogs/DialogConversationTitleUpdate.svelte'; -export { default as DialogEmptyFileAlert } from './dialogs/DialogEmptyFileAlert.svelte'; -export { default as DialogModelInformation } from './dialogs/DialogModelInformation.svelte'; -export { default as DialogModelNotAvailable } from './dialogs/DialogModelNotAvailable.svelte'; - -// Compatibility aliases -export { default as ActionButton } from './actions/ActionIcon.svelte'; -export { default as ActionDropdown } from './navigation/DropdownMenuActions.svelte'; -export { default as CopyToClipboardIcon } from './actions/ActionIconCopyToClipboard.svelte'; -export { default as RemoveButton } from './actions/ActionIconRemove.svelte'; diff --git a/tools/server/webui/src/lib/constants/cache.ts b/tools/server/webui/src/lib/constants/cache.ts index acdb7a6430..07fe868341 100644 --- a/tools/server/webui/src/lib/constants/cache.ts +++ b/tools/server/webui/src/lib/constants/cache.ts @@ -3,31 +3,52 @@ */ /** - * Default TTL (Time-To-Live) for cache entries in milliseconds. + * Default TTL (Time-To-Live) for cache entries in milliseconds + * @default 5 minutes */ export const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000; /** - * Default maximum number of entries in a cache. + * Default maximum number of entries in a cache + * @default 100 */ export const DEFAULT_CACHE_MAX_ENTRIES = 100; /** - * TTL for model props cache in milliseconds. + * TTL for model props cache in milliseconds + * Props don't change frequently, so we can cache them longer + * @default 10 minutes */ export const MODEL_PROPS_CACHE_TTL_MS = 10 * 60 * 1000; /** - * Maximum number of model props to cache. + * Maximum number of model props to cache + * @default 50 */ export const MODEL_PROPS_CACHE_MAX_ENTRIES = 50; /** - * Maximum number of inactive conversation states to keep in memory. + * Maximum number of MCP resources to cache + * @default 50 + */ +export const MCP_RESOURCE_CACHE_MAX_ENTRIES = 50; + +/** + * TTL for MCP resource cache entries in milliseconds + * @default 5 minutes + */ +export const MCP_RESOURCE_CACHE_TTL_MS = 5 * 60 * 1000; + +/** + * Maximum number of inactive conversation states to keep in memory + * States for conversations beyond this limit will be cleaned up + * @default 10 */ export const MAX_INACTIVE_CONVERSATION_STATES = 10; /** - * Maximum age (in ms) for inactive conversation states before cleanup. + * Maximum age (in ms) for inactive conversation states before cleanup + * States older than this will be removed during cleanup + * @default 30 minutes */ export const INACTIVE_CONVERSATION_STATE_MAX_AGE_MS = 30 * 60 * 1000; diff --git a/tools/server/webui/src/lib/enums/files.ts b/tools/server/webui/src/lib/enums/files.ts index a4f079d405..7efe0c706b 100644 --- a/tools/server/webui/src/lib/enums/files.ts +++ b/tools/server/webui/src/lib/enums/files.ts @@ -11,6 +11,13 @@ export enum FileTypeCategory { TEXT = 'text' } +/** + * Special file types for internal use (not MIME types) + */ +export enum SpecialFileType { + MCP_PROMPT = 'mcp-prompt' +} + // Specific file type enums for each category export enum FileTypeImage { JPEG = 'jpeg', @@ -136,9 +143,28 @@ export enum FileExtensionText { CS = '.cs' } +// MIME type prefixes and includes for content detection +export enum MimeTypePrefix { + IMAGE = 'image/', + TEXT = 'text' +} + +export enum MimeTypeIncludes { + JSON = 'json', + JAVASCRIPT = 'javascript', + TYPESCRIPT = 'typescript' +} + +// URI patterns for content detection +export enum UriPattern { + DATABASE_KEYWORD = 'database', + DATABASE_SCHEME = 'db://' +} + // MIME type enums export enum MimeTypeApplication { - PDF = 'application/pdf' + PDF = 'application/pdf', + OCTET_STREAM = 'application/octet-stream' } export enum MimeTypeAudio { @@ -152,6 +178,7 @@ export enum MimeTypeAudio { export enum MimeTypeImage { JPEG = 'image/jpeg', + JPG = 'image/jpg', PNG = 'image/png', GIF = 'image/gif', WEBP = 'image/webp', diff --git a/tools/server/webui/src/lib/enums/index.ts b/tools/server/webui/src/lib/enums/index.ts index 5b39eebbb1..541854cd87 100644 --- a/tools/server/webui/src/lib/enums/index.ts +++ b/tools/server/webui/src/lib/enums/index.ts @@ -1,12 +1,14 @@ export { AttachmentType } from './attachment'; +export { AgenticSectionType } from './agentic'; + export { ChatMessageStatsView, - ReasoningFormat, + ContentPartType, + ErrorDialogType, MessageRole, MessageType, - ContentPartType, - ErrorDialogType + ReasoningFormat } from './chat'; export { @@ -19,18 +21,31 @@ export { FileExtensionAudio, FileExtensionPdf, FileExtensionText, + MimeTypePrefix, + MimeTypeIncludes, + UriPattern, MimeTypeApplication, MimeTypeAudio, MimeTypeImage, - MimeTypeText + MimeTypeText, + SpecialFileType } from './files'; +export { + MCPConnectionPhase, + MCPLogLevel, + MCPTransportType, + HealthCheckStatus, + MCPContentType, + MCPRefType +} from './mcp'; + export { ModelModality } from './model'; export { ServerRole, ServerModelStatus } from './server'; export { ParameterSource, SyncableParameterType, SettingsFieldType } from './settings'; -export { KeyboardKey } from './keyboard'; +export { ColorMode, McpPromptVariant, UrlPrefix } from './ui'; -export { UrlPrefix } from './ui'; +export { KeyboardKey } from './keyboard'; diff --git a/tools/server/webui/src/lib/enums/ui.ts b/tools/server/webui/src/lib/enums/ui.ts index 72a5848263..827c3e6d1f 100644 --- a/tools/server/webui/src/lib/enums/ui.ts +++ b/tools/server/webui/src/lib/enums/ui.ts @@ -1,5 +1,19 @@ +export enum ColorMode { + LIGHT = 'light', + DARK = 'dark', + SYSTEM = 'system' +} + +/** + * MCP prompt display variant + */ +export enum McpPromptVariant { + MESSAGE = 'message', + ATTACHMENT = 'attachment' +} + /** - * URL prefixes for protocol detection. + * URL prefixes for protocol detection */ export enum UrlPrefix { DATA = 'data:', diff --git a/tools/server/webui/src/lib/markdown/resolve-attachment-images.ts b/tools/server/webui/src/lib/markdown/resolve-attachment-images.ts index bc67ef9869..87c00d883d 100644 --- a/tools/server/webui/src/lib/markdown/resolve-attachment-images.ts +++ b/tools/server/webui/src/lib/markdown/resolve-attachment-images.ts @@ -5,7 +5,7 @@ import { AttachmentType, UrlPrefix } from '$lib/enums'; /** * Rehype plugin to resolve attachment image sources. - * Converts attachment names to base64 data URLs. + * Converts attachment names (e.g., "mcp-attachment-xxx.png") to base64 data URLs. */ export function rehypeResolveAttachmentImages(options: { attachments?: DatabaseMessageExtra[] }) { return (tree: HastRoot) => { @@ -13,15 +13,18 @@ export function rehypeResolveAttachmentImages(options: { attachments?: DatabaseM if (node.tagName === 'img' && node.properties?.src) { const src = String(node.properties.src); + // Skip data URLs and external URLs if (src.startsWith(UrlPrefix.DATA) || src.startsWith(UrlPrefix.HTTP)) { return; } + // Find matching attachment const attachment = options.attachments?.find( (a): a is DatabaseMessageExtraImageFile => a.type === AttachmentType.IMAGE && a.name === src ); + // Replace with base64 URL if found if (attachment?.base64Url) { node.properties.src = attachment.base64Url; } diff --git a/tools/server/webui/src/lib/services/chat.ts b/tools/server/webui/src/lib/services/chat.service.ts similarity index 79% rename from tools/server/webui/src/lib/services/chat.ts rename to tools/server/webui/src/lib/services/chat.service.ts index 55af0ce816..389939901a 100644 --- a/tools/server/webui/src/lib/services/chat.ts +++ b/tools/server/webui/src/lib/services/chat.service.ts @@ -1,42 +1,57 @@ -import { getJsonHeaders } from '$lib/utils'; -import { AttachmentType } from '$lib/enums'; - -/** - * ChatService - Low-level API communication layer for Chat Completions - * - * **Terminology - Chat vs Conversation:** - * - **Chat**: The active interaction space with the Chat Completions API. This service - * handles the real-time communication with the AI backend - sending messages, receiving - * streaming responses, and managing request lifecycles. "Chat" is ephemeral and runtime-focused. - * - **Conversation**: The persistent database entity storing all messages and metadata. - * Managed by ConversationsService/Store, conversations persist across sessions. - * - * This service handles direct communication with the llama-server's Chat Completions API. - * It provides the network layer abstraction for AI model interactions while remaining - * stateless and focused purely on API communication. - * - * **Architecture & Relationships:** - * - **ChatService** (this class): Stateless API communication layer - * - Handles HTTP requests/responses with the llama-server - * - Manages streaming and non-streaming response parsing - * - Provides per-conversation request abortion capabilities - * - Converts database messages to API format - * - Handles error translation for server responses - * - * - **chatStore**: Uses ChatService for all AI model communication - * - **conversationsStore**: Provides message context for API requests - * - * **Key Responsibilities:** - * - Message format conversion (DatabaseMessage → API format) - * - Streaming response handling with real-time callbacks - * - Reasoning content extraction and processing - * - File attachment processing (images, PDFs, audio, text) - * - Request lifecycle management (abort via AbortSignal) - */ +import { getJsonHeaders, formatAttachmentText, isAbortError } from '$lib/utils'; +import { AGENTIC_REGEX } from '$lib/constants/agentic'; +import { + ATTACHMENT_LABEL_PDF_FILE, + ATTACHMENT_LABEL_MCP_PROMPT, + ATTACHMENT_LABEL_MCP_RESOURCE +} from '$lib/constants/attachment-labels'; +import { + AttachmentType, + ContentPartType, + MessageRole, + ReasoningFormat, + UrlPrefix +} from '$lib/enums'; +import type { ApiChatMessageContentPart, ApiChatCompletionToolCall } from '$lib/types/api'; +import type { DatabaseMessageExtraMcpPrompt, DatabaseMessageExtraMcpResource } from '$lib/types'; +import { modelsStore } from '$lib/stores/models.svelte'; + export class ChatService { - // ───────────────────────────────────────────────────────────────────────────── - // Messaging - // ───────────────────────────────────────────────────────────────────────────── + private static stripReasoningContent( + content: ApiChatMessageData['content'] | null | undefined + ): ApiChatMessageData['content'] | null | undefined { + if (!content) { + return content; + } + + if (typeof content === 'string') { + return content + .replace(AGENTIC_REGEX.REASONING_BLOCK, '') + .replace(AGENTIC_REGEX.REASONING_OPEN, ''); + } + + if (!Array.isArray(content)) { + return content; + } + + return content.map((part: ApiChatMessageContentPart) => { + if (part.type !== ContentPartType.TEXT || !part.text) return part; + return { + ...part, + text: part.text + .replace(AGENTIC_REGEX.REASONING_BLOCK, '') + .replace(AGENTIC_REGEX.REASONING_OPEN, '') + }; + }); + } + + /** + * + * + * Messaging + * + * + */ /** * Sends a chat completion request to the llama.cpp server. @@ -63,6 +78,8 @@ export class ChatService { onToolCallChunk, onModel, onTimings, + // Tools for function calling + tools, // Generation parameters temperature, max_tokens, @@ -97,6 +114,7 @@ export class ChatService { .map((msg) => { if ('id' in msg && 'convId' in msg && 'timestamp' in msg) { const dbMsg = msg as DatabaseMessage & { extra?: DatabaseMessageExtra[] }; + return ChatService.convertDbMessageToApiChatMessageData(dbMsg); } else { return msg as ApiChatMessageData; @@ -104,7 +122,7 @@ export class ChatService { }) .filter((msg) => { // Filter out empty system messages - if (msg.role === 'system') { + if (msg.role === MessageRole.SYSTEM) { const content = typeof msg.content === 'string' ? msg.content : ''; return content.trim().length > 0; @@ -113,13 +131,41 @@ export class ChatService { return true; }); + // Filter out image attachments if the model doesn't support vision + if (options.model && !modelsStore.modelSupportsVision(options.model)) { + normalizedMessages.forEach((msg) => { + if (Array.isArray(msg.content)) { + msg.content = msg.content.filter((part: ApiChatMessageContentPart) => { + if (part.type === ContentPartType.IMAGE_URL) { + console.info( + `[ChatService] Skipping image attachment in message history (model "${options.model}" does not support vision)` + ); + + return false; + } + + return true; + }); + // If only text remains and it's a single part, simplify to string + if (msg.content.length === 1 && msg.content[0].type === ContentPartType.TEXT) { + msg.content = msg.content[0].text; + } + } + }); + } + const requestBody: ApiChatCompletionRequest = { messages: normalizedMessages.map((msg: ApiChatMessageData) => ({ role: msg.role, - content: msg.content + // Strip reasoning tags/content from the prompt to avoid polluting KV cache. + // TODO: investigate backend expectations for reasoning tags and add a toggle if needed. + content: ChatService.stripReasoningContent(msg.content), + tool_calls: msg.tool_calls, + tool_call_id: msg.tool_call_id })), stream, - return_progress: stream ? true : undefined + return_progress: stream ? true : undefined, + tools: tools && tools.length > 0 ? tools : undefined }; // Include model in request if provided (required in ROUTER mode) @@ -127,7 +173,9 @@ export class ChatService { requestBody.model = options.model; } - requestBody.reasoning_format = disableReasoningParsing ? 'none' : 'auto'; + requestBody.reasoning_format = disableReasoningParsing + ? ReasoningFormat.NONE + : ReasoningFormat.AUTO; if (temperature !== undefined) requestBody.temperature = temperature; if (max_tokens !== undefined) { @@ -183,9 +231,11 @@ export class ChatService { if (!response.ok) { const error = await ChatService.parseErrorResponse(response); + if (onError) { onError(error); } + throw error; } @@ -202,6 +252,7 @@ export class ChatService { conversationId, signal ); + return; } else { return ChatService.handleNonStreamResponse( @@ -213,7 +264,7 @@ export class ChatService { ); } } catch (error) { - if (error instanceof Error && error.name === 'AbortError') { + if (isAbortError(error)) { console.log('Chat completion request was aborted'); return; } @@ -240,16 +291,22 @@ export class ChatService { } console.error('Error in sendMessage:', error); + if (onError) { onError(userFriendlyError); } + throw userFriendlyError; } } - // ───────────────────────────────────────────────────────────────────────────── - // Streaming - // ───────────────────────────────────────────────────────────────────────────── + /** + * + * + * Streaming + * + * + */ /** * Handles streaming response from the chat completion API @@ -323,6 +380,10 @@ export class ChatService { const serializedToolCalls = JSON.stringify(aggregatedToolCalls); + if (import.meta.env.DEV) { + console.log('[ChatService] Aggregated tool calls:', serializedToolCalls); + } + if (!serializedToolCalls) { return; } @@ -349,10 +410,11 @@ export class ChatService { for (const line of lines) { if (abortSignal?.aborted) break; - if (line.startsWith('data: ')) { + if (line.startsWith(UrlPrefix.DATA)) { const data = line.slice(6); if (data === '[DONE]') { streamFinished = true; + continue; } @@ -458,6 +520,7 @@ export class ChatService { if (!responseText.trim()) { const noResponseError = new Error('No response received from server. Please try again.'); + throw noResponseError; } @@ -472,10 +535,6 @@ export class ChatService { const reasoningContent = data.choices[0]?.message?.reasoning_content; const toolCalls = data.choices[0]?.message?.tool_calls; - if (reasoningContent) { - console.log('Full reasoning content:', reasoningContent); - } - let serializedToolCalls: string | undefined; if (toolCalls && toolCalls.length > 0) { @@ -491,6 +550,7 @@ export class ChatService { if (!content.trim() && !serializedToolCalls) { const noResponseError = new Error('No response received from server. Please try again.'); + throw noResponseError; } @@ -563,9 +623,13 @@ export class ChatService { return result; } - // ───────────────────────────────────────────────────────────────────────────── - // Conversion - // ───────────────────────────────────────────────────────────────────────────── + /** + * + * + * Conversion + * + * + */ /** * Converts a database message with attachments to API chat message format. @@ -582,22 +646,48 @@ export class ChatService { static convertDbMessageToApiChatMessageData( message: DatabaseMessage & { extra?: DatabaseMessageExtra[] } ): ApiChatMessageData { - if (!message.extra || message.extra.length === 0) { + // Handle tool result messages (role: 'tool') + if (message.role === MessageRole.TOOL && message.toolCallId) { return { - role: message.role as 'user' | 'assistant' | 'system', + role: MessageRole.TOOL, + content: message.content, + tool_call_id: message.toolCallId + }; + } + + // Parse tool calls for assistant messages + let toolCalls: ApiChatCompletionToolCall[] | undefined; + if (message.toolCalls) { + try { + toolCalls = JSON.parse(message.toolCalls); + } catch { + // Ignore parse errors for malformed tool calls + } + } + + if (!message.extra || message.extra.length === 0) { + const result: ApiChatMessageData = { + role: message.role as MessageRole, content: message.content }; + + if (toolCalls && toolCalls.length > 0) { + result.tool_calls = toolCalls; + } + + return result; } const contentParts: ApiChatMessageContentPart[] = []; if (message.content) { contentParts.push({ - type: 'text', + type: ContentPartType.TEXT, text: message.content }); } + // Include images from all messages const imageFiles = message.extra.filter( (extra: DatabaseMessageExtra): extra is DatabaseMessageExtraImageFile => extra.type === AttachmentType.IMAGE @@ -605,7 +695,7 @@ export class ChatService { for (const image of imageFiles) { contentParts.push({ - type: 'image_url', + type: ContentPartType.IMAGE_URL, image_url: { url: image.base64Url } }); } @@ -617,8 +707,8 @@ export class ChatService { for (const textFile of textFiles) { contentParts.push({ - type: 'text', - text: `\n\n--- File: ${textFile.name} ---\n${textFile.content}` + type: ContentPartType.TEXT, + text: formatAttachmentText('File', textFile.name, textFile.content) }); } @@ -630,8 +720,8 @@ export class ChatService { for (const legacyContextFile of legacyContextFiles) { contentParts.push({ - type: 'text', - text: `\n\n--- File: ${legacyContextFile.name} ---\n${legacyContextFile.content}` + type: ContentPartType.TEXT, + text: formatAttachmentText('File', legacyContextFile.name, legacyContextFile.content) }); } @@ -642,7 +732,7 @@ export class ChatService { for (const audio of audioFiles) { contentParts.push({ - type: 'input_audio', + type: ContentPartType.INPUT_AUDIO, input_audio: { data: audio.base64Data, format: audio.mimeType.includes('wav') ? 'wav' : 'mp3' @@ -659,27 +749,69 @@ export class ChatService { if (pdfFile.processedAsImages && pdfFile.images) { for (let i = 0; i < pdfFile.images.length; i++) { contentParts.push({ - type: 'image_url', + type: ContentPartType.IMAGE_URL, image_url: { url: pdfFile.images[i] } }); } } else { contentParts.push({ - type: 'text', - text: `\n\n--- PDF File: ${pdfFile.name} ---\n${pdfFile.content}` + type: ContentPartType.TEXT, + text: formatAttachmentText(ATTACHMENT_LABEL_PDF_FILE, pdfFile.name, pdfFile.content) }); } } - return { - role: message.role as 'user' | 'assistant' | 'system', + const mcpPrompts = message.extra.filter( + (extra: DatabaseMessageExtra): extra is DatabaseMessageExtraMcpPrompt => + extra.type === AttachmentType.MCP_PROMPT + ); + + for (const mcpPrompt of mcpPrompts) { + contentParts.push({ + type: ContentPartType.TEXT, + text: formatAttachmentText( + ATTACHMENT_LABEL_MCP_PROMPT, + mcpPrompt.name, + mcpPrompt.content, + mcpPrompt.serverName + ) + }); + } + + const mcpResources = message.extra.filter( + (extra: DatabaseMessageExtra): extra is DatabaseMessageExtraMcpResource => + extra.type === AttachmentType.MCP_RESOURCE + ); + + for (const mcpResource of mcpResources) { + contentParts.push({ + type: ContentPartType.TEXT, + text: formatAttachmentText( + ATTACHMENT_LABEL_MCP_RESOURCE, + mcpResource.name, + mcpResource.content, + mcpResource.serverName + ) + }); + } + + const result: ApiChatMessageData = { + role: message.role as MessageRole, content: contentParts }; + if (toolCalls && toolCalls.length > 0) { + result.tool_calls = toolCalls; + } + return result; } - // ───────────────────────────────────────────────────────────────────────────── - // Utilities - // ───────────────────────────────────────────────────────────────────────────── + /** + * + * + * Utilities + * + * + */ /** * Parses error response and creates appropriate error with context information @@ -714,6 +846,7 @@ export class ChatService { contextInfo?: { n_prompt_tokens: number; n_ctx: number }; }; fallback.name = 'HttpError'; + return fallback; } } @@ -745,18 +878,26 @@ export class ChatService { // 1) root (some implementations provide `model` at the top level) const rootModel = getTrimmedString(root.model); - if (rootModel) return rootModel; + if (rootModel) { + return rootModel; + } // 2) streaming choice (delta) or final response (message) const firstChoice = Array.isArray(root.choices) ? asRecord(root.choices[0]) : undefined; - if (!firstChoice) return undefined; + if (!firstChoice) { + return undefined; + } // priority: delta.model (first chunk) else message.model (final response) const deltaModel = getTrimmedString(asRecord(firstChoice.delta)?.model); - if (deltaModel) return deltaModel; + if (deltaModel) { + return deltaModel; + } const messageModel = getTrimmedString(asRecord(firstChoice.message)?.model); - if (messageModel) return messageModel; + if (messageModel) { + return messageModel; + } // avoid guessing from non-standard locations (metadata, etc.) return undefined; diff --git a/tools/server/webui/src/lib/services/index.ts b/tools/server/webui/src/lib/services/index.ts index b59d7cec34..7c98383d1f 100644 --- a/tools/server/webui/src/lib/services/index.ts +++ b/tools/server/webui/src/lib/services/index.ts @@ -1,5 +1,262 @@ -export { ChatService } from './chat'; +/** + * + * SERVICES + * + * Stateless service layer for API communication and data operations. + * Services handle protocol-level concerns (HTTP, WebSocket, MCP, IndexedDB) + * without managing reactive state — that responsibility belongs to stores. + * + * **Design Principles:** + * - All methods are static — no instance state + * - Pure I/O operations (network requests, database queries) + * - No Svelte runes or reactive primitives + * - Error handling at the protocol level; business-level error handling in stores + * + * **Architecture (bottom to top):** + * - **Services** (this layer): Stateless protocol communication + * - **Stores**: Reactive state management consuming services + * - **Components**: UI consuming stores + * + */ + +/** + * **ChatService** - Chat Completions API communication layer + * + * Handles direct communication with the llama-server's `/v1/chat/completions` endpoint. + * Provides streaming and non-streaming response parsing, message format conversion + * (DatabaseMessage → API format), and request lifecycle management. + * + * **Terminology - Chat vs Conversation:** + * - **Chat**: The active interaction space with the Chat Completions API. Ephemeral and + * runtime-focused — sending messages, receiving streaming responses, managing request lifecycles. + * - **Conversation**: The persistent database entity storing all messages and metadata. + * Managed by conversationsStore, conversations persist across sessions. + * + * **Architecture & Relationships:** + * - **ChatService** (this class): Stateless API communication layer + * - Handles HTTP requests/responses with the llama-server + * - Manages streaming and non-streaming response parsing + * - Converts database messages to API format (multimodal, tool calls) + * - Handles error translation with user-friendly messages + * + * - **chatStore**: Primary consumer — uses ChatService for all AI model communication + * - **agenticStore**: Uses ChatService for multi-turn agentic loop streaming + * - **conversationsStore**: Provides message context for API requests + * + * **Key Responsibilities:** + * - Streaming response handling with real-time content/reasoning/tool-call callbacks + * - Non-streaming response parsing with complete response extraction + * - Database message to API format conversion (attachments, tool calls, multimodal) + * - Tool call delta merging for incremental streaming aggregation + * - Request parameter assembly (sampling, penalties, custom params) + * - File attachment processing (images, PDFs, audio, text, MCP prompts/resources) + * - Reasoning content stripping from prompt history to avoid KV cache pollution + * - Error translation (network, timeout, server errors → user-friendly messages) + * + * @see chatStore in stores/chat.svelte.ts — primary consumer for chat state management + * @see agenticStore in stores/agentic.svelte.ts — uses ChatService for agentic loop streaming + * @see conversationsStore in stores/conversations.svelte.ts — provides message context + */ +export { ChatService } from './chat.service'; + +/** + * **DatabaseService** - IndexedDB persistence layer via Dexie ORM + * + * Provides stateless data access for conversations and messages using IndexedDB. + * Handles all low-level storage operations including branching tree structures, + * cascade deletions, and transaction safety for multi-table operations. + * + * **Architecture & Relationships (bottom to top):** + * - **DatabaseService** (this class): Stateless IndexedDB operations + * - Lowest layer — direct Dexie/IndexedDB communication + * - Pure CRUD operations without business logic + * - Handles branching tree structure (parent-child relationships) + * - Provides transaction safety for multi-table operations + * + * - **conversationsStore**: Reactive state management layer + * - Uses DatabaseService for all persistence operations + * - Manages conversation list, active conversation, and messages in memory + * + * - **chatStore**: Active AI interaction management + * - Uses conversationsStore for conversation context + * - Directly uses DatabaseService for message CRUD during streaming + * + * **Key Responsibilities:** + * - Conversation CRUD (create, read, update, delete) + * - Message CRUD with branching support (parent-child relationships) + * - Root message and system prompt creation + * - Cascade deletion of message branches (descendants) + * - Transaction-safe multi-table operations + * - Conversation import with duplicate detection + * + * **Database Schema:** + * - `conversations`: id, lastModified, currNode, name + * - `messages`: id, convId, type, role, timestamp, parent, children + * + * **Branching Model:** + * Messages form a tree structure where each message can have multiple children, + * enabling conversation branching and alternative response paths. The conversation's + * `currNode` tracks the currently active branch endpoint. + * + * @see conversationsStore in stores/conversations.svelte.ts — reactive layer on top of DatabaseService + * @see chatStore in stores/chat.svelte.ts — uses DatabaseService directly for message CRUD during streaming + */ export { DatabaseService } from './database.service'; + +/** + * **ModelsService** - Model management API communication + * + * Handles communication with model-related endpoints for both MODEL (single model) + * and ROUTER (multi-model) server modes. Provides model listing, loading/unloading, + * and status checking without managing any model state. + * + * **Architecture & Relationships:** + * - **ModelsService** (this class): Stateless HTTP communication + * - Sends requests to model endpoints + * - Parses and returns typed API responses + * - Provides model status utility methods + * + * - **modelsStore**: Primary consumer — manages reactive model state + * - Calls ModelsService for all model API operations + * - Handles polling, caching, and state updates + * + * **Key Responsibilities:** + * - List available models via OpenAI-compatible `/v1/models` endpoint + * - Load/unload models via `/models/load` and `/models/unload` (ROUTER mode) + * - Model status queries (loaded, loading) + * + * **Server Mode Behavior:** + * - **MODEL mode**: Only `list()` is relevant — single model always loaded + * - **ROUTER mode**: Full lifecycle — `list()`, `listRouter()`, `load()`, `unload()` + * + * **Endpoints:** + * - `GET /v1/models` — OpenAI-compatible model list (both modes) + * - `POST /models/load` — Load a model (ROUTER mode only) + * - `POST /models/unload` — Unload a model (ROUTER mode only) + * + * @see modelsStore in stores/models.svelte.ts — primary consumer for reactive model state + */ export { ModelsService } from './models.service'; + +/** + * **PropsService** - Server properties and capabilities retrieval + * + * Fetches server configuration, model information, and capabilities from the `/props` + * endpoint. Supports both global server props and per-model props (ROUTER mode). + * + * **Architecture & Relationships:** + * - **PropsService** (this class): Stateless HTTP communication + * - Fetches server properties from `/props` endpoint + * - Handles authentication and request parameters + * - Returns typed `ApiLlamaCppServerProps` responses + * + * - **serverStore**: Consumes global server properties (role detection, connection state) + * - **modelsStore**: Consumes per-model properties (modalities, context size) + * - **settingsStore**: Syncs default generation parameters from props response + * + * **Key Responsibilities:** + * - Fetch global server properties (default generation settings, modalities) + * - Fetch per-model properties in ROUTER mode via `?model=` parameter + * - Handle autoload control to prevent unintended model loading + * + * **API Behavior:** + * - `GET /props` → Global server props (MODEL mode: includes modalities) + * - `GET /props?model=` → Per-model props (ROUTER mode: model-specific modalities) + * - `&autoload=false` → Prevents model auto-loading when querying props + * + * @see serverStore in stores/server.svelte.ts — consumes global server props + * @see modelsStore in stores/models.svelte.ts — consumes per-model props for modalities + * @see settingsStore in stores/settings.svelte.ts — syncs default generation params from props + */ export { PropsService } from './props.service'; -export { ParameterSyncService, SYNCABLE_PARAMETERS } from './parameter-sync.service'; + +/** + * **ParameterSyncService** - Server defaults and user settings synchronization + * + * Manages the complex logic of merging server-provided default parameters with + * user-configured overrides. Ensures the UI reflects the actual server state + * while preserving user customizations. Tracks parameter sources (server default + * vs user override) for display in the settings UI. + * + * **Architecture & Relationships:** + * - **ParameterSyncService** (this class): Stateless sync logic + * - Pure functions for parameter extraction, merging, and diffing + * - No side effects — receives data in, returns data out + * - Handles floating-point precision normalization + * + * - **settingsStore**: Primary consumer — calls sync methods during: + * - Initial load (`syncWithServerDefaults`) + * - Settings reset (`forceSyncWithServerDefaults`) + * - Parameter info queries (`getParameterInfo`) + * + * - **PropsService**: Provides raw server props that feed into extraction + * + * **Key Responsibilities:** + * - Extract syncable parameters from server `/props` response + * - Merge server defaults with user overrides (user wins) + * - Track parameter source (Custom vs Default) for UI badges + * - Validate server parameter values by type (number, string, boolean) + * - Create diffs between current settings and server defaults + * - Floating-point precision normalization for consistent comparisons + * + * **Parameter Source Priority:** + * 1. **User Override** (Custom badge) — explicitly set by user in settings + * 2. **Server Default** (Default badge) — from `/props` endpoint + * 3. **App Default** — hardcoded fallback when server props unavailable + * + * **Exports:** + * - `ParameterSyncService` class — static methods for sync logic + * - `SYNCABLE_PARAMETERS` — mapping of webui setting keys to server parameter keys + * + * @see settingsStore in stores/settings.svelte.ts — primary consumer for settings sync + * @see ChatSettingsParameterSourceIndicator — displays parameter source badges in UI + */ +export { ParameterSyncService } from './parameter-sync.service'; + +/** + * **MCPService** - Low-level MCP protocol communication layer + * + * Implements the client-side MCP (Model Context Protocol) SDK operations for connecting + * to MCP servers, discovering capabilities, and executing protocol operations. + * Supports multiple transport types: WebSocket, StreamableHTTP, and SSE (legacy fallback). + * + * **Architecture & Relationships:** + * - **MCPService** (this class): Stateless protocol communication + * - Creates and manages transport connections (WebSocket, StreamableHTTP, SSE) + * - Wraps MCP SDK client operations with error handling + * - Formats tool results and extracts server info + * - Provides abort signal support for cancellable operations + * + * - **mcpStore**: Reactive business logic facade + * - Uses MCPService for all protocol-level operations + * - Manages connection lifecycle, health checks, reconnection + * - Handles tool name conflict resolution and server coordination + * + * - **mcpResourceStore**: Reactive resource state + * - Receives resource data fetched via MCPService + * - Manages resource caching, subscriptions, and attachments + * + * - **agenticStore**: Agentic loop orchestration + * - Executes tool calls via mcpStore → MCPService chain + * + * **Key Responsibilities:** + * - Transport creation with automatic fallback (StreamableHTTP → SSE) + * - Server connection with detailed phase tracking and progress callbacks + * - Tool discovery (`listTools`) and execution (`callTool`) with abort support + * - Prompt listing (`listPrompts`) and retrieval (`getPrompt`) with arguments + * - Resource operations: list, read, subscribe/unsubscribe, template support + * - Completion suggestions for prompt arguments and resource URI templates + * - CORS proxy routing via llama-server for cross-origin MCP servers + * - Tool result formatting (text, images, embedded resources) + * + * **Transport Hierarchy:** + * 1. **WebSocket** — bidirectional, no CORS proxy support + * 2. **StreamableHTTP** — modern HTTP-based, supports CORS proxy + * 3. **SSE** — legacy fallback, supports CORS proxy + * + * @see mcpStore in stores/mcp.svelte.ts — reactive business logic facade on top of MCPService + * @see mcpResourceStore in stores/mcp-resources.svelte.ts — reactive resource state management + * @see agenticStore in stores/agentic.svelte.ts — uses MCPService (via mcpStore) for tool execution + * @see MCP Protocol Specification: https://modelcontextprotocol.io/specification/2025-06-18 + */ +export { MCPService } from './mcp.service'; diff --git a/tools/server/webui/src/lib/stores/chat.svelte.ts b/tools/server/webui/src/lib/stores/chat.svelte.ts index 362e6d44b3..5cb9d3a4b1 100644 --- a/tools/server/webui/src/lib/stores/chat.svelte.ts +++ b/tools/server/webui/src/lib/stores/chat.svelte.ts @@ -1,6 +1,22 @@ +/** + * chatStore - Reactive State Store for Chat Operations + * + * Manages chat lifecycle, streaming, message operations, and processing state. + * + * **Architecture & Relationships:** + * - **ChatService**: Stateless API layer (sendMessage, streaming) + * - **chatStore** (this): Reactive state + business logic + * - **conversationsStore**: Conversation persistence and navigation + * + * @see ChatService in services/chat.service.ts for API operations + */ + +import { SvelteMap } from 'svelte/reactivity'; import { DatabaseService, ChatService } from '$lib/services'; import { conversationsStore } from '$lib/stores/conversations.svelte'; import { config } from '$lib/stores/settings.svelte'; +import { agenticStore } from '$lib/stores/agentic.svelte'; +import { mcpStore } from '$lib/stores/mcp.svelte'; import { contextSize, isRouterMode } from '$lib/stores/server.svelte'; import { selectedModelName, @@ -11,618 +27,418 @@ import { normalizeModelName, filterByLeafNodeId, findDescendantMessages, - findLeafNode + findLeafNode, + isAbortError } from '$lib/utils'; -import { SvelteMap } from 'svelte/reactivity'; -import { DEFAULT_CONTEXT } from '$lib/constants/default-context'; import { SYSTEM_MESSAGE_PLACEHOLDER } from '$lib/constants/ui'; +import { REASONING_TAGS } from '$lib/constants/agentic'; +import { + MAX_INACTIVE_CONVERSATION_STATES, + INACTIVE_CONVERSATION_STATE_MAX_AGE_MS +} from '$lib/constants/cache'; +import type { + ChatMessageTimings, + ChatMessagePromptProgress, + ChatStreamCallbacks, + ErrorDialogState +} from '$lib/types/chat'; +import type { ApiProcessingState, DatabaseMessage, DatabaseMessageExtra } from '$lib/types'; +import { ErrorDialogType, MessageRole, MessageType } from '$lib/enums'; + +interface ConversationStateEntry { + lastAccessed: number; +} -/** - * chatStore - Active AI interaction and streaming state management - * - * **Terminology - Chat vs Conversation:** - * - **Chat**: The active interaction space with the Chat Completions API. Represents the - * real-time streaming session, loading states, and UI visualization of AI communication. - * A "chat" is ephemeral - it exists only while the user is actively interacting with the AI. - * - **Conversation**: The persistent database entity storing all messages and metadata. - * Managed by conversationsStore, conversations persist across sessions and page reloads. - * - * This store manages all active AI interactions including real-time streaming, response - * generation, and per-chat loading states. It handles the runtime layer between UI and - * AI backend, supporting concurrent streaming across multiple conversations. - * - * **Architecture & Relationships:** - * - **chatStore** (this class): Active AI session and streaming management - * - Manages real-time AI response streaming via ChatService - * - Tracks per-chat loading and streaming states for concurrent sessions - * - Handles message operations (send, edit, regenerate, branch) - * - Coordinates with conversationsStore for persistence - * - * - **conversationsStore**: Provides conversation data and message arrays for chat context - * - **ChatService**: Low-level API communication with llama.cpp server - * - **DatabaseService**: Message persistence and retrieval - * - * **Key Features:** - * - **AI Streaming**: Real-time token streaming with abort support - * - **Concurrent Chats**: Independent loading/streaming states per conversation - * - **Message Branching**: Edit, regenerate, and branch conversation trees - * - **Error Handling**: Timeout and server error recovery with user feedback - * - **Graceful Stop**: Save partial responses when stopping generation - * - * **State Management:** - * - Global `isLoading` and `currentResponse` for active chat UI - * - `chatLoadingStates` Map for per-conversation streaming tracking - * - `chatStreamingStates` Map for per-conversation streaming content - * - `processingStates` Map for per-conversation processing state (timing/context info) - * - Automatic state sync when switching between conversations - */ -class ChatStore { - // ───────────────────────────────────────────────────────────────────────────── - // State - // ───────────────────────────────────────────────────────────────────────────── +const countOccurrences = (source: string, token: string): number => + source ? source.split(token).length - 1 : 0; +const hasUnclosedReasoningTag = (content: string): boolean => + countOccurrences(content, REASONING_TAGS.START) > countOccurrences(content, REASONING_TAGS.END); +const wrapReasoningContent = (content: string, reasoningContent?: string): string => { + if (!reasoningContent) return content; + return `${REASONING_TAGS.START}${reasoningContent}${REASONING_TAGS.END}${content}`; +}; +class ChatStore { activeProcessingState = $state(null); currentResponse = $state(''); - errorDialogState = $state<{ - type: 'timeout' | 'server'; - message: string; - contextInfo?: { n_prompt_tokens: number; n_ctx: number }; - } | null>(null); + errorDialogState = $state(null); isLoading = $state(false); chatLoadingStates = new SvelteMap(); chatStreamingStates = new SvelteMap(); private abortControllers = new SvelteMap(); private processingStates = new SvelteMap(); + private conversationStateTimestamps = new SvelteMap(); private activeConversationId = $state(null); private isStreamingActive = $state(false); private isEditModeActive = $state(false); private addFilesHandler: ((files: File[]) => void) | null = $state(null); pendingEditMessageId = $state(null); - // Draft preservation for navigation (e.g., when adding system prompt from welcome page) + private messageUpdateCallback: + | ((messageId: string, updates: Partial) => void) + | null = null; private _pendingDraftMessage = $state(''); private _pendingDraftFiles = $state([]); - // ───────────────────────────────────────────────────────────────────────────── - // Loading State - // ───────────────────────────────────────────────────────────────────────────── - private setChatLoading(convId: string, loading: boolean): void { + this.touchConversationState(convId); if (loading) { this.chatLoadingStates.set(convId, true); - if (conversationsStore.activeConversation?.id === convId) this.isLoading = true; + if (convId === conversationsStore.activeConversation?.id) this.isLoading = true; } else { this.chatLoadingStates.delete(convId); - if (conversationsStore.activeConversation?.id === convId) this.isLoading = false; + if (convId === conversationsStore.activeConversation?.id) this.isLoading = false; } } - - private isChatLoading(convId: string): boolean { - return this.chatLoadingStates.get(convId) || false; - } - private setChatStreaming(convId: string, response: string, messageId: string): void { + this.touchConversationState(convId); this.chatStreamingStates.set(convId, { response, messageId }); - if (conversationsStore.activeConversation?.id === convId) this.currentResponse = response; + if (convId === conversationsStore.activeConversation?.id) this.currentResponse = response; } - private clearChatStreaming(convId: string): void { this.chatStreamingStates.delete(convId); - if (conversationsStore.activeConversation?.id === convId) this.currentResponse = ''; + if (convId === conversationsStore.activeConversation?.id) this.currentResponse = ''; } - private getChatStreaming(convId: string): { response: string; messageId: string } | undefined { return this.chatStreamingStates.get(convId); } - syncLoadingStateForChat(convId: string): void { - this.isLoading = this.isChatLoading(convId); - const streamingState = this.getChatStreaming(convId); - this.currentResponse = streamingState?.response || ''; - this.isStreamingActive = streamingState !== undefined; + this.isLoading = this.chatLoadingStates.get(convId) || false; + const s = this.chatStreamingStates.get(convId); + this.currentResponse = s?.response || ''; + this.isStreamingActive = s !== undefined; this.setActiveProcessingConversation(convId); - // Sync streaming content to activeMessages so UI displays current content - if (streamingState?.response && streamingState?.messageId) { - const idx = conversationsStore.findMessageIndex(streamingState.messageId); + if (s?.response && s?.messageId) { + const idx = conversationsStore.findMessageIndex(s.messageId); if (idx !== -1) { - conversationsStore.updateMessageAtIndex(idx, { content: streamingState.response }); + conversationsStore.updateMessageAtIndex(idx, { content: s.response }); } } } - /** - * Clears global UI state without affecting background streaming. - * Used when navigating to empty/new chat while other chats stream in background. - */ clearUIState(): void { this.isLoading = false; this.currentResponse = ''; this.isStreamingActive = false; } - // ───────────────────────────────────────────────────────────────────────────── - // Processing State - // ───────────────────────────────────────────────────────────────────────────── - - /** - * Set the active conversation for statistics display - */ setActiveProcessingConversation(conversationId: string | null): void { this.activeConversationId = conversationId; - - if (conversationId) { - this.activeProcessingState = this.processingStates.get(conversationId) || null; - } else { - this.activeProcessingState = null; - } + this.activeProcessingState = conversationId + ? this.processingStates.get(conversationId) || null + : null; } - /** - * Get processing state for a specific conversation - */ getProcessingState(conversationId: string): ApiProcessingState | null { return this.processingStates.get(conversationId) || null; } - /** - * Clear processing state for a specific conversation - */ + private setProcessingState(conversationId: string, state: ApiProcessingState | null): void { + if (state === null) this.processingStates.delete(conversationId); + else this.processingStates.set(conversationId, state); + if (conversationId === this.activeConversationId) this.activeProcessingState = state; + } + clearProcessingState(conversationId: string): void { this.processingStates.delete(conversationId); - - if (conversationId === this.activeConversationId) { - this.activeProcessingState = null; - } + if (conversationId === this.activeConversationId) this.activeProcessingState = null; } - /** - * Get the current processing state for the active conversation (reactive) - * Returns the direct reactive state for UI binding - */ getActiveProcessingState(): ApiProcessingState | null { return this.activeProcessingState; } - /** - * Updates processing state with timing data from streaming response - */ - updateProcessingStateFromTimings( - timingData: { - prompt_n: number; - prompt_ms?: number; - predicted_n: number; - predicted_per_second: number; - cache_n: number; - prompt_progress?: ChatMessagePromptProgress; - }, - conversationId?: string - ): void { - const processingState = this.parseTimingData(timingData); - - if (processingState === null) { - console.warn('Failed to parse timing data - skipping update'); - return; - } - - const targetId = conversationId || this.activeConversationId; - if (targetId) { - this.processingStates.set(targetId, processingState); - - if (targetId === this.activeConversationId) { - this.activeProcessingState = processingState; - } - } - } - - /** - * Get current processing state (sync version for reactive access) - */ getCurrentProcessingStateSync(): ApiProcessingState | null { return this.activeProcessingState; } - /** - * Restore processing state from last assistant message timings - * Call this when keepStatsVisible is enabled and we need to show last known stats - */ - restoreProcessingStateFromMessages(messages: DatabaseMessage[], conversationId: string): void { - for (let i = messages.length - 1; i >= 0; i--) { - const message = messages[i]; - if (message.role === 'assistant' && message.timings) { - const restoredState = this.parseTimingData({ - prompt_n: message.timings.prompt_n || 0, - prompt_ms: message.timings.prompt_ms, - predicted_n: message.timings.predicted_n || 0, - predicted_per_second: - message.timings.predicted_n && message.timings.predicted_ms - ? (message.timings.predicted_n / message.timings.predicted_ms) * 1000 - : 0, - cache_n: message.timings.cache_n || 0 - }); + private setStreamingActive(active: boolean): void { + this.isStreamingActive = active; + } - if (restoredState) { - this.processingStates.set(conversationId, restoredState); + isStreaming(): boolean { + return this.isStreamingActive; + } - if (conversationId === this.activeConversationId) { - this.activeProcessingState = restoredState; - } + private getOrCreateAbortController(convId: string): AbortController { + let c = this.abortControllers.get(convId); + if (!c || c.signal.aborted) { + c = new AbortController(); + this.abortControllers.set(convId, c); + } + return c; + } - return; - } + private abortRequest(convId?: string): void { + if (convId) { + const c = this.abortControllers.get(convId); + if (c) { + c.abort(); + this.abortControllers.delete(convId); } + } else { + for (const c of this.abortControllers.values()) c.abort(); + this.abortControllers.clear(); } } - // ───────────────────────────────────────────────────────────────────────────── - // Streaming - // ───────────────────────────────────────────────────────────────────────────── - - /** - * Start streaming session tracking - */ - startStreaming(): void { - this.isStreamingActive = true; + private showErrorDialog(state: ErrorDialogState | null): void { + this.errorDialogState = state; } - /** - * Stop streaming session tracking - */ - stopStreaming(): void { - this.isStreamingActive = false; + dismissErrorDialog(): void { + this.errorDialogState = null; } - /** - * Check if currently in a streaming session - */ - isStreaming(): boolean { - return this.isStreamingActive; + clearEditMode(): void { + this.isEditModeActive = false; + this.addFilesHandler = null; } - private getContextTotal(): number { - const activeState = this.getActiveProcessingState(); - - if (activeState && activeState.contextTotal > 0) { - return activeState.contextTotal; - } - - if (isRouterMode()) { - const modelContextSize = selectedModelContextSize(); - if (modelContextSize && modelContextSize > 0) { - return modelContextSize; - } - } + isEditing(): boolean { + return this.isEditModeActive; + } - const propsContextSize = contextSize(); - if (propsContextSize && propsContextSize > 0) { - return propsContextSize; - } + setEditModeActive(handler: (files: File[]) => void): void { + this.isEditModeActive = true; + this.addFilesHandler = handler; + } - return DEFAULT_CONTEXT; + getAddFilesHandler(): ((files: File[]) => void) | null { + return this.addFilesHandler; } - private parseTimingData(timingData: Record): ApiProcessingState | null { - const promptTokens = (timingData.prompt_n as number) || 0; - const promptMs = (timingData.prompt_ms as number) || undefined; - const predictedTokens = (timingData.predicted_n as number) || 0; - const tokensPerSecond = (timingData.predicted_per_second as number) || 0; - const cacheTokens = (timingData.cache_n as number) || 0; - const promptProgress = timingData.prompt_progress as - | { - total: number; - cache: number; - processed: number; - time_ms: number; - } - | undefined; + clearPendingEditMessageId(): void { + this.pendingEditMessageId = null; + } - const contextTotal = this.getContextTotal(); - const currentConfig = config(); - const outputTokensMax = currentConfig.max_tokens || -1; + savePendingDraft(message: string, files: ChatUploadedFile[]): void { + this._pendingDraftMessage = message; + this._pendingDraftFiles = [...files]; + } - // Note: for timings data, the n_prompt does NOT include cache tokens - const contextUsed = promptTokens + cacheTokens + predictedTokens; - const outputTokensUsed = predictedTokens; + consumePendingDraft(): { message: string; files: ChatUploadedFile[] } | null { + if (!this._pendingDraftMessage && this._pendingDraftFiles.length === 0) return null; + const d = { message: this._pendingDraftMessage, files: [...this._pendingDraftFiles] }; + this._pendingDraftMessage = ''; + this._pendingDraftFiles = []; + return d; + } - // Note: for prompt progress, the "processed" DOES include cache tokens - // we need to exclude them to get the real prompt tokens processed count - const progressCache = promptProgress?.cache || 0; - const progressActualDone = (promptProgress?.processed ?? 0) - progressCache; - const progressActualTotal = (promptProgress?.total ?? 0) - progressCache; - const progressPercent = promptProgress - ? Math.round((progressActualDone / progressActualTotal) * 100) - : undefined; + hasPendingDraft(): boolean { + return Boolean(this._pendingDraftMessage) || this._pendingDraftFiles.length > 0; + } - return { - status: predictedTokens > 0 ? 'generating' : promptProgress ? 'preparing' : 'idle', - tokensDecoded: predictedTokens, - tokensRemaining: outputTokensMax - predictedTokens, - contextUsed, - contextTotal, - outputTokensUsed, - outputTokensMax, - hasNextToken: predictedTokens > 0, - tokensPerSecond, - temperature: currentConfig.temperature ?? 0.8, - topP: currentConfig.top_p ?? 0.95, - speculative: false, - progressPercent, - promptProgress, - promptTokens, - promptMs, - cacheTokens - }; + getAllLoadingChats(): string[] { + return Array.from(this.chatLoadingStates.keys()); } - /** - * Gets the model used in a conversation based on the latest assistant message. - * Returns the model from the most recent assistant message that has a model field set. - * - * @param messages - Array of messages to search through - * @returns The model name or null if no model found - */ - getConversationModel(messages: DatabaseMessage[]): string | null { - // Search backwards through messages to find most recent assistant message with model - for (let i = messages.length - 1; i >= 0; i--) { - const message = messages[i]; - if (message.role === 'assistant' && message.model) { - return message.model; - } - } - return null; + getAllStreamingChats(): string[] { + return Array.from(this.chatStreamingStates.keys()); } - // ───────────────────────────────────────────────────────────────────────────── - // Error Handling - // ───────────────────────────────────────────────────────────────────────────── + getChatStreamingPublic(convId: string): { response: string; messageId: string } | undefined { + return this.getChatStreaming(convId); + } - private isAbortError(error: unknown): boolean { - return error instanceof Error && (error.name === 'AbortError' || error instanceof DOMException); + isChatLoadingPublic(convId: string): boolean { + return this.chatLoadingStates.get(convId) || false; } - private showErrorDialog( - type: 'timeout' | 'server', - message: string, - contextInfo?: { n_prompt_tokens: number; n_ctx: number } - ): void { - this.errorDialogState = { type, message, contextInfo }; + private isChatLoadingInternal(convId: string): boolean { + return this.chatStreamingStates.has(convId); } - dismissErrorDialog(): void { - this.errorDialogState = null; + private touchConversationState(convId: string): void { + this.conversationStateTimestamps.set(convId, { lastAccessed: Date.now() }); } - // ───────────────────────────────────────────────────────────────────────────── - // Message Operations - // ───────────────────────────────────────────────────────────────────────────── + cleanupOldConversationStates(activeConversationIds?: string[]): number { + const now = Date.now(); + const activeIdsList = activeConversationIds ?? []; + const preserveIds = this.activeConversationId + ? [...activeIdsList, this.activeConversationId] + : activeIdsList; + const allConvIds = [ + ...new Set([ + ...this.chatLoadingStates.keys(), + ...this.chatStreamingStates.keys(), + ...this.abortControllers.keys(), + ...this.processingStates.keys(), + ...this.conversationStateTimestamps.keys() + ]) + ]; + const cleanupCandidates: Array<{ convId: string; lastAccessed: number }> = []; + for (const convId of allConvIds) { + if (preserveIds.includes(convId)) continue; + if (this.chatLoadingStates.get(convId)) continue; + if (this.chatStreamingStates.has(convId)) continue; + const ts = this.conversationStateTimestamps.get(convId); + cleanupCandidates.push({ convId, lastAccessed: ts?.lastAccessed ?? 0 }); + } + cleanupCandidates.sort((a, b) => a.lastAccessed - b.lastAccessed); + let cleanedUp = 0; + for (const { convId, lastAccessed } of cleanupCandidates) { + if ( + cleanupCandidates.length - cleanedUp > MAX_INACTIVE_CONVERSATION_STATES || + now - lastAccessed > INACTIVE_CONVERSATION_STATE_MAX_AGE_MS + ) { + this.cleanupConversationState(convId); + cleanedUp++; + } + } + return cleanedUp; + } + private cleanupConversationState(convId: string): void { + const c = this.abortControllers.get(convId); + if (c && !c.signal.aborted) c.abort(); + this.chatLoadingStates.delete(convId); + this.chatStreamingStates.delete(convId); + this.abortControllers.delete(convId); + this.processingStates.delete(convId); + this.conversationStateTimestamps.delete(convId); + } + getTrackedConversationCount(): number { + return new Set([ + ...this.chatLoadingStates.keys(), + ...this.chatStreamingStates.keys(), + ...this.abortControllers.keys(), + ...this.processingStates.keys() + ]).size; + } - /** - * Finds a message by ID and optionally validates its role. - * Returns message and index, or null if not found or role doesn't match. - */ private getMessageByIdWithRole( messageId: string, - expectedRole?: ChatRole + expectedRole?: MessageRole ): { message: DatabaseMessage; index: number } | null { const index = conversationsStore.findMessageIndex(messageId); if (index === -1) return null; - const message = conversationsStore.activeMessages[index]; if (expectedRole && message.role !== expectedRole) return null; - return { message, index }; } async addMessage( - role: ChatRole, + role: MessageRole, content: string, - type: ChatMessageType = 'text', + type: MessageType = MessageType.TEXT, parent: string = '-1', extras?: DatabaseMessageExtra[] - ): Promise { + ): Promise { const activeConv = conversationsStore.activeConversation; - if (!activeConv) { - console.error('No active conversation when trying to add message'); - return null; - } - - try { - let parentId: string | null = null; - - if (parent === '-1') { - const activeMessages = conversationsStore.activeMessages; - if (activeMessages.length > 0) { - parentId = activeMessages[activeMessages.length - 1].id; - } else { - const allMessages = await conversationsStore.getConversationMessages(activeConv.id); - const rootMessage = allMessages.find((m) => m.parent === null && m.type === 'root'); - if (!rootMessage) { - parentId = await DatabaseService.createRootMessage(activeConv.id); - } else { - parentId = rootMessage.id; - } - } - } else { - parentId = parent; + if (!activeConv) throw new Error('No active conversation'); + let parentId: string | null = null; + if (parent === '-1') { + const am = conversationsStore.activeMessages; + if (am.length > 0) parentId = am[am.length - 1].id; + else { + const all = await conversationsStore.getConversationMessages(activeConv.id); + const r = all.find((m) => m.parent === null && m.type === 'root'); + parentId = r ? r.id : await DatabaseService.createRootMessage(activeConv.id); } - - const message = await DatabaseService.createMessageBranch( - { - convId: activeConv.id, - role, - content, - type, - timestamp: Date.now(), - thinking: '', - toolCalls: '', - children: [], - extra: extras - }, - parentId - ); - - conversationsStore.addMessageToActive(message); - await conversationsStore.updateCurrentNode(message.id); - conversationsStore.updateConversationTimestamp(); - - return message; - } catch (error) { - console.error('Failed to add message:', error); - return null; - } + } else parentId = parent; + const message = await DatabaseService.createMessageBranch( + { + convId: activeConv.id, + role, + content, + type, + timestamp: Date.now(), + toolCalls: '', + children: [], + extra: extras + }, + parentId + ); + conversationsStore.addMessageToActive(message); + await conversationsStore.updateCurrentNode(message.id); + conversationsStore.updateConversationTimestamp(); + return message; } - /** - * Adds a system message at the top of a conversation and triggers edit mode. - * The system message is inserted between root and the first message of the active branch. - * Creates a new conversation if one doesn't exist. - */ async addSystemPrompt(): Promise { let activeConv = conversationsStore.activeConversation; - - // Create conversation if needed if (!activeConv) { await conversationsStore.createConversation(); activeConv = conversationsStore.activeConversation; } if (!activeConv) return; - try { - // Get all messages to find the root const allMessages = await conversationsStore.getConversationMessages(activeConv.id); const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null); - let rootId: string; - - // Create root message if it doesn't exist - if (!rootMessage) { - rootId = await DatabaseService.createRootMessage(activeConv.id); - } else { - rootId = rootMessage.id; - } - - // Check if there's already a system message as root's child + const rootId = rootMessage + ? rootMessage.id + : await DatabaseService.createRootMessage(activeConv.id); const existingSystemMessage = allMessages.find( - (m) => m.role === 'system' && m.parent === rootId + (m) => m.role === MessageRole.SYSTEM && m.parent === rootId ); - if (existingSystemMessage) { - // If system message exists, just trigger edit mode on it this.pendingEditMessageId = existingSystemMessage.id; - - // Make sure it's in active messages at the beginning - if (!conversationsStore.activeMessages.some((m) => m.id === existingSystemMessage.id)) { + if (!conversationsStore.activeMessages.some((m) => m.id === existingSystemMessage.id)) conversationsStore.activeMessages.unshift(existingSystemMessage); - } return; } - - // Find the first message of the active branch (child of root that's in activeMessages) - const activeMessages = conversationsStore.activeMessages; - const firstActiveMessage = activeMessages.find((m) => m.parent === rootId); - - // Create new system message with placeholder content (will be edited by user) + const am = conversationsStore.activeMessages; + const firstActiveMessage = am.find((m) => m.parent === rootId); const systemMessage = await DatabaseService.createSystemMessage( activeConv.id, SYSTEM_MESSAGE_PLACEHOLDER, rootId ); - - // If there's a first message in the active branch, re-parent it to the system message if (firstActiveMessage) { - // Update the first message's parent to be the system message - await DatabaseService.updateMessage(firstActiveMessage.id, { - parent: systemMessage.id - }); - - // Update the system message's children to include the first message + await DatabaseService.updateMessage(firstActiveMessage.id, { parent: systemMessage.id }); await DatabaseService.updateMessage(systemMessage.id, { children: [firstActiveMessage.id] }); - - // Remove first message from root's children const updatedRootChildren = rootMessage ? rootMessage.children.filter((id: string) => id !== firstActiveMessage.id) : []; - // Note: system message was already added to root's children by createSystemMessage await DatabaseService.updateMessage(rootId, { children: [ ...updatedRootChildren.filter((id: string) => id !== systemMessage.id), systemMessage.id ] }); - - // Update local state const firstMsgIndex = conversationsStore.findMessageIndex(firstActiveMessage.id); - if (firstMsgIndex !== -1) { + if (firstMsgIndex !== -1) conversationsStore.updateMessageAtIndex(firstMsgIndex, { parent: systemMessage.id }); - } } - - // Add system message to active messages at the beginning conversationsStore.activeMessages.unshift(systemMessage); - - // Set pending edit message ID to trigger edit mode this.pendingEditMessageId = systemMessage.id; - conversationsStore.updateConversationTimestamp(); } catch (error) { console.error('Failed to add system prompt:', error); } } - /** - * Removes a system message placeholder without deleting its children. - * Re-parents children back to the root message. - * If this is a new empty conversation (only root + system placeholder), deletes the entire conversation. - * @returns true if the entire conversation was deleted, false otherwise - */ async removeSystemPromptPlaceholder(messageId: string): Promise { const activeConv = conversationsStore.activeConversation; if (!activeConv) return false; - try { const allMessages = await conversationsStore.getConversationMessages(activeConv.id); const systemMessage = allMessages.find((m) => m.id === messageId); - if (!systemMessage || systemMessage.role !== 'system') return false; - + if (!systemMessage || systemMessage.role !== MessageRole.SYSTEM) return false; const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null); if (!rootMessage) return false; - - // Check if this is a new empty conversation (only root + system placeholder) - const isEmptyConversation = allMessages.length === 2 && systemMessage.children.length === 0; - - if (isEmptyConversation) { - // Delete the entire conversation + if (allMessages.length === 2 && systemMessage.children.length === 0) { await conversationsStore.deleteConversation(activeConv.id); return true; } - - // Re-parent system message's children to root for (const childId of systemMessage.children) { await DatabaseService.updateMessage(childId, { parent: rootMessage.id }); - - // Update local state const childIndex = conversationsStore.findMessageIndex(childId); - if (childIndex !== -1) { + if (childIndex !== -1) conversationsStore.updateMessageAtIndex(childIndex, { parent: rootMessage.id }); - } } - - // Update root's children: remove system message, add system's children - const newRootChildren = [ - ...rootMessage.children.filter((id: string) => id !== messageId), - ...systemMessage.children - ]; - await DatabaseService.updateMessage(rootMessage.id, { children: newRootChildren }); - - // Delete the system message (without cascade) + await DatabaseService.updateMessage(rootMessage.id, { + children: [ + ...rootMessage.children.filter((id: string) => id !== messageId), + ...systemMessage.children + ] + }); await DatabaseService.deleteMessage(messageId); - - // Remove from active messages const systemIndex = conversationsStore.findMessageIndex(messageId); - if (systemIndex !== -1) { - conversationsStore.activeMessages.splice(systemIndex, 1); - } - + if (systemIndex !== -1) conversationsStore.activeMessages.splice(systemIndex, 1); conversationsStore.updateConversationTimestamp(); return false; } catch (error) { @@ -631,18 +447,16 @@ class ChatStore { } } - private async createAssistantMessage(parentId?: string): Promise { + private async createAssistantMessage(parentId?: string): Promise { const activeConv = conversationsStore.activeConversation; - if (!activeConv) return null; - + if (!activeConv) throw new Error('No active conversation'); return await DatabaseService.createMessageBranch( { convId: activeConv.id, - type: 'text', - role: 'assistant', + type: MessageType.TEXT, + role: MessageRole.ASSISTANT, content: '', timestamp: Date.now(), - thinking: '', toolCalls: '', children: [], model: null @@ -651,174 +465,14 @@ class ChatStore { ); } - private async streamChatCompletion( - allMessages: DatabaseMessage[], - assistantMessage: DatabaseMessage, - onComplete?: (content: string) => Promise, - onError?: (error: Error) => void, - modelOverride?: string | null - ): Promise { - // Ensure model props are cached before streaming (for correct n_ctx in processing info) - if (isRouterMode()) { - const modelName = modelOverride || selectedModelName(); - if (modelName && !modelsStore.getModelProps(modelName)) { - await modelsStore.fetchModelProps(modelName); - } - } - - let streamedContent = ''; - let streamedReasoningContent = ''; - let streamedToolCallContent = ''; - let resolvedModel: string | null = null; - let modelPersisted = false; - - const recordModel = (modelName: string | null | undefined, persistImmediately = true): void => { - if (!modelName) return; - const normalizedModel = normalizeModelName(modelName); - if (!normalizedModel || normalizedModel === resolvedModel) return; - resolvedModel = normalizedModel; - const messageIndex = conversationsStore.findMessageIndex(assistantMessage.id); - conversationsStore.updateMessageAtIndex(messageIndex, { model: normalizedModel }); - if (persistImmediately && !modelPersisted) { - modelPersisted = true; - DatabaseService.updateMessage(assistantMessage.id, { model: normalizedModel }).catch(() => { - modelPersisted = false; - resolvedModel = null; - }); - } - }; - - this.startStreaming(); - this.setActiveProcessingConversation(assistantMessage.convId); - - const abortController = this.getOrCreateAbortController(assistantMessage.convId); - - await ChatService.sendMessage( - allMessages, - { - ...this.getApiOptions(), - ...(modelOverride ? { model: modelOverride } : {}), - onChunk: (chunk: string) => { - streamedContent += chunk; - this.setChatStreaming(assistantMessage.convId, streamedContent, assistantMessage.id); - const idx = conversationsStore.findMessageIndex(assistantMessage.id); - conversationsStore.updateMessageAtIndex(idx, { content: streamedContent }); - }, - onReasoningChunk: (reasoningChunk: string) => { - streamedReasoningContent += reasoningChunk; - const idx = conversationsStore.findMessageIndex(assistantMessage.id); - conversationsStore.updateMessageAtIndex(idx, { thinking: streamedReasoningContent }); - }, - onToolCallChunk: (toolCallChunk: string) => { - const chunk = toolCallChunk.trim(); - if (!chunk) return; - streamedToolCallContent = chunk; - const idx = conversationsStore.findMessageIndex(assistantMessage.id); - conversationsStore.updateMessageAtIndex(idx, { toolCalls: streamedToolCallContent }); - }, - onModel: (modelName: string) => recordModel(modelName), - onTimings: (timings?: ChatMessageTimings, promptProgress?: ChatMessagePromptProgress) => { - const tokensPerSecond = - timings?.predicted_ms && timings?.predicted_n - ? (timings.predicted_n / timings.predicted_ms) * 1000 - : 0; - this.updateProcessingStateFromTimings( - { - prompt_n: timings?.prompt_n || 0, - prompt_ms: timings?.prompt_ms, - predicted_n: timings?.predicted_n || 0, - predicted_per_second: tokensPerSecond, - cache_n: timings?.cache_n || 0, - prompt_progress: promptProgress - }, - assistantMessage.convId - ); - }, - onComplete: async ( - finalContent?: string, - reasoningContent?: string, - timings?: ChatMessageTimings, - toolCallContent?: string - ) => { - this.stopStreaming(); - - const updateData: Record = { - content: finalContent || streamedContent, - thinking: reasoningContent || streamedReasoningContent, - toolCalls: toolCallContent || streamedToolCallContent, - timings - }; - if (resolvedModel && !modelPersisted) { - updateData.model = resolvedModel; - } - await DatabaseService.updateMessage(assistantMessage.id, updateData); - - const idx = conversationsStore.findMessageIndex(assistantMessage.id); - const uiUpdate: Partial = { - content: updateData.content as string, - toolCalls: updateData.toolCalls as string - }; - if (timings) uiUpdate.timings = timings; - if (resolvedModel) uiUpdate.model = resolvedModel; - - conversationsStore.updateMessageAtIndex(idx, uiUpdate); - await conversationsStore.updateCurrentNode(assistantMessage.id); - - if (onComplete) await onComplete(streamedContent); - this.setChatLoading(assistantMessage.convId, false); - this.clearChatStreaming(assistantMessage.convId); - this.clearProcessingState(assistantMessage.convId); - - if (isRouterMode()) { - modelsStore.fetchRouterModels().catch(console.error); - } - }, - onError: (error: Error) => { - this.stopStreaming(); - - if (this.isAbortError(error)) { - this.setChatLoading(assistantMessage.convId, false); - this.clearChatStreaming(assistantMessage.convId); - this.clearProcessingState(assistantMessage.convId); - - return; - } - - console.error('Streaming error:', error); - - this.setChatLoading(assistantMessage.convId, false); - this.clearChatStreaming(assistantMessage.convId); - this.clearProcessingState(assistantMessage.convId); - - const idx = conversationsStore.findMessageIndex(assistantMessage.id); - - if (idx !== -1) { - const failedMessage = conversationsStore.removeMessageAtIndex(idx); - if (failedMessage) DatabaseService.deleteMessage(failedMessage.id).catch(console.error); - } - - const contextInfo = ( - error as Error & { contextInfo?: { n_prompt_tokens: number; n_ctx: number } } - ).contextInfo; - - this.showErrorDialog( - error.name === 'TimeoutError' ? 'timeout' : 'server', - error.message, - contextInfo - ); - - if (onError) onError(error); - } - }, - assistantMessage.convId, - abortController.signal - ); - } - async sendMessage(content: string, extras?: DatabaseMessageExtra[]): Promise { if (!content.trim() && (!extras || extras.length === 0)) return; const activeConv = conversationsStore.activeConversation; - if (activeConv && this.isChatLoading(activeConv.id)) return; + if (activeConv && this.isChatLoadingInternal(activeConv.id)) return; + + // Consume MCP resource attachments - converts them to extras and clears the live store + const resourceExtras = mcpStore.consumeResourceAttachmentsAsExtras(); + const allExtras = resourceExtras.length > 0 ? [...(extras || []), ...resourceExtras] : extras; let isNewConversation = false; if (!activeConv) { @@ -827,137 +481,295 @@ class ChatStore { } const currentConv = conversationsStore.activeConversation; if (!currentConv) return; - - this.errorDialogState = null; + this.showErrorDialog(null); this.setChatLoading(currentConv.id, true); this.clearChatStreaming(currentConv.id); - try { + let parentIdForUserMessage: string | undefined; if (isNewConversation) { const rootId = await DatabaseService.createRootMessage(currentConv.id); const currentConfig = config(); const systemPrompt = currentConfig.systemMessage?.toString().trim(); - if (systemPrompt) { const systemMessage = await DatabaseService.createSystemMessage( currentConv.id, systemPrompt, rootId ); - conversationsStore.addMessageToActive(systemMessage); - } + parentIdForUserMessage = systemMessage.id; + } else parentIdForUserMessage = rootId; } - - const userMessage = await this.addMessage('user', content, 'text', '-1', extras); - if (!userMessage) throw new Error('Failed to add user message'); + const userMessage = await this.addMessage( + MessageRole.USER, + content, + MessageType.TEXT, + parentIdForUserMessage ?? '-1', + allExtras + ); if (isNewConversation && content) await conversationsStore.updateConversationName(currentConv.id, content.trim()); - const assistantMessage = await this.createAssistantMessage(userMessage.id); - - if (!assistantMessage) throw new Error('Failed to create assistant message'); - conversationsStore.addMessageToActive(assistantMessage); await this.streamChatCompletion( conversationsStore.activeMessages.slice(0, -1), assistantMessage ); } catch (error) { - if (this.isAbortError(error)) { + if (isAbortError(error)) { this.setChatLoading(currentConv.id, false); return; } console.error('Failed to send message:', error); this.setChatLoading(currentConv.id, false); - if (!this.errorDialogState) { - const dialogType = - error instanceof Error && error.name === 'TimeoutError' ? 'timeout' : 'server'; + const dialogType = + error instanceof Error && error.name === 'TimeoutError' + ? ErrorDialogType.TIMEOUT + : ErrorDialogType.SERVER; + const contextInfo = ( + error as Error & { contextInfo?: { n_prompt_tokens: number; n_ctx: number } } + ).contextInfo; + this.showErrorDialog({ + type: dialogType, + message: error instanceof Error ? error.message : 'Unknown error', + contextInfo + }); + } + } + + private async streamChatCompletion( + allMessages: DatabaseMessage[], + assistantMessage: DatabaseMessage, + onComplete?: (content: string) => Promise, + onError?: (error: Error) => void, + modelOverride?: string | null + ): Promise { + let effectiveModel = modelOverride; + + if (isRouterMode() && !effectiveModel) { + const conversationModel = this.getConversationModel(allMessages); + effectiveModel = selectedModelName() || conversationModel; + } + + if (isRouterMode() && effectiveModel) { + if (!modelsStore.getModelProps(effectiveModel)) + await modelsStore.fetchModelProps(effectiveModel); + } + + let streamedContent = '', + streamedToolCallContent = '', + isReasoningOpen = false, + hasStreamedChunks = false, + resolvedModel: string | null = null, + modelPersisted = false; + let streamedExtras: DatabaseMessageExtra[] = assistantMessage.extra + ? JSON.parse(JSON.stringify(assistantMessage.extra)) + : []; + const recordModel = (modelName: string | null | undefined, persistImmediately = true): void => { + if (!modelName) return; + const n = normalizeModelName(modelName); + if (!n || n === resolvedModel) return; + resolvedModel = n; + const idx = conversationsStore.findMessageIndex(assistantMessage.id); + conversationsStore.updateMessageAtIndex(idx, { model: n }); + if (persistImmediately && !modelPersisted) { + modelPersisted = true; + DatabaseService.updateMessage(assistantMessage.id, { model: n }).catch(() => { + modelPersisted = false; + resolvedModel = null; + }); + } + }; + const updateStreamingContent = () => { + this.setChatStreaming(assistantMessage.convId, streamedContent, assistantMessage.id); + const idx = conversationsStore.findMessageIndex(assistantMessage.id); + conversationsStore.updateMessageAtIndex(idx, { content: streamedContent }); + }; + const appendContentChunk = (chunk: string) => { + if (isReasoningOpen) { + streamedContent += REASONING_TAGS.END; + isReasoningOpen = false; + } + streamedContent += chunk; + hasStreamedChunks = true; + updateStreamingContent(); + }; + const appendReasoningChunk = (chunk: string) => { + if (!isReasoningOpen) { + streamedContent += REASONING_TAGS.START; + isReasoningOpen = true; + } + streamedContent += chunk; + hasStreamedChunks = true; + updateStreamingContent(); + }; + const finalizeReasoning = () => { + if (isReasoningOpen) { + streamedContent += REASONING_TAGS.END; + isReasoningOpen = false; + } + }; + this.setStreamingActive(true); + this.setActiveProcessingConversation(assistantMessage.convId); + const abortController = this.getOrCreateAbortController(assistantMessage.convId); + const streamCallbacks: ChatStreamCallbacks = { + onChunk: (chunk: string) => appendContentChunk(chunk), + onReasoningChunk: (chunk: string) => appendReasoningChunk(chunk), + onToolCallChunk: (chunk: string) => { + const c = chunk.trim(); + if (!c) return; + streamedToolCallContent = c; + const idx = conversationsStore.findMessageIndex(assistantMessage.id); + conversationsStore.updateMessageAtIndex(idx, { toolCalls: streamedToolCallContent }); + }, + onAttachments: (extras: DatabaseMessageExtra[]) => { + if (!extras.length) return; + streamedExtras = [...streamedExtras, ...extras]; + const idx = conversationsStore.findMessageIndex(assistantMessage.id); + conversationsStore.updateMessageAtIndex(idx, { extra: streamedExtras }); + DatabaseService.updateMessage(assistantMessage.id, { extra: streamedExtras }).catch( + console.error + ); + }, + onModel: (modelName: string) => recordModel(modelName), + onTimings: (timings?: ChatMessageTimings, promptProgress?: ChatMessagePromptProgress) => { + const tokensPerSecond = + timings?.predicted_ms && timings?.predicted_n + ? (timings.predicted_n / timings.predicted_ms) * 1000 + : 0; + this.updateProcessingStateFromTimings( + { + prompt_n: timings?.prompt_n || 0, + prompt_ms: timings?.prompt_ms, + predicted_n: timings?.predicted_n || 0, + predicted_per_second: tokensPerSecond, + cache_n: timings?.cache_n || 0, + prompt_progress: promptProgress + }, + assistantMessage.convId + ); + }, + onComplete: async ( + finalContent?: string, + reasoningContent?: string, + timings?: ChatMessageTimings, + toolCallContent?: string + ) => { + this.setStreamingActive(false); + finalizeReasoning(); + const combinedContent = hasStreamedChunks + ? streamedContent + : wrapReasoningContent(finalContent || '', reasoningContent); + const updateData: Record = { + content: combinedContent, + toolCalls: toolCallContent || streamedToolCallContent, + timings + }; + if (streamedExtras.length > 0) updateData.extra = streamedExtras; + if (resolvedModel && !modelPersisted) updateData.model = resolvedModel; + await DatabaseService.updateMessage(assistantMessage.id, updateData); + const idx = conversationsStore.findMessageIndex(assistantMessage.id); + const uiUpdate: Partial = { + content: combinedContent, + toolCalls: updateData.toolCalls as string + }; + if (streamedExtras.length > 0) uiUpdate.extra = streamedExtras; + if (timings) uiUpdate.timings = timings; + if (resolvedModel) uiUpdate.model = resolvedModel; + conversationsStore.updateMessageAtIndex(idx, uiUpdate); + await conversationsStore.updateCurrentNode(assistantMessage.id); + if (onComplete) await onComplete(combinedContent); + this.setChatLoading(assistantMessage.convId, false); + this.clearChatStreaming(assistantMessage.convId); + this.setProcessingState(assistantMessage.convId, null); + if (isRouterMode()) modelsStore.fetchRouterModels().catch(console.error); + }, + onError: (error: Error) => { + this.setStreamingActive(false); + if (isAbortError(error)) { + this.setChatLoading(assistantMessage.convId, false); + this.clearChatStreaming(assistantMessage.convId); + this.setProcessingState(assistantMessage.convId, null); + return; + } + console.error('Streaming error:', error); + this.setChatLoading(assistantMessage.convId, false); + this.clearChatStreaming(assistantMessage.convId); + this.setProcessingState(assistantMessage.convId, null); + const idx = conversationsStore.findMessageIndex(assistantMessage.id); + if (idx !== -1) { + const failedMessage = conversationsStore.removeMessageAtIndex(idx); + if (failedMessage) DatabaseService.deleteMessage(failedMessage.id).catch(console.error); + } const contextInfo = ( error as Error & { contextInfo?: { n_prompt_tokens: number; n_ctx: number } } ).contextInfo; - - this.showErrorDialog( - dialogType, - error instanceof Error ? error.message : 'Unknown error', + this.showErrorDialog({ + type: error.name === 'TimeoutError' ? ErrorDialogType.TIMEOUT : ErrorDialogType.SERVER, + message: error.message, contextInfo - ); + }); + if (onError) onError(error); } + }; + const perChatOverrides = conversationsStore.activeConversation?.mcpServerOverrides; + + const agenticConfig = agenticStore.getConfig(config(), perChatOverrides); + if (agenticConfig.enabled) { + const agenticResult = await agenticStore.runAgenticFlow({ + conversationId: assistantMessage.convId, + messages: allMessages, + options: { ...this.getApiOptions(), ...(effectiveModel ? { model: effectiveModel } : {}) }, + callbacks: streamCallbacks, + signal: abortController.signal, + perChatOverrides + }); + if (agenticResult.handled) return; } + + const completionOptions = { + ...this.getApiOptions(), + ...(effectiveModel ? { model: effectiveModel } : {}), + ...streamCallbacks + }; + + await ChatService.sendMessage( + allMessages, + completionOptions, + assistantMessage.convId, + abortController.signal + ); } async stopGeneration(): Promise { const activeConv = conversationsStore.activeConversation; - if (!activeConv) return; - await this.stopGenerationForChat(activeConv.id); } - async stopGenerationForChat(convId: string): Promise { await this.savePartialResponseIfNeeded(convId); - - this.stopStreaming(); + this.setStreamingActive(false); this.abortRequest(convId); this.setChatLoading(convId, false); this.clearChatStreaming(convId); - this.clearProcessingState(convId); - } - - /** - * Gets or creates an AbortController for a conversation - */ - private getOrCreateAbortController(convId: string): AbortController { - let controller = this.abortControllers.get(convId); - if (!controller || controller.signal.aborted) { - controller = new AbortController(); - this.abortControllers.set(convId, controller); - } - return controller; + this.setProcessingState(convId, null); } - - /** - * Aborts any ongoing request for a conversation - */ - private abortRequest(convId?: string): void { - if (convId) { - const controller = this.abortControllers.get(convId); - if (controller) { - controller.abort(); - this.abortControllers.delete(convId); - } - } else { - for (const controller of this.abortControllers.values()) { - controller.abort(); - } - this.abortControllers.clear(); - } - } - private async savePartialResponseIfNeeded(convId?: string): Promise { const conversationId = convId || conversationsStore.activeConversation?.id; - if (!conversationId) return; - - const streamingState = this.chatStreamingStates.get(conversationId); - + const streamingState = this.getChatStreaming(conversationId); if (!streamingState || !streamingState.response.trim()) return; - const messages = conversationId === conversationsStore.activeConversation?.id ? conversationsStore.activeMessages : await conversationsStore.getConversationMessages(conversationId); - if (!messages.length) return; - const lastMessage = messages[messages.length - 1]; - - if (lastMessage?.role === 'assistant') { + if (lastMessage?.role === MessageRole.ASSISTANT) { try { - const updateData: { content: string; thinking?: string; timings?: ChatMessageTimings } = { + const updateData: { content: string; timings?: ChatMessageTimings } = { content: streamingState.response }; - if (lastMessage.thinking?.trim()) updateData.thinking = lastMessage.thinking; const lastKnownState = this.getProcessingState(conversationId); if (lastKnownState) { updateData.timings = { @@ -971,16 +783,11 @@ class ChatStore { : undefined }; } - await DatabaseService.updateMessage(lastMessage.id, updateData); - - lastMessage.content = this.currentResponse; - - if (updateData.thinking) lastMessage.thinking = updateData.thinking; - + lastMessage.content = streamingState.response; if (updateData.timings) lastMessage.timings = updateData.timings; } catch (error) { - lastMessage.content = this.currentResponse; + lastMessage.content = streamingState.response; console.error('Failed to save partial response:', error); } } @@ -989,45 +796,30 @@ class ChatStore { async updateMessage(messageId: string, newContent: string): Promise { const activeConv = conversationsStore.activeConversation; if (!activeConv) return; - if (this.isLoading) this.stopGeneration(); - - const result = this.getMessageByIdWithRole(messageId, 'user'); + if (this.isChatLoadingInternal(activeConv.id)) await this.stopGeneration(); + const result = this.getMessageByIdWithRole(messageId, MessageRole.USER); if (!result) return; const { message: messageToUpdate, index: messageIndex } = result; const originalContent = messageToUpdate.content; - try { const allMessages = await conversationsStore.getConversationMessages(activeConv.id); const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null); const isFirstUserMessage = rootMessage && messageToUpdate.parent === rootMessage.id; - conversationsStore.updateMessageAtIndex(messageIndex, { content: newContent }); await DatabaseService.updateMessage(messageId, { content: newContent }); - - if (isFirstUserMessage && newContent.trim()) { + if (isFirstUserMessage && newContent.trim()) await conversationsStore.updateConversationTitleWithConfirmation( activeConv.id, - newContent.trim(), - conversationsStore.titleUpdateConfirmationCallback + newContent.trim() ); - } - const messagesToRemove = conversationsStore.activeMessages.slice(messageIndex + 1); - for (const message of messagesToRemove) await DatabaseService.deleteMessage(message.id); - conversationsStore.sliceActiveMessages(messageIndex + 1); conversationsStore.updateConversationTimestamp(); - this.setChatLoading(activeConv.id, true); this.clearChatStreaming(activeConv.id); - const assistantMessage = await this.createAssistantMessage(); - - if (!assistantMessage) throw new Error('Failed to create assistant message'); - conversationsStore.addMessageToActive(assistantMessage); - await conversationsStore.updateCurrentNode(assistantMessage.id); await this.streamChatCompletion( conversationsStore.activeMessages.slice(0, -1), @@ -1040,44 +832,84 @@ class ChatStore { } ); } catch (error) { - if (!this.isAbortError(error)) console.error('Failed to update message:', error); + if (!isAbortError(error)) console.error('Failed to update message:', error); } } - // ───────────────────────────────────────────────────────────────────────────── - // Regeneration - // ───────────────────────────────────────────────────────────────────────────── - async regenerateMessage(messageId: string): Promise { const activeConv = conversationsStore.activeConversation; - if (!activeConv || this.isLoading) return; - - const result = this.getMessageByIdWithRole(messageId, 'assistant'); + if (!activeConv || this.isChatLoadingInternal(activeConv.id)) return; + const result = this.getMessageByIdWithRole(messageId, MessageRole.ASSISTANT); if (!result) return; const { index: messageIndex } = result; - try { const messagesToRemove = conversationsStore.activeMessages.slice(messageIndex); for (const message of messagesToRemove) await DatabaseService.deleteMessage(message.id); conversationsStore.sliceActiveMessages(messageIndex); conversationsStore.updateConversationTimestamp(); - this.setChatLoading(activeConv.id, true); this.clearChatStreaming(activeConv.id); - const parentMessageId = conversationsStore.activeMessages.length > 0 ? conversationsStore.activeMessages[conversationsStore.activeMessages.length - 1].id : undefined; const assistantMessage = await this.createAssistantMessage(parentMessageId); - if (!assistantMessage) throw new Error('Failed to create assistant message'); conversationsStore.addMessageToActive(assistantMessage); await this.streamChatCompletion( conversationsStore.activeMessages.slice(0, -1), assistantMessage ); } catch (error) { - if (!this.isAbortError(error)) console.error('Failed to regenerate message:', error); + if (!isAbortError(error)) console.error('Failed to regenerate message:', error); + this.setChatLoading(activeConv?.id || '', false); + } + } + + async regenerateMessageWithBranching(messageId: string, modelOverride?: string): Promise { + const activeConv = conversationsStore.activeConversation; + if (!activeConv || this.isChatLoadingInternal(activeConv.id)) return; + try { + const idx = conversationsStore.findMessageIndex(messageId); + if (idx === -1) return; + const msg = conversationsStore.activeMessages[idx]; + if (msg.role !== MessageRole.ASSISTANT) return; + const allMessages = await conversationsStore.getConversationMessages(activeConv.id); + const parentMessage = allMessages.find((m) => m.id === msg.parent); + if (!parentMessage) return; + this.setChatLoading(activeConv.id, true); + this.clearChatStreaming(activeConv.id); + const newAssistantMessage = await DatabaseService.createMessageBranch( + { + convId: msg.convId, + type: msg.type, + timestamp: Date.now(), + role: msg.role, + content: '', + toolCalls: '', + children: [], + model: null + }, + parentMessage.id + ); + await conversationsStore.updateCurrentNode(newAssistantMessage.id); + conversationsStore.updateConversationTimestamp(); + await conversationsStore.refreshActiveMessages(); + const conversationPath = filterByLeafNodeId( + allMessages, + parentMessage.id, + false + ) as DatabaseMessage[]; + const modelToUse = modelOverride || msg.model || undefined; + await this.streamChatCompletion( + conversationPath, + newAssistantMessage, + undefined, + undefined, + modelToUse + ); + } catch (error) { + if (!isAbortError(error)) + console.error('Failed to regenerate message with branching:', error); this.setChatLoading(activeConv?.id || '', false); } } @@ -1095,17 +927,17 @@ class ChatStore { const messageToDelete = allMessages.find((m) => m.id === messageId); // For system messages, don't count descendants as they will be preserved (reparented to root) - if (messageToDelete?.role === 'system') { + if (messageToDelete?.role === MessageRole.SYSTEM) { const messagesToDelete = allMessages.filter((m) => m.id === messageId); let userMessages = 0, assistantMessages = 0; const messageTypes: string[] = []; for (const msg of messagesToDelete) { - if (msg.role === 'user') { + if (msg.role === MessageRole.USER) { userMessages++; if (!messageTypes.includes('user message')) messageTypes.push('user message'); - } else if (msg.role === 'assistant') { + } else if (msg.role === MessageRole.ASSISTANT) { assistantMessages++; if (!messageTypes.includes('assistant response')) messageTypes.push('assistant response'); } @@ -1120,15 +952,17 @@ class ChatStore { let userMessages = 0, assistantMessages = 0; const messageTypes: string[] = []; + for (const msg of messagesToDelete) { - if (msg.role === 'user') { + if (msg.role === MessageRole.USER) { userMessages++; if (!messageTypes.includes('user message')) messageTypes.push('user message'); - } else if (msg.role === 'assistant') { + } else if (msg.role === MessageRole.ASSISTANT) { assistantMessages++; if (!messageTypes.includes('assistant response')) messageTypes.push('assistant response'); } } + return { totalCount: allToDelete.length, userMessages, assistantMessages, messageTypes }; } @@ -1138,6 +972,7 @@ class ChatStore { try { const allMessages = await conversationsStore.getConversationMessages(activeConv.id); const messageToDelete = allMessages.find((m) => m.id === messageId); + if (!messageToDelete) return; const currentPath = filterByLeafNodeId(allMessages, activeConv.currNode || '', false); @@ -1152,6 +987,7 @@ class ChatStore { const latestSibling = siblings.reduce((latest, sibling) => sibling.timestamp > latest.timestamp ? sibling : latest ); + await conversationsStore.updateCurrentNode(findLeafNode(allMessages, latestSibling.id)); } else if (messageToDelete.parent) { await conversationsStore.updateCurrentNode( @@ -1159,6 +995,7 @@ class ChatStore { ); } } + await DatabaseService.deleteMessageCascading(activeConv.id, messageId); await conversationsStore.refreshActiveMessages(); @@ -1168,27 +1005,17 @@ class ChatStore { } } - // ───────────────────────────────────────────────────────────────────────────── - // Editing - // ───────────────────────────────────────────────────────────────────────────── - - clearEditMode(): void { - this.isEditModeActive = false; - this.addFilesHandler = null; - } - async continueAssistantMessage(messageId: string): Promise { const activeConv = conversationsStore.activeConversation; - if (!activeConv || this.isLoading) return; + if (!activeConv || this.isChatLoadingInternal(activeConv.id)) return; + const result = this.getMessageByIdWithRole(messageId, MessageRole.ASSISTANT); - const result = this.getMessageByIdWithRole(messageId, 'assistant'); if (!result) return; - const { message: msg, index: idx } = result; - if (this.isChatLoading(activeConv.id)) return; + const { message: msg, index: idx } = result; try { - this.errorDialogState = null; + this.showErrorDialog(null); this.setChatLoading(activeConv.id, true); this.clearChatStreaming(activeConv.id); @@ -1197,22 +1024,51 @@ class ChatStore { if (!dbMessage) { this.setChatLoading(activeConv.id, false); - return; } const originalContent = dbMessage.content; - const originalThinking = dbMessage.thinking || ''; - const conversationContext = conversationsStore.activeMessages.slice(0, idx); const contextWithContinue = [ ...conversationContext, - { role: 'assistant' as const, content: originalContent } + { role: MessageRole.ASSISTANT as const, content: originalContent } ]; let appendedContent = '', - appendedThinking = '', - hasReceivedContent = false; + hasReceivedContent = false, + isReasoningOpen = hasUnclosedReasoningTag(originalContent); + + const updateStreamingContent = (fullContent: string) => { + this.setChatStreaming(msg.convId, fullContent, msg.id); + conversationsStore.updateMessageAtIndex(idx, { content: fullContent }); + }; + + const appendContentChunk = (chunk: string) => { + if (isReasoningOpen) { + appendedContent += REASONING_TAGS.END; + isReasoningOpen = false; + } + appendedContent += chunk; + hasReceivedContent = true; + updateStreamingContent(originalContent + appendedContent); + }; + + const appendReasoningChunk = (chunk: string) => { + if (!isReasoningOpen) { + appendedContent += REASONING_TAGS.START; + isReasoningOpen = true; + } + appendedContent += chunk; + hasReceivedContent = true; + updateStreamingContent(originalContent + appendedContent); + }; + + const finalizeReasoning = () => { + if (isReasoningOpen) { + appendedContent += REASONING_TAGS.END; + isReasoningOpen = false; + } + }; const abortController = this.getOrCreateAbortController(msg.convId); @@ -1220,23 +1076,8 @@ class ChatStore { contextWithContinue, { ...this.getApiOptions(), - - onChunk: (chunk: string) => { - hasReceivedContent = true; - appendedContent += chunk; - const fullContent = originalContent + appendedContent; - this.setChatStreaming(msg.convId, fullContent, msg.id); - conversationsStore.updateMessageAtIndex(idx, { content: fullContent }); - }, - - onReasoningChunk: (reasoningChunk: string) => { - hasReceivedContent = true; - appendedThinking += reasoningChunk; - conversationsStore.updateMessageAtIndex(idx, { - thinking: originalThinking + appendedThinking - }); - }, - + onChunk: (chunk: string) => appendContentChunk(chunk), + onReasoningChunk: (chunk: string) => appendReasoningChunk(chunk), onTimings: (timings?: ChatMessageTimings, promptProgress?: ChatMessagePromptProgress) => { const tokensPerSecond = timings?.predicted_ms && timings?.predicted_n @@ -1254,74 +1095,78 @@ class ChatStore { msg.convId ); }, - onComplete: async ( finalContent?: string, reasoningContent?: string, timings?: ChatMessageTimings ) => { - const fullContent = originalContent + (finalContent || appendedContent); - const fullThinking = originalThinking + (reasoningContent || appendedThinking); + finalizeReasoning(); + + const appendedFromCompletion = hasReceivedContent + ? appendedContent + : wrapReasoningContent(finalContent || '', reasoningContent); + const fullContent = originalContent + appendedFromCompletion; + await DatabaseService.updateMessage(msg.id, { content: fullContent, - thinking: fullThinking, timestamp: Date.now(), timings }); + conversationsStore.updateMessageAtIndex(idx, { content: fullContent, - thinking: fullThinking, timestamp: Date.now(), timings }); + conversationsStore.updateConversationTimestamp(); + this.setChatLoading(msg.convId, false); this.clearChatStreaming(msg.convId); - this.clearProcessingState(msg.convId); + this.setProcessingState(msg.convId, null); }, - onError: async (error: Error) => { - if (this.isAbortError(error)) { + if (isAbortError(error)) { if (hasReceivedContent && appendedContent) { await DatabaseService.updateMessage(msg.id, { content: originalContent + appendedContent, - thinking: originalThinking + appendedThinking, timestamp: Date.now() }); + conversationsStore.updateMessageAtIndex(idx, { content: originalContent + appendedContent, - thinking: originalThinking + appendedThinking, timestamp: Date.now() }); } + this.setChatLoading(msg.convId, false); this.clearChatStreaming(msg.convId); - this.clearProcessingState(msg.convId); + this.setProcessingState(msg.convId, null); + return; } + console.error('Continue generation error:', error); - conversationsStore.updateMessageAtIndex(idx, { - content: originalContent, - thinking: originalThinking - }); - await DatabaseService.updateMessage(msg.id, { - content: originalContent, - thinking: originalThinking - }); + conversationsStore.updateMessageAtIndex(idx, { content: originalContent }); + + await DatabaseService.updateMessage(msg.id, { content: originalContent }); + this.setChatLoading(msg.convId, false); this.clearChatStreaming(msg.convId); - this.clearProcessingState(msg.convId); - this.showErrorDialog( - error.name === 'TimeoutError' ? 'timeout' : 'server', - error.message - ); + this.setProcessingState(msg.convId, null); + this.showErrorDialog({ + type: + error.name === 'TimeoutError' ? ErrorDialogType.TIMEOUT : ErrorDialogType.SERVER, + message: error.message + }); } }, + msg.convId, abortController.signal ); } catch (error) { - if (!this.isAbortError(error)) console.error('Failed to continue message:', error); + if (!isAbortError(error)) console.error('Failed to continue message:', error); if (activeConv) this.setChatLoading(activeConv.id, false); } } @@ -1332,10 +1177,11 @@ class ChatStore { shouldBranch: boolean ): Promise { const activeConv = conversationsStore.activeConversation; - if (!activeConv || this.isLoading) return; + if (!activeConv || this.isChatLoadingInternal(activeConv.id)) return; - const result = this.getMessageByIdWithRole(messageId, 'assistant'); + const result = this.getMessageByIdWithRole(messageId, MessageRole.ASSISTANT); if (!result) return; + const { message: msg, index: idx } = result; try { @@ -1347,22 +1193,22 @@ class ChatStore { timestamp: Date.now(), role: msg.role, content: newContent, - thinking: msg.thinking || '', toolCalls: msg.toolCalls || '', children: [], model: msg.model }, msg.parent! ); + await conversationsStore.updateCurrentNode(newMessage.id); } else { await DatabaseService.updateMessage(msg.id, { content: newContent }); await conversationsStore.updateCurrentNode(msg.id); - conversationsStore.updateMessageAtIndex(idx, { - content: newContent - }); + conversationsStore.updateMessageAtIndex(idx, { content: newContent }); } + conversationsStore.updateConversationTimestamp(); + await conversationsStore.refreshActiveMessages(); } catch (error) { console.error('Failed to edit assistant message:', error); @@ -1377,22 +1223,17 @@ class ChatStore { const activeConv = conversationsStore.activeConversation; if (!activeConv) return; - const result = this.getMessageByIdWithRole(messageId, 'user'); + const result = this.getMessageByIdWithRole(messageId, MessageRole.USER); if (!result) return; - const { message: msg, index: idx } = result; + const { message: msg, index: idx } = result; try { - const updateData: Partial = { - content: newContent - }; + const updateData: Partial = { content: newContent }; - // Update extras if provided (including empty array to clear attachments) - // Deep clone to avoid Proxy objects from Svelte reactivity - if (newExtras !== undefined) { - updateData.extra = JSON.parse(JSON.stringify(newExtras)); - } + if (newExtras !== undefined) updateData.extra = JSON.parse(JSON.stringify(newExtras)); await DatabaseService.updateMessage(messageId, updateData); + conversationsStore.updateMessageAtIndex(idx, updateData); const allMessages = await conversationsStore.getConversationMessages(activeConv.id); @@ -1401,10 +1242,10 @@ class ChatStore { if (rootMessage && msg.parent === rootMessage.id && newContent.trim()) { await conversationsStore.updateConversationTitleWithConfirmation( activeConv.id, - newContent.trim(), - conversationsStore.titleUpdateConfirmationCallback + newContent.trim() ); } + conversationsStore.updateConversationTimestamp(); } catch (error) { console.error('Failed to edit user message:', error); @@ -1417,35 +1258,24 @@ class ChatStore { newExtras?: DatabaseMessageExtra[] ): Promise { const activeConv = conversationsStore.activeConversation; - if (!activeConv || this.isLoading) return; - - let result = this.getMessageByIdWithRole(messageId, 'user'); - - if (!result) { - result = this.getMessageByIdWithRole(messageId, 'system'); - } - + if (!activeConv || this.isChatLoadingInternal(activeConv.id)) return; + let result = this.getMessageByIdWithRole(messageId, MessageRole.USER); + if (!result) result = this.getMessageByIdWithRole(messageId, MessageRole.SYSTEM); if (!result) return; const { message: msg } = result; - try { const allMessages = await conversationsStore.getConversationMessages(activeConv.id); const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null); const isFirstUserMessage = - msg.role === 'user' && rootMessage && msg.parent === rootMessage.id; - + msg.role === MessageRole.USER && rootMessage && msg.parent === rootMessage.id; const parentId = msg.parent || rootMessage?.id; if (!parentId) return; - - // Use newExtras if provided, otherwise copy existing extras - // Deep clone to avoid Proxy objects from Svelte reactivity const extrasToUse = newExtras !== undefined ? JSON.parse(JSON.stringify(newExtras)) : msg.extra ? JSON.parse(JSON.stringify(msg.extra)) : undefined; - const newMessage = await DatabaseService.createMessageBranch( { convId: msg.convId, @@ -1453,7 +1283,6 @@ class ChatStore { timestamp: Date.now(), role: msg.role, content: newContent, - thinking: msg.thinking || '', toolCalls: msg.toolCalls || '', children: [], extra: extrasToUse, @@ -1463,86 +1292,23 @@ class ChatStore { ); await conversationsStore.updateCurrentNode(newMessage.id); conversationsStore.updateConversationTimestamp(); - - if (isFirstUserMessage && newContent.trim()) { + if (isFirstUserMessage && newContent.trim()) await conversationsStore.updateConversationTitleWithConfirmation( activeConv.id, - newContent.trim(), - conversationsStore.titleUpdateConfirmationCallback + newContent.trim() ); - } await conversationsStore.refreshActiveMessages(); - - if (msg.role === 'user') { - await this.generateResponseForMessage(newMessage.id); - } + if (msg.role === MessageRole.USER) await this.generateResponseForMessage(newMessage.id); } catch (error) { console.error('Failed to edit message with branching:', error); } } - async regenerateMessageWithBranching(messageId: string, modelOverride?: string): Promise { - const activeConv = conversationsStore.activeConversation; - if (!activeConv || this.isLoading) return; - try { - const idx = conversationsStore.findMessageIndex(messageId); - if (idx === -1) return; - const msg = conversationsStore.activeMessages[idx]; - if (msg.role !== 'assistant') return; - - const allMessages = await conversationsStore.getConversationMessages(activeConv.id); - const parentMessage = allMessages.find((m) => m.id === msg.parent); - if (!parentMessage) return; - - this.setChatLoading(activeConv.id, true); - this.clearChatStreaming(activeConv.id); - - const newAssistantMessage = await DatabaseService.createMessageBranch( - { - convId: activeConv.id, - type: 'text', - timestamp: Date.now(), - role: 'assistant', - content: '', - thinking: '', - toolCalls: '', - children: [], - model: null - }, - parentMessage.id - ); - await conversationsStore.updateCurrentNode(newAssistantMessage.id); - conversationsStore.updateConversationTimestamp(); - await conversationsStore.refreshActiveMessages(); - - const conversationPath = filterByLeafNodeId( - allMessages, - parentMessage.id, - false - ) as DatabaseMessage[]; - // Use modelOverride if provided, otherwise use the original message's model - // If neither is available, don't pass model (will use global selection) - const modelToUse = modelOverride || msg.model || undefined; - await this.streamChatCompletion( - conversationPath, - newAssistantMessage, - undefined, - undefined, - modelToUse - ); - } catch (error) { - if (!this.isAbortError(error)) - console.error('Failed to regenerate message with branching:', error); - this.setChatLoading(activeConv?.id || '', false); - } - } - private async generateResponseForMessage(userMessageId: string): Promise { const activeConv = conversationsStore.activeConversation; - if (!activeConv) return; - this.errorDialogState = null; + this.showErrorDialog(null); this.setChatLoading(activeConv.id, true); this.clearChatStreaming(activeConv.id); @@ -1556,18 +1322,19 @@ class ChatStore { const assistantMessage = await DatabaseService.createMessageBranch( { convId: activeConv.id, - type: 'text', + type: MessageType.TEXT, timestamp: Date.now(), - role: 'assistant', + role: MessageRole.ASSISTANT, content: '', - thinking: '', toolCalls: '', children: [], model: null }, userMessageId ); + conversationsStore.addMessageToActive(assistantMessage); + await this.streamChatCompletion(conversationPath, assistantMessage); } catch (error) { console.error('Failed to generate response:', error); @@ -1575,117 +1342,194 @@ class ChatStore { } } - getAddFilesHandler(): ((files: File[]) => void) | null { - return this.addFilesHandler; - } - - savePendingDraft(message: string, files: ChatUploadedFile[]): void { - this._pendingDraftMessage = message; - this._pendingDraftFiles = [...files]; - } + private getContextTotal(): number | null { + const activeConvId = this.activeConversationId; + const activeState = activeConvId ? this.getProcessingState(activeConvId) : null; - consumePendingDraft(): { message: string; files: ChatUploadedFile[] } | null { - if (!this._pendingDraftMessage && this._pendingDraftFiles.length === 0) { - return null; - } + if (activeState && typeof activeState.contextTotal === 'number' && activeState.contextTotal > 0) + return activeState.contextTotal; - const draft = { - message: this._pendingDraftMessage, - files: [...this._pendingDraftFiles] - }; + if (isRouterMode()) { + const modelContextSize = selectedModelContextSize(); - this._pendingDraftMessage = ''; - this._pendingDraftFiles = []; + if (typeof modelContextSize === 'number' && modelContextSize > 0) { + return modelContextSize; + } + } else { + const propsContextSize = contextSize(); - return draft; - } + if (typeof propsContextSize === 'number' && propsContextSize > 0) { + return propsContextSize; + } + } - hasPendingDraft(): boolean { - return Boolean(this._pendingDraftMessage) || this._pendingDraftFiles.length > 0; + return null; } - public getAllLoadingChats(): string[] { - return Array.from(this.chatLoadingStates.keys()); - } + updateProcessingStateFromTimings( + timingData: { + prompt_n: number; + prompt_ms?: number; + predicted_n: number; + predicted_per_second: number; + cache_n: number; + prompt_progress?: ChatMessagePromptProgress; + }, + conversationId?: string + ): void { + const processingState = this.parseTimingData(timingData); - public getAllStreamingChats(): string[] { - return Array.from(this.chatStreamingStates.keys()); - } + if (processingState === null) { + console.warn('Failed to parse timing data - skipping update'); + return; + } - public getChatStreamingPublic( - convId: string - ): { response: string; messageId: string } | undefined { - return this.getChatStreaming(convId); + const targetId = conversationId || this.activeConversationId; + if (targetId) { + this.setProcessingState(targetId, processingState); + } } - public isChatLoadingPublic(convId: string): boolean { - return this.isChatLoading(convId); + private parseTimingData(timingData: Record): ApiProcessingState | null { + const promptTokens = (timingData.prompt_n as number) || 0, + promptMs = (timingData.prompt_ms as number) || undefined, + predictedTokens = (timingData.predicted_n as number) || 0, + tokensPerSecond = (timingData.predicted_per_second as number) || 0, + cacheTokens = (timingData.cache_n as number) || 0; + const promptProgress = timingData.prompt_progress as + | { total: number; cache: number; processed: number; time_ms: number } + | undefined; + const contextTotal = this.getContextTotal(); + const currentConfig = config(); + const outputTokensMax = currentConfig.max_tokens || -1; + const contextUsed = promptTokens + cacheTokens + predictedTokens, + outputTokensUsed = predictedTokens; + const progressCache = promptProgress?.cache || 0, + progressActualDone = (promptProgress?.processed ?? 0) - progressCache, + progressActualTotal = (promptProgress?.total ?? 0) - progressCache; + const progressPercent = promptProgress + ? Math.round((progressActualDone / progressActualTotal) * 100) + : undefined; + return { + status: predictedTokens > 0 ? 'generating' : promptProgress ? 'preparing' : 'idle', + tokensDecoded: predictedTokens, + tokensRemaining: outputTokensMax - predictedTokens, + contextUsed, + contextTotal, + outputTokensUsed, + outputTokensMax, + hasNextToken: predictedTokens > 0, + tokensPerSecond, + temperature: currentConfig.temperature ?? 0.8, + topP: currentConfig.top_p ?? 0.95, + speculative: false, + progressPercent, + promptProgress, + promptTokens, + promptMs, + cacheTokens + }; } - isEditing(): boolean { - return this.isEditModeActive; + restoreProcessingStateFromMessages(messages: DatabaseMessage[], conversationId: string): void { + for (let i = messages.length - 1; i >= 0; i--) { + const message = messages[i]; + if (message.role === MessageRole.ASSISTANT && message.timings) { + const restoredState = this.parseTimingData({ + prompt_n: message.timings.prompt_n || 0, + prompt_ms: message.timings.prompt_ms, + predicted_n: message.timings.predicted_n || 0, + predicted_per_second: + message.timings.predicted_n && message.timings.predicted_ms + ? (message.timings.predicted_n / message.timings.predicted_ms) * 1000 + : 0, + cache_n: message.timings.cache_n || 0 + }); + if (restoredState) { + this.setProcessingState(conversationId, restoredState); + return; + } + } + } } - setEditModeActive(handler: (files: File[]) => void): void { - this.isEditModeActive = true; - this.addFilesHandler = handler; + getConversationModel(messages: DatabaseMessage[]): string | null { + for (let i = messages.length - 1; i >= 0; i--) { + const message = messages[i]; + if (message.role === MessageRole.ASSISTANT && message.model) return message.model; + } + return null; } - // ───────────────────────────────────────────────────────────────────────────── - // Utilities - // ───────────────────────────────────────────────────────────────────────────── - private getApiOptions(): Record { const currentConfig = config(); const hasValue = (value: unknown): boolean => value !== undefined && value !== null && value !== ''; - const apiOptions: Record = { stream: true, timings_per_token: true }; - // Model selection (required in ROUTER mode) if (isRouterMode()) { const modelName = selectedModelName(); if (modelName) apiOptions.model = modelName; } - // Config options needed by ChatService if (currentConfig.systemMessage) apiOptions.systemMessage = currentConfig.systemMessage; + if (currentConfig.disableReasoningParsing) apiOptions.disableReasoningParsing = true; if (hasValue(currentConfig.temperature)) apiOptions.temperature = Number(currentConfig.temperature); + if (hasValue(currentConfig.max_tokens)) apiOptions.max_tokens = Number(currentConfig.max_tokens); + if (hasValue(currentConfig.dynatemp_range)) apiOptions.dynatemp_range = Number(currentConfig.dynatemp_range); + if (hasValue(currentConfig.dynatemp_exponent)) apiOptions.dynatemp_exponent = Number(currentConfig.dynatemp_exponent); + if (hasValue(currentConfig.top_k)) apiOptions.top_k = Number(currentConfig.top_k); + if (hasValue(currentConfig.top_p)) apiOptions.top_p = Number(currentConfig.top_p); + if (hasValue(currentConfig.min_p)) apiOptions.min_p = Number(currentConfig.min_p); + if (hasValue(currentConfig.xtc_probability)) apiOptions.xtc_probability = Number(currentConfig.xtc_probability); + if (hasValue(currentConfig.xtc_threshold)) apiOptions.xtc_threshold = Number(currentConfig.xtc_threshold); + if (hasValue(currentConfig.typ_p)) apiOptions.typ_p = Number(currentConfig.typ_p); + if (hasValue(currentConfig.repeat_last_n)) apiOptions.repeat_last_n = Number(currentConfig.repeat_last_n); + if (hasValue(currentConfig.repeat_penalty)) apiOptions.repeat_penalty = Number(currentConfig.repeat_penalty); + if (hasValue(currentConfig.presence_penalty)) apiOptions.presence_penalty = Number(currentConfig.presence_penalty); + if (hasValue(currentConfig.frequency_penalty)) apiOptions.frequency_penalty = Number(currentConfig.frequency_penalty); + if (hasValue(currentConfig.dry_multiplier)) apiOptions.dry_multiplier = Number(currentConfig.dry_multiplier); + if (hasValue(currentConfig.dry_base)) apiOptions.dry_base = Number(currentConfig.dry_base); + if (hasValue(currentConfig.dry_allowed_length)) apiOptions.dry_allowed_length = Number(currentConfig.dry_allowed_length); + if (hasValue(currentConfig.dry_penalty_last_n)) apiOptions.dry_penalty_last_n = Number(currentConfig.dry_penalty_last_n); + if (currentConfig.samplers) apiOptions.samplers = currentConfig.samplers; + if (currentConfig.backend_sampling) apiOptions.backend_sampling = currentConfig.backend_sampling; + if (currentConfig.custom) apiOptions.custom = currentConfig.custom; return apiOptions; @@ -1695,7 +1539,6 @@ class ChatStore { export const chatStore = new ChatStore(); export const activeProcessingState = () => chatStore.activeProcessingState; -export const clearEditMode = () => chatStore.clearEditMode(); export const currentResponse = () => chatStore.currentResponse; export const errorDialog = () => chatStore.errorDialogState; export const getAddFilesHandler = () => chatStore.getAddFilesHandler(); @@ -1706,9 +1549,4 @@ export const isChatLoading = (convId: string) => chatStore.isChatLoadingPublic(c export const isChatStreaming = () => chatStore.isStreaming(); export const isEditing = () => chatStore.isEditing(); export const isLoading = () => chatStore.isLoading; -export const setEditModeActive = (handler: (files: File[]) => void) => - chatStore.setEditModeActive(handler); export const pendingEditMessageId = () => chatStore.pendingEditMessageId; -export const clearPendingEditMessageId = () => (chatStore.pendingEditMessageId = null); -export const removeSystemPromptPlaceholder = (messageId: string) => - chatStore.removeSystemPromptPlaceholder(messageId); diff --git a/tools/server/webui/src/lib/stores/conversations.svelte.ts b/tools/server/webui/src/lib/stores/conversations.svelte.ts index 1d1c6f16a1..ec1daa90d9 100644 --- a/tools/server/webui/src/lib/stores/conversations.svelte.ts +++ b/tools/server/webui/src/lib/stores/conversations.svelte.ts @@ -1,54 +1,40 @@ -import { browser } from '$app/environment'; -import { goto } from '$app/navigation'; -import { toast } from 'svelte-sonner'; -import { DatabaseService } from '$lib/services/database.service'; -import { config } from '$lib/stores/settings.svelte'; -import { filterByLeafNodeId, findLeafNode } from '$lib/utils'; -import { AttachmentType } from '$lib/enums'; - /** - * conversationsStore - Persistent conversation data and lifecycle management - * - * **Terminology - Chat vs Conversation:** - * - **Chat**: The active interaction space with the Chat Completions API. Represents the - * real-time streaming session, loading states, and UI visualization of AI communication. - * Managed by chatStore, a "chat" is ephemeral and exists during active AI interactions. - * - **Conversation**: The persistent database entity storing all messages and metadata. - * A "conversation" survives across sessions, page reloads, and browser restarts. - * It contains the complete message history, branching structure, and conversation metadata. + * conversationsStore - Reactive State Store for Conversations * - * This store manages all conversation-level data and operations including creation, loading, - * deletion, and navigation. It maintains the list of conversations and the currently active - * conversation with its message history, providing reactive state for UI components. + * Manages conversation lifecycle, persistence, navigation, and MCP server overrides. * * **Architecture & Relationships:** - * - **conversationsStore** (this class): Persistent conversation data management - * - Manages conversation list and active conversation state - * - Handles conversation CRUD operations via DatabaseService - * - Maintains active message array for current conversation - * - Coordinates branching navigation (currNode tracking) + * - **DatabaseService**: Stateless IndexedDB layer + * - **conversationsStore** (this): Reactive state + business logic + * - **chatStore**: Chat-specific state (streaming, loading) * - * - **chatStore**: Uses conversation data as context for active AI streaming - * - **DatabaseService**: Low-level IndexedDB storage for conversations and messages + * **Key Responsibilities:** + * - Conversation CRUD (create, load, delete) + * - Message management and tree navigation + * - MCP server per-chat overrides + * - Import/Export functionality + * - Title management with confirmation * - * **Key Features:** - * - **Conversation Lifecycle**: Create, load, update, delete conversations - * - **Message Management**: Active message array with branching support - * - **Import/Export**: JSON-based conversation backup and restore - * - **Branch Navigation**: Navigate between message tree branches - * - **Title Management**: Auto-update titles with confirmation dialogs - * - **Reactive State**: Svelte 5 runes for automatic UI updates - * - * **State Properties:** - * - `conversations`: All conversations sorted by last modified - * - `activeConversation`: Currently viewed conversation - * - `activeMessages`: Messages in current conversation path - * - `isInitialized`: Store initialization status + * @see DatabaseService in services/database.ts for IndexedDB operations */ + +import { goto } from '$app/navigation'; +import { browser } from '$app/environment'; +import { toast } from 'svelte-sonner'; +import { DatabaseService } from '$lib/services/database.service'; +import { config } from '$lib/stores/settings.svelte'; +import { filterByLeafNodeId, findLeafNode } from '$lib/utils'; +import type { McpServerOverride } from '$lib/types/database'; +import { MessageRole } from '$lib/enums'; + class ConversationsStore { - // ───────────────────────────────────────────────────────────────────────────── - // State - // ───────────────────────────────────────────────────────────────────────────── + /** + * + * + * State + * + * + */ /** List of all conversations */ conversations = $state([]); @@ -62,105 +48,134 @@ class ConversationsStore { /** Whether the store has been initialized */ isInitialized = $state(false); + /** Pending MCP server overrides for new conversations (before first message) */ + pendingMcpServerOverrides = $state([]); + /** Callback for title update confirmation dialog */ titleUpdateConfirmationCallback?: (currentTitle: string, newTitle: string) => Promise; - // ───────────────────────────────────────────────────────────────────────────── - // Modalities - // ───────────────────────────────────────────────────────────────────────────── + /** + * Callback for updating message content in chatStore. + * Registered by chatStore to enable cross-store updates without circular dependency. + */ + private messageUpdateCallback: + | ((messageId: string, updates: Partial) => void) + | null = null; /** - * Modalities used in the active conversation. - * Computed from attachments in activeMessages. - * Used to filter available models - models must support all used modalities. + * + * + * Lifecycle + * + * */ - usedModalities: ModelModalities = $derived.by(() => { - return this.calculateModalitiesFromMessages(this.activeMessages); - }); /** - * Calculate modalities from a list of messages. - * Helper method used by both usedModalities and getModalitiesUpToMessage. + * Initialize the store by loading conversations from database. + * Must be called once after app startup. */ - private calculateModalitiesFromMessages(messages: DatabaseMessage[]): ModelModalities { - const modalities: ModelModalities = { vision: false, audio: false }; + async init(): Promise { + if (!browser) return; + if (this.isInitialized) return; - for (const message of messages) { - if (!message.extra) continue; + try { + await this.loadConversations(); + this.isInitialized = true; + } catch (error) { + console.error('Failed to initialize conversations:', error); + } + } - for (const extra of message.extra) { - if (extra.type === AttachmentType.IMAGE) { - modalities.vision = true; - } + /** + * Alias for init() for backward compatibility. + */ + async initialize(): Promise { + return this.init(); + } - // PDF only requires vision if processed as images - if (extra.type === AttachmentType.PDF) { - const pdfExtra = extra as DatabaseMessageExtraPdfFile; + /** + * Register a callback for message updates from other stores. + * Called by chatStore during initialization. + */ + registerMessageUpdateCallback( + callback: (messageId: string, updates: Partial) => void + ): void { + this.messageUpdateCallback = callback; + } - if (pdfExtra.processedAsImages) { - modalities.vision = true; - } - } + /** + * + * + * Message Array Operations + * + * + */ - if (extra.type === AttachmentType.AUDIO) { - modalities.audio = true; - } - } + /** + * Adds a message to the active messages array + */ + addMessageToActive(message: DatabaseMessage): void { + this.activeMessages.push(message); + } - if (modalities.vision && modalities.audio) break; + /** + * Updates a message at a specific index in active messages + */ + updateMessageAtIndex(index: number, updates: Partial): void { + if (index !== -1 && this.activeMessages[index]) { + this.activeMessages[index] = { ...this.activeMessages[index], ...updates }; } - - return modalities; } /** - * Get modalities used in messages BEFORE the specified message. - * Used for regeneration - only consider context that was available when generating this message. + * Finds the index of a message in active messages */ - getModalitiesUpToMessage(messageId: string): ModelModalities { - const messageIndex = this.activeMessages.findIndex((m) => m.id === messageId); - - if (messageIndex === -1) { - return this.usedModalities; - } + findMessageIndex(messageId: string): number { + return this.activeMessages.findIndex((m) => m.id === messageId); + } - const messagesBefore = this.activeMessages.slice(0, messageIndex); - return this.calculateModalitiesFromMessages(messagesBefore); + /** + * Removes messages from active messages starting at an index + */ + sliceActiveMessages(startIndex: number): void { + this.activeMessages = this.activeMessages.slice(0, startIndex); } - constructor() { - if (browser) { - this.initialize(); + /** + * Removes a message from active messages by index + */ + removeMessageAtIndex(index: number): DatabaseMessage | undefined { + if (index !== -1) { + return this.activeMessages.splice(index, 1)[0]; } + return undefined; } - // ───────────────────────────────────────────────────────────────────────────── - // Lifecycle - // ───────────────────────────────────────────────────────────────────────────── - /** - * Initializes the conversations store by loading conversations from the database + * Sets the callback function for title update confirmations */ - async initialize(): Promise { - try { - await this.loadConversations(); - this.isInitialized = true; - } catch (error) { - console.error('Failed to initialize conversations store:', error); - } + setTitleUpdateConfirmationCallback( + callback: (currentTitle: string, newTitle: string) => Promise + ): void { + this.titleUpdateConfirmationCallback = callback; } + /** + * + * + * Conversation CRUD + * + * + */ + /** * Loads all conversations from the database */ async loadConversations(): Promise { - this.conversations = await DatabaseService.getAllConversations(); + const conversations = await DatabaseService.getAllConversations(); + this.conversations = conversations; } - // ───────────────────────────────────────────────────────────────────────────── - // Conversation CRUD - // ───────────────────────────────────────────────────────────────────────────── - /** * Creates a new conversation and navigates to it * @param name - Optional name for the conversation @@ -170,7 +185,20 @@ class ConversationsStore { const conversationName = name || `Chat ${new Date().toLocaleString()}`; const conversation = await DatabaseService.createConversation(conversationName); - this.conversations.unshift(conversation); + if (this.pendingMcpServerOverrides.length > 0) { + // Deep clone to plain objects (Svelte 5 $state uses Proxies which can't be cloned to IndexedDB) + const plainOverrides = this.pendingMcpServerOverrides.map((o) => ({ + serverId: o.serverId, + enabled: o.enabled + })); + conversation.mcpServerOverrides = plainOverrides; + await DatabaseService.updateConversation(conversation.id, { + mcpServerOverrides: plainOverrides + }); + this.pendingMcpServerOverrides = []; + } + + this.conversations = [conversation, ...this.conversations]; this.activeConversation = conversation; this.activeMessages = []; @@ -192,17 +220,20 @@ class ConversationsStore { return false; } + this.pendingMcpServerOverrides = []; this.activeConversation = conversation; if (conversation.currNode) { const allMessages = await DatabaseService.getConversationMessages(convId); - this.activeMessages = filterByLeafNodeId( + const filteredMessages = filterByLeafNodeId( allMessages, conversation.currNode, false ) as DatabaseMessage[]; + this.activeMessages = filteredMessages; } else { - this.activeMessages = await DatabaseService.getConversationMessages(convId); + const messages = await DatabaseService.getConversationMessages(convId); + this.activeMessages = messages; } return true; @@ -213,21 +244,65 @@ class ConversationsStore { } /** - * Clears the active conversation and messages - * Used when navigating away from chat or starting fresh + * Clears the active conversation and messages. */ clearActiveConversation(): void { this.activeConversation = null; this.activeMessages = []; - // Active processing conversation is now managed by chatStore } - // ───────────────────────────────────────────────────────────────────────────── - // Message Management - // ───────────────────────────────────────────────────────────────────────────── + /** + * Deletes a conversation and all its messages + * @param convId - The conversation ID to delete + */ + async deleteConversation(convId: string): Promise { + try { + await DatabaseService.deleteConversation(convId); + + this.conversations = this.conversations.filter((c) => c.id !== convId); + + if (this.activeConversation?.id === convId) { + this.clearActiveConversation(); + await goto(`?new_chat=true#/`); + } + } catch (error) { + console.error('Failed to delete conversation:', error); + } + } + + /** + * Deletes all conversations and their messages + */ + async deleteAll(): Promise { + try { + const allConversations = await DatabaseService.getAllConversations(); + + for (const conv of allConversations) { + await DatabaseService.deleteConversation(conv.id); + } + + this.clearActiveConversation(); + this.conversations = []; + + toast.success('All conversations deleted'); + + await goto(`?new_chat=true#/`); + } catch (error) { + console.error('Failed to delete all conversations:', error); + toast.error('Failed to delete conversations'); + } + } + + /** + * + * + * Message Management + * + * + */ /** - * Refreshes active messages based on currNode after branch navigation + * Refreshes active messages based on currNode after branch navigation. */ async refreshActiveMessages(): Promise { if (!this.activeConversation) return; @@ -241,18 +316,32 @@ class ConversationsStore { const leafNodeId = this.activeConversation.currNode || - allMessages.reduce((latest: DatabaseMessage, msg: DatabaseMessage) => - msg.timestamp > latest.timestamp ? msg : latest - ).id; + allMessages.reduce((latest, msg) => (msg.timestamp > latest.timestamp ? msg : latest)).id; const currentPath = filterByLeafNodeId(allMessages, leafNodeId, false) as DatabaseMessage[]; - this.activeMessages.length = 0; - this.activeMessages.push(...currentPath); + this.activeMessages = currentPath; } /** - * Updates the name of a conversation + * Gets all messages for a specific conversation + * @param convId - The conversation ID + * @returns Array of messages + */ + async getConversationMessages(convId: string): Promise { + return await DatabaseService.getConversationMessages(convId); + } + + /** + * + * + * Title Management + * + * + */ + + /** + * Updates the name of a conversation. * @param convId - The conversation ID to update * @param name - The new name for the conversation */ @@ -264,10 +353,11 @@ class ConversationsStore { if (convIndex !== -1) { this.conversations[convIndex].name = name; + this.conversations = [...this.conversations]; } if (this.activeConversation?.id === convId) { - this.activeConversation.name = name; + this.activeConversation = { ...this.activeConversation, name }; } } catch (error) { console.error('Failed to update conversation name:', error); @@ -278,22 +368,23 @@ class ConversationsStore { * Updates conversation title with optional confirmation dialog based on settings * @param convId - The conversation ID to update * @param newTitle - The new title content - * @param onConfirmationNeeded - Callback when user confirmation is needed * @returns True if title was updated, false if cancelled */ async updateConversationTitleWithConfirmation( convId: string, - newTitle: string, - onConfirmationNeeded?: (currentTitle: string, newTitle: string) => Promise + newTitle: string ): Promise { try { const currentConfig = config(); - if (currentConfig.askForTitleConfirmation && onConfirmationNeeded) { + if (currentConfig.askForTitleConfirmation && this.titleUpdateConfirmationCallback) { const conversation = await DatabaseService.getConversation(convId); if (!conversation) return false; - const shouldUpdate = await onConfirmationNeeded(conversation.name, newTitle); + const shouldUpdate = await this.titleUpdateConfirmationCallback( + conversation.name, + newTitle + ); if (!shouldUpdate) return false; } @@ -305,21 +396,6 @@ class ConversationsStore { } } - // ───────────────────────────────────────────────────────────────────────────── - // Navigation - // ───────────────────────────────────────────────────────────────────────────── - - /** - * Updates the current node of the active conversation - * @param nodeId - The new current node ID - */ - async updateCurrentNode(nodeId: string): Promise { - if (!this.activeConversation) return; - - await DatabaseService.updateCurrentNode(this.activeConversation.id, nodeId); - this.activeConversation.currNode = nodeId; - } - /** * Updates conversation lastModified timestamp and moves it to top of list */ @@ -331,35 +407,51 @@ class ConversationsStore { if (chatIndex !== -1) { this.conversations[chatIndex].lastModified = Date.now(); const updatedConv = this.conversations.splice(chatIndex, 1)[0]; - this.conversations.unshift(updatedConv); + this.conversations = [updatedConv, ...this.conversations]; } } /** - * Navigates to a specific sibling branch by updating currNode and refreshing messages + * Updates the current node of the active conversation + * @param nodeId - The new current node ID + */ + async updateCurrentNode(nodeId: string): Promise { + if (!this.activeConversation) return; + + await DatabaseService.updateCurrentNode(this.activeConversation.id, nodeId); + this.activeConversation = { ...this.activeConversation, currNode: nodeId }; + } + + /** + * + * + * Branch Navigation + * + * + */ + + /** + * Navigates to a specific sibling branch by updating currNode and refreshing messages. * @param siblingId - The sibling message ID to navigate to */ async navigateToSibling(siblingId: string): Promise { if (!this.activeConversation) return; const allMessages = await DatabaseService.getConversationMessages(this.activeConversation.id); - const rootMessage = allMessages.find( - (m: DatabaseMessage) => m.type === 'root' && m.parent === null - ); + const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null); const currentFirstUserMessage = this.activeMessages.find( - (m: DatabaseMessage) => m.role === 'user' && m.parent === rootMessage?.id + (m) => m.role === MessageRole.USER && m.parent === rootMessage?.id ); const currentLeafNodeId = findLeafNode(allMessages, siblingId); await DatabaseService.updateCurrentNode(this.activeConversation.id, currentLeafNodeId); - this.activeConversation.currNode = currentLeafNodeId; + this.activeConversation = { ...this.activeConversation, currNode: currentLeafNodeId }; await this.refreshActiveMessages(); - // Only show title dialog if we're navigating between different first user message siblings if (rootMessage && this.activeMessages.length > 0) { const newFirstUserMessage = this.activeMessages.find( - (m: DatabaseMessage) => m.role === 'user' && m.parent === rootMessage.id + (m) => m.role === MessageRole.USER && m.parent === rootMessage.id ); if ( @@ -371,61 +463,164 @@ class ConversationsStore { ) { await this.updateConversationTitleWithConfirmation( this.activeConversation.id, - newFirstUserMessage.content.trim(), - this.titleUpdateConfirmationCallback + newFirstUserMessage.content.trim() ); } } } /** - * Deletes a conversation and all its messages - * @param convId - The conversation ID to delete + * + * + * MCP Server Overrides + * + * */ - async deleteConversation(convId: string): Promise { - try { - await DatabaseService.deleteConversation(convId); - this.conversations = this.conversations.filter((c) => c.id !== convId); + /** + * Gets MCP server override for a specific server in the active conversation. + * Falls back to pending overrides if no active conversation exists. + * @param serverId - The server ID to check + * @returns The override if set, undefined if using global setting + */ + getMcpServerOverride(serverId: string): McpServerOverride | undefined { + if (this.activeConversation) { + return this.activeConversation.mcpServerOverrides?.find( + (o: McpServerOverride) => o.serverId === serverId + ); + } + return this.pendingMcpServerOverrides.find((o) => o.serverId === serverId); + } - if (this.activeConversation?.id === convId) { - this.clearActiveConversation(); - await goto(`?new_chat=true#/`); - } - } catch (error) { - console.error('Failed to delete conversation:', error); + /** + * Get all MCP server overrides for the current conversation. + * Returns pending overrides if no active conversation. + */ + getAllMcpServerOverrides(): McpServerOverride[] { + if (this.activeConversation?.mcpServerOverrides) { + return this.activeConversation.mcpServerOverrides; } + return this.pendingMcpServerOverrides; } /** - * Deletes all conversations and their messages + * Checks if an MCP server is enabled for the active conversation. + * @param serverId - The server ID to check + * @returns True if server is enabled for this conversation */ - async deleteAll(): Promise { - try { - const allConversations = await DatabaseService.getAllConversations(); + isMcpServerEnabledForChat(serverId: string): boolean { + const override = this.getMcpServerOverride(serverId); + return override?.enabled ?? false; + } - for (const conv of allConversations) { - await DatabaseService.deleteConversation(conv.id); + /** + * Sets or removes MCP server override for the active conversation. + * If no conversation exists, stores as pending override. + * @param serverId - The server ID to override + * @param enabled - The enabled state, or undefined to remove override + */ + async setMcpServerOverride(serverId: string, enabled: boolean | undefined): Promise { + if (!this.activeConversation) { + this.setPendingMcpServerOverride(serverId, enabled); + return; + } + + // Clone to plain objects to avoid Proxy serialization issues with IndexedDB + const currentOverrides = (this.activeConversation.mcpServerOverrides || []).map( + (o: McpServerOverride) => ({ + serverId: o.serverId, + enabled: o.enabled + }) + ); + let newOverrides: McpServerOverride[]; + + if (enabled === undefined) { + newOverrides = currentOverrides.filter((o: McpServerOverride) => o.serverId !== serverId); + } else { + const existingIndex = currentOverrides.findIndex( + (o: McpServerOverride) => o.serverId === serverId + ); + if (existingIndex >= 0) { + newOverrides = [...currentOverrides]; + newOverrides[existingIndex] = { serverId, enabled }; + } else { + newOverrides = [...currentOverrides, { serverId, enabled }]; } + } - this.clearActiveConversation(); - this.conversations = []; + await DatabaseService.updateConversation(this.activeConversation.id, { + mcpServerOverrides: newOverrides.length > 0 ? newOverrides : undefined + }); - toast.success('All conversations deleted'); + this.activeConversation = { + ...this.activeConversation, + mcpServerOverrides: newOverrides.length > 0 ? newOverrides : undefined + }; - await goto(`?new_chat=true#/`); - } catch (error) { - console.error('Failed to delete all conversations:', error); - toast.error('Failed to delete conversations'); + const convIndex = this.conversations.findIndex((c) => c.id === this.activeConversation!.id); + if (convIndex !== -1) { + this.conversations[convIndex].mcpServerOverrides = + newOverrides.length > 0 ? newOverrides : undefined; + this.conversations = [...this.conversations]; + } + } + + /** + * Sets or removes a pending MCP server override (for new conversations). + */ + private setPendingMcpServerOverride(serverId: string, enabled: boolean | undefined): void { + if (enabled === undefined) { + this.pendingMcpServerOverrides = this.pendingMcpServerOverrides.filter( + (o) => o.serverId !== serverId + ); + } else { + const existingIndex = this.pendingMcpServerOverrides.findIndex( + (o) => o.serverId === serverId + ); + if (existingIndex >= 0) { + const newOverrides = [...this.pendingMcpServerOverrides]; + newOverrides[existingIndex] = { serverId, enabled }; + this.pendingMcpServerOverrides = newOverrides; + } else { + this.pendingMcpServerOverrides = [...this.pendingMcpServerOverrides, { serverId, enabled }]; + } } } - // ───────────────────────────────────────────────────────────────────────────── - // Import/Export - // ───────────────────────────────────────────────────────────────────────────── + /** + * Toggles MCP server enabled state for the active conversation. + * @param serverId - The server ID to toggle + */ + async toggleMcpServerForChat(serverId: string): Promise { + const currentEnabled = this.isMcpServerEnabledForChat(serverId); + await this.setMcpServerOverride(serverId, !currentEnabled); + } + + /** + * Removes MCP server override for the active conversation. + * @param serverId - The server ID to remove override for + */ + async removeMcpServerOverride(serverId: string): Promise { + await this.setMcpServerOverride(serverId, undefined); + } /** - * Downloads a conversation as JSON file + * Clears all pending MCP server overrides. + */ + clearPendingMcpServerOverrides(): void { + this.pendingMcpServerOverrides = []; + } + + /** + * + * + * Import & Export + * + * + */ + + /** + * Downloads a conversation as JSON file. * @param convId - The conversation ID to download */ async downloadConversation(convId: string): Promise { @@ -456,7 +651,7 @@ class ConversationsStore { } const allData = await Promise.all( - allConversations.map(async (conv: DatabaseConversation) => { + allConversations.map(async (conv) => { const messages = await DatabaseService.getConversationMessages(conv.id); return { conv, messages }; }) @@ -536,15 +731,6 @@ class ConversationsStore { }); } - /** - * Gets all messages for a specific conversation - * @param convId - The conversation ID - * @returns Array of messages - */ - async getConversationMessages(convId: string): Promise { - return await DatabaseService.getConversationMessages(convId); - } - /** * Imports conversations from provided data (without file picker) * @param data - Array of conversation data with messages @@ -558,61 +744,8 @@ class ConversationsStore { return result; } - /** - * Adds a message to the active messages array - * Used by chatStore when creating new messages - * @param message - The message to add - */ - addMessageToActive(message: DatabaseMessage): void { - this.activeMessages.push(message); - } - - /** - * Updates a message at a specific index in active messages - * Creates a new object to trigger Svelte 5 reactivity - * @param index - The index of the message to update - * @param updates - Partial message data to update - */ - updateMessageAtIndex(index: number, updates: Partial): void { - if (index !== -1 && this.activeMessages[index]) { - // Create new object to trigger Svelte 5 reactivity - this.activeMessages[index] = { ...this.activeMessages[index], ...updates }; - } - } - - /** - * Finds the index of a message in active messages - * @param messageId - The message ID to find - * @returns The index of the message, or -1 if not found - */ - findMessageIndex(messageId: string): number { - return this.activeMessages.findIndex((m) => m.id === messageId); - } - - /** - * Removes messages from active messages starting at an index - * @param startIndex - The index to start removing from - */ - sliceActiveMessages(startIndex: number): void { - this.activeMessages = this.activeMessages.slice(0, startIndex); - } - - /** - * Removes a message from active messages by index - * @param index - The index to remove - * @returns The removed message or undefined - */ - removeMessageAtIndex(index: number): DatabaseMessage | undefined { - if (index !== -1) { - return this.activeMessages.splice(index, 1)[0]; - } - return undefined; - } - /** * Triggers file download in browser - * @param data - The data to download - * @param filename - Optional filename for the download */ private triggerDownload(data: ExportedConversations, filename?: string): void { const conversation = @@ -641,26 +774,16 @@ class ConversationsStore { document.body.removeChild(a); URL.revokeObjectURL(url); } - - // ───────────────────────────────────────────────────────────────────────────── - // Utilities - // ───────────────────────────────────────────────────────────────────────────── - - /** - * Sets the callback function for title update confirmations - * @param callback - Function to call when confirmation is needed - */ - setTitleUpdateConfirmationCallback( - callback: (currentTitle: string, newTitle: string) => Promise - ): void { - this.titleUpdateConfirmationCallback = callback; - } } export const conversationsStore = new ConversationsStore(); +// Auto-initialize in browser +if (browser) { + conversationsStore.init(); +} + export const conversations = () => conversationsStore.conversations; export const activeConversation = () => conversationsStore.activeConversation; export const activeMessages = () => conversationsStore.activeMessages; export const isConversationsInitialized = () => conversationsStore.isInitialized; -export const usedModalities = () => conversationsStore.usedModalities; diff --git a/tools/server/webui/src/lib/types/chat.d.ts b/tools/server/webui/src/lib/types/chat.d.ts index 8d4661960a..86e98c8b6b 100644 --- a/tools/server/webui/src/lib/types/chat.d.ts +++ b/tools/server/webui/src/lib/types/chat.d.ts @@ -1,8 +1,5 @@ import type { ErrorDialogType } from '$lib/enums'; -import type { DatabaseMessage, DatabaseMessageExtra } from './database'; - -export type ChatMessageType = 'root' | 'text' | 'think' | 'system'; -export type ChatRole = 'user' | 'assistant' | 'system'; +import type { DatabaseMessageExtra } from './database'; export interface ChatUploadedFile { id: string; @@ -12,6 +9,11 @@ export interface ChatUploadedFile { file: File; preview?: string; textContent?: string; + mcpPrompt?: { + serverName: string; + promptName: string; + arguments?: Record; + }; isLoading?: boolean; loadError?: string; } @@ -22,6 +24,8 @@ export interface ChatAttachmentDisplayItem { size?: number; preview?: string; isImage: boolean; + isMcpPrompt?: boolean; + isMcpResource?: boolean; isLoading?: boolean; loadError?: string; uploadedFile?: ChatUploadedFile; @@ -59,8 +63,44 @@ export interface ChatMessageTimings { predicted_n?: number; prompt_ms?: number; prompt_n?: number; + agentic?: ChatMessageAgenticTimings; +} + +export interface ChatMessageAgenticTimings { + turns: number; + toolCallsCount: number; + toolsMs: number; + toolCalls?: ChatMessageToolCallTiming[]; + perTurn?: ChatMessageAgenticTurnStats[]; + llm: { + predicted_n: number; + predicted_ms: number; + prompt_n: number; + prompt_ms: number; + }; +} + +export interface ChatMessageAgenticTurnStats { + turn: number; + llm: { + predicted_n: number; + predicted_ms: number; + prompt_n: number; + prompt_ms: number; + }; + toolCalls: ChatMessageToolCallTiming[]; + toolsMs: number; +} + +export interface ChatMessageToolCallTiming { + name: string; + duration_ms: number; + success: boolean; } +/** + * Callbacks for streaming chat responses + */ export interface ChatStreamCallbacks { onChunk?: (chunk: string) => void; onReasoningChunk?: (chunk: string) => void; @@ -77,12 +117,18 @@ export interface ChatStreamCallbacks { onError?: (error: Error) => void; } +/** + * Error dialog state for displaying server/timeout errors + */ export interface ErrorDialogState { type: ErrorDialogType; message: string; contextInfo?: { n_prompt_tokens: number; n_ctx: number }; } +/** + * Live processing stats during prompt evaluation + */ export interface LiveProcessingStats { tokensProcessed: number; totalTokens: number; @@ -91,17 +137,26 @@ export interface LiveProcessingStats { etaSecs?: number; } +/** + * Live generation stats during token generation + */ export interface LiveGenerationStats { tokensGenerated: number; timeMs: number; tokensPerSecond: number; } +/** + * Options for getting attachment display items + */ export interface AttachmentDisplayItemsOptions { uploadedFiles?: ChatUploadedFile[]; attachments?: DatabaseMessageExtra[]; } +/** + * Result of file processing operation + */ export interface FileProcessingResult { extras: DatabaseMessageExtra[]; emptyFiles: string[]; diff --git a/tools/server/webui/src/lib/types/common.d.ts b/tools/server/webui/src/lib/types/common.d.ts index a4ae12fb86..a9bd34722e 100644 --- a/tools/server/webui/src/lib/types/common.d.ts +++ b/tools/server/webui/src/lib/types/common.d.ts @@ -1,7 +1,12 @@ import type { AttachmentType } from '$lib/enums'; +/** + * Common utility types used across the application + */ + /** * Represents a key-value pair. + * Used for headers, environment variables, query parameters, etc. */ export interface KeyValuePair { key: string; @@ -9,16 +14,19 @@ export interface KeyValuePair { } /** - * Binary detection configuration options. + * Binary detection configuration options */ export interface BinaryDetectionOptions { + /** Number of characters to check from the beginning of the file */ prefixLength: number; + /** Maximum ratio of suspicious characters allowed (0.0 to 1.0) */ suspiciousCharThresholdRatio: number; + /** Maximum absolute number of null bytes allowed */ maxAbsoluteNullBytes: number; } /** - * Format for text attachments when copied to clipboard. + * Format for text attachments when copied to clipboard */ export interface ClipboardTextAttachment { type: typeof AttachmentType.TEXT; @@ -27,9 +35,29 @@ export interface ClipboardTextAttachment { } /** - * Parsed result from clipboard content. + * Format for MCP prompt attachments when copied to clipboard + */ +export interface ClipboardMcpPromptAttachment { + type: typeof AttachmentType.MCP_PROMPT; + name: string; + serverName: string; + promptName: string; + content: string; + arguments?: Record; +} + +/** + * Union type for all clipboard attachment types + */ +export type ClipboardAttachment = ClipboardTextAttachment | ClipboardMcpPromptAttachment; + +/** + * Parsed result from clipboard content */ export interface ParsedClipboardContent { message: string; textAttachments: ClipboardTextAttachment[]; + mcpPromptAttachments: ClipboardMcpPromptAttachment[]; } + +export type MimeTypeUnion = MimeTypeAudio | MimeTypeImage | MimeTypeApplication | MimeTypeText; diff --git a/tools/server/webui/src/lib/types/database.d.ts b/tools/server/webui/src/lib/types/database.d.ts index 1a336e059c..50f51ecf5d 100644 --- a/tools/server/webui/src/lib/types/database.d.ts +++ b/tools/server/webui/src/lib/types/database.d.ts @@ -1,11 +1,17 @@ import type { ChatMessageTimings, ChatRole, ChatMessageType } from '$lib/types/chat'; import { AttachmentType } from '$lib/enums'; +export interface McpServerOverride { + serverId: string; + enabled: boolean; +} + export interface DatabaseConversation { currNode: string | null; id: string; lastModified: number; name: string; + mcpServerOverrides?: McpServerOverride[]; } export interface DatabaseMessageExtraAudioFile { @@ -35,9 +41,9 @@ export interface DatabaseMessageExtraPdfFile { type: AttachmentType.PDF; base64Data: string; name: string; - content: string; // Text content extracted from PDF - images?: string[]; // Optional: PDF pages as base64 images - processedAsImages: boolean; // Whether PDF was processed as images + content: string; + images?: string[]; + processedAsImages: boolean; } export interface DatabaseMessageExtraTextFile { @@ -46,11 +52,31 @@ export interface DatabaseMessageExtraTextFile { content: string; } +export interface DatabaseMessageExtraMcpPrompt { + type: AttachmentType.MCP_PROMPT; + name: string; + serverName: string; + promptName: string; + content: string; + arguments?: Record; +} + +export interface DatabaseMessageExtraMcpResource { + type: AttachmentType.MCP_RESOURCE; + name: string; + uri: string; + serverName: string; + content: string; + mimeType?: string; +} + export type DatabaseMessageExtra = | DatabaseMessageExtraImageFile | DatabaseMessageExtraTextFile | DatabaseMessageExtraAudioFile | DatabaseMessageExtraPdfFile + | DatabaseMessageExtraMcpPrompt + | DatabaseMessageExtraMcpResource | DatabaseMessageExtraLegacyContext; export interface DatabaseMessage { @@ -60,26 +86,24 @@ export interface DatabaseMessage { timestamp: number; role: ChatRole; content: string; - parent: string; - thinking: string; + parent: string | null; + /** + * @deprecated - left for backward compatibility + */ + thinking?: string; + /** Serialized JSON array of tool calls made by assistant messages */ toolCalls?: string; + /** Tool call ID for tool result messages (role: 'tool') */ + toolCallId?: string; children: string[]; extra?: DatabaseMessageExtra[]; timings?: ChatMessageTimings; model?: string; } -/** - * Represents a single conversation with its associated messages, - * typically used for import/export operations. - */ export type ExportedConversation = { conv: DatabaseConversation; messages: DatabaseMessage[]; }; -/** - * Type representing one or more exported conversations. - * Can be a single conversation object or an array of them. - */ export type ExportedConversations = ExportedConversation | ExportedConversation[]; diff --git a/tools/server/webui/src/lib/types/index.ts b/tools/server/webui/src/lib/types/index.ts index 7b1bba717d..93a39f03da 100644 --- a/tools/server/webui/src/lib/types/index.ts +++ b/tools/server/webui/src/lib/types/index.ts @@ -34,28 +34,32 @@ export type { // Chat types export type { - ChatMessageType, - ChatRole, ChatUploadedFile, ChatAttachmentDisplayItem, ChatAttachmentPreviewItem, ChatMessageSiblingInfo, ChatMessagePromptProgress, ChatMessageTimings, + ChatMessageAgenticTimings, + ChatMessageAgenticTurnStats, + ChatMessageToolCallTiming, ChatStreamCallbacks, ErrorDialogState, LiveProcessingStats, LiveGenerationStats, AttachmentDisplayItemsOptions, FileProcessingResult -} from './chat'; +} from './chat.d'; // Database types export type { + McpServerOverride, DatabaseConversation, DatabaseMessageExtraAudioFile, DatabaseMessageExtraImageFile, DatabaseMessageExtraLegacyContext, + DatabaseMessageExtraMcpPrompt, + DatabaseMessageExtraMcpResource, DatabaseMessageExtraPdfFile, DatabaseMessageExtraTextFile, DatabaseMessageExtra, @@ -84,5 +88,66 @@ export type { KeyValuePair, BinaryDetectionOptions, ClipboardTextAttachment, + ClipboardMcpPromptAttachment, + ClipboardAttachment, ParsedClipboardContent } from './common'; + +// MCP types +export type { + ClientCapabilities, + ServerCapabilities, + Implementation, + MCPConnectionLog, + MCPServerInfo, + MCPCapabilitiesInfo, + MCPToolInfo, + MCPPromptInfo, + MCPConnectionDetails, + MCPPhaseCallback, + MCPConnection, + HealthCheckState, + HealthCheckParams, + MCPServerConfig, + MCPClientConfig, + MCPServerSettingsEntry, + MCPToolCall, + OpenAIToolDefinition, + ServerStatus, + ToolCallParams, + ToolExecutionResult, + Tool, + Prompt, + GetPromptResult, + PromptMessage, + MCPProgressState, + MCPResourceAnnotations, + MCPResourceIcon, + MCPResource, + MCPResourceTemplate, + MCPTextResourceContent, + MCPBlobResourceContent, + MCPResourceContent, + MCPReadResourceResult, + MCPResourceInfo, + MCPResourceTemplateInfo, + MCPCachedResource, + MCPResourceAttachment, + MCPResourceSubscription, + MCPServerResources +} from './mcp'; + +// Agentic types +export type { + AgenticConfig, + AgenticToolCallPayload, + AgenticMessage, + AgenticAssistantMessage, + AgenticToolCallList, + AgenticChatCompletionRequest, + AgenticSession, + AgenticFlowCallbacks, + AgenticFlowOptions, + AgenticFlowParams, + AgenticFlowResult +} from './agentic'; diff --git a/tools/server/webui/src/lib/types/settings.d.ts b/tools/server/webui/src/lib/types/settings.d.ts index eca6d8c4da..82fd034f90 100644 --- a/tools/server/webui/src/lib/types/settings.d.ts +++ b/tools/server/webui/src/lib/types/settings.d.ts @@ -1,7 +1,8 @@ import type { SETTING_CONFIG_DEFAULT } from '$lib/constants/settings-config'; import type { ChatMessagePromptProgress, ChatMessageTimings } from './chat'; -import type { ParameterSource, SyncableParameterType, SettingsFieldType } from '$lib/enums'; +import type { OpenAIToolDefinition } from './mcp'; import type { DatabaseMessageExtra } from './database'; +import type { ParameterSource, SyncableParameterType, SettingsFieldType } from '$lib/enums'; export type SettingsConfigValue = string | number | boolean; @@ -22,6 +23,7 @@ export interface SettingsChatServiceOptions { systemMessage?: string; // Disable reasoning parsing (use 'none' instead of 'auto') disableReasoningParsing?: boolean; + tools?: OpenAIToolDefinition[]; // Generation parameters temperature?: number; max_tokens?: number; @@ -69,14 +71,18 @@ export type SettingsConfigType = typeof SETTING_CONFIG_DEFAULT & { [key: string]: SettingsConfigValue; }; +/** + * Parameter synchronization types for server defaults and user overrides + * Note: ParameterSource and SyncableParameterType enums are imported from '$lib/enums' + */ export type ParameterValue = string | number | boolean; export type ParameterRecord = Record; export interface ParameterInfo { - value: ParameterValue; + value: string | number | boolean; source: ParameterSource; - serverDefault?: ParameterValue; - userOverride?: ParameterValue; + serverDefault?: string | number | boolean; + userOverride?: string | number | boolean; } export interface SyncableParameter { diff --git a/tools/server/webui/src/lib/utils/attachment-display.ts b/tools/server/webui/src/lib/utils/attachment-display.ts index 750aaa38d7..396ed6671d 100644 --- a/tools/server/webui/src/lib/utils/attachment-display.ts +++ b/tools/server/webui/src/lib/utils/attachment-display.ts @@ -1,9 +1,30 @@ -import { FileTypeCategory } from '$lib/enums'; +import { AttachmentType, FileTypeCategory, SpecialFileType } from '$lib/enums'; import { getFileTypeCategory, getFileTypeCategoryByExtension, isImageFile } from '$lib/utils'; +import type { + AttachmentDisplayItemsOptions, + ChatUploadedFile, + DatabaseMessageExtra +} from '$lib/types'; -export interface AttachmentDisplayItemsOptions { - uploadedFiles?: ChatUploadedFile[]; - attachments?: DatabaseMessageExtra[]; +/** + * Check if an uploaded file is an MCP prompt + */ +function isMcpPromptUpload(file: ChatUploadedFile): boolean { + return file.type === SpecialFileType.MCP_PROMPT && !!file.mcpPrompt; +} + +/** + * Check if an attachment is an MCP prompt + */ +function isMcpPromptAttachment(attachment: DatabaseMessageExtra): boolean { + return attachment.type === AttachmentType.MCP_PROMPT; +} + +/** + * Check if an attachment is an MCP resource + */ +function isMcpResourceAttachment(attachment: DatabaseMessageExtra): boolean { + return attachment.type === AttachmentType.MCP_RESOURCE; } /** @@ -37,6 +58,9 @@ export function getAttachmentDisplayItems( size: file.size, preview: file.preview, isImage: getUploadedFileCategory(file) === FileTypeCategory.IMAGE, + isMcpPrompt: isMcpPromptUpload(file), + isLoading: file.isLoading, + loadError: file.loadError, uploadedFile: file, textContent: file.textContent }); @@ -45,12 +69,16 @@ export function getAttachmentDisplayItems( // Add stored attachments (ChatMessage) for (const [index, attachment] of attachments.entries()) { const isImage = isImageFile(attachment); + const isMcpPrompt = isMcpPromptAttachment(attachment); + const isMcpResource = isMcpResourceAttachment(attachment); items.push({ id: `attachment-${index}`, name: attachment.name, preview: isImage && 'base64Url' in attachment ? attachment.base64Url : undefined, isImage, + isMcpPrompt, + isMcpResource, attachment, attachmentIndex: index, textContent: 'content' in attachment ? attachment.content : undefined diff --git a/tools/server/webui/src/lib/utils/clipboard.ts b/tools/server/webui/src/lib/utils/clipboard.ts index 940e64c8ff..8fcb554b1a 100644 --- a/tools/server/webui/src/lib/utils/clipboard.ts +++ b/tools/server/webui/src/lib/utils/clipboard.ts @@ -3,8 +3,14 @@ import { AttachmentType } from '$lib/enums'; import type { DatabaseMessageExtra, DatabaseMessageExtraTextFile, - DatabaseMessageExtraLegacyContext -} from '$lib/types/database'; + DatabaseMessageExtraLegacyContext, + DatabaseMessageExtraMcpPrompt, + DatabaseMessageExtraMcpResource, + ClipboardTextAttachment, + ClipboardMcpPromptAttachment, + ClipboardAttachment, + ParsedClipboardContent +} from '$lib/types'; /** * Copy text to clipboard with toast notification @@ -68,23 +74,6 @@ export async function copyCodeToClipboard( return copyToClipboard(rawCode, successMessage, errorMessage); } -/** - * Format for text attachments when copied to clipboard - */ -export interface ClipboardTextAttachment { - type: typeof AttachmentType.TEXT; - name: string; - content: string; -} - -/** - * Parsed result from clipboard content - */ -export interface ParsedClipboardContent { - message: string; - textAttachments: ClipboardTextAttachment[]; -} - /** * Formats a message with text attachments for clipboard copying. * @@ -116,11 +105,20 @@ export function formatMessageForClipboard( extras?: DatabaseMessageExtra[], asPlainText: boolean = false ): string { - // Filter only text attachments (TEXT type and legacy CONTEXT type) + // Filter text-like attachments (TEXT, LEGACY_CONTEXT, MCP_PROMPT, and MCP_RESOURCE types) const textAttachments = extras?.filter( - (extra): extra is DatabaseMessageExtraTextFile | DatabaseMessageExtraLegacyContext => - extra.type === AttachmentType.TEXT || extra.type === AttachmentType.LEGACY_CONTEXT + ( + extra + ): extra is + | DatabaseMessageExtraTextFile + | DatabaseMessageExtraLegacyContext + | DatabaseMessageExtraMcpPrompt + | DatabaseMessageExtraMcpResource => + extra.type === AttachmentType.TEXT || + extra.type === AttachmentType.LEGACY_CONTEXT || + extra.type === AttachmentType.MCP_PROMPT || + extra.type === AttachmentType.MCP_RESOURCE ) ?? []; if (textAttachments.length === 0) { @@ -135,11 +133,24 @@ export function formatMessageForClipboard( return parts.join('\n\n'); } - const clipboardAttachments: ClipboardTextAttachment[] = textAttachments.map((att) => ({ - type: AttachmentType.TEXT, - name: att.name, - content: att.content - })); + const clipboardAttachments: ClipboardAttachment[] = textAttachments.map((att) => { + if (att.type === AttachmentType.MCP_PROMPT) { + const mcpAtt = att as DatabaseMessageExtraMcpPrompt; + return { + type: AttachmentType.MCP_PROMPT, + name: mcpAtt.name, + serverName: mcpAtt.serverName, + promptName: mcpAtt.promptName, + content: mcpAtt.content, + arguments: mcpAtt.arguments + } as ClipboardMcpPromptAttachment; + } + return { + type: AttachmentType.TEXT, + name: att.name, + content: att.content + } as ClipboardTextAttachment; + }); return `${JSON.stringify(content)}\n${JSON.stringify(clipboardAttachments, null, 2)}`; } @@ -154,7 +165,8 @@ export function formatMessageForClipboard( export function parseClipboardContent(clipboardText: string): ParsedClipboardContent { const defaultResult: ParsedClipboardContent = { message: clipboardText, - textAttachments: [] + textAttachments: [], + mcpPromptAttachments: [] }; if (!clipboardText.startsWith('"')) { @@ -196,17 +208,28 @@ export function parseClipboardContent(clipboardText: string): ParsedClipboardCon if (!remainingPart || !remainingPart.startsWith('[')) { return { message, - textAttachments: [] + textAttachments: [], + mcpPromptAttachments: [] }; } const attachments = JSON.parse(remainingPart) as unknown[]; - const validAttachments: ClipboardTextAttachment[] = []; + const validTextAttachments: ClipboardTextAttachment[] = []; + const validMcpPromptAttachments: ClipboardMcpPromptAttachment[] = []; for (const att of attachments) { - if (isValidTextAttachment(att)) { - validAttachments.push({ + if (isValidMcpPromptAttachment(att)) { + validMcpPromptAttachments.push({ + type: AttachmentType.MCP_PROMPT, + name: att.name, + serverName: att.serverName, + promptName: att.promptName, + content: att.content, + arguments: att.arguments + }); + } else if (isValidTextAttachment(att)) { + validTextAttachments.push({ type: AttachmentType.TEXT, name: att.name, content: att.content @@ -216,13 +239,42 @@ export function parseClipboardContent(clipboardText: string): ParsedClipboardCon return { message, - textAttachments: validAttachments + textAttachments: validTextAttachments, + mcpPromptAttachments: validMcpPromptAttachments }; } catch { return defaultResult; } } +/** + * Type guard to validate an MCP prompt attachment object + * @param obj The object to validate + * @returns true if the object is a valid MCP prompt attachment + */ +function isValidMcpPromptAttachment(obj: unknown): obj is { + type: string; + name: string; + serverName: string; + promptName: string; + content: string; + arguments?: Record; +} { + if (typeof obj !== 'object' || obj === null) { + return false; + } + + const record = obj as Record; + + return ( + (record.type === AttachmentType.MCP_PROMPT || record.type === 'MCP_PROMPT') && + typeof record.name === 'string' && + typeof record.serverName === 'string' && + typeof record.promptName === 'string' && + typeof record.content === 'string' + ); +} + /** * Type guard to validate a text attachment object * @param obj The object to validate @@ -255,5 +307,5 @@ export function hasClipboardAttachments(clipboardText: string): boolean { } const parsed = parseClipboardContent(clipboardText); - return parsed.textAttachments.length > 0; + return parsed.textAttachments.length > 0 || parsed.mcpPromptAttachments.length > 0; } diff --git a/tools/server/webui/src/lib/utils/convert-files-to-extra.ts b/tools/server/webui/src/lib/utils/convert-files-to-extra.ts index 6eb50f6dce..15074e919a 100644 --- a/tools/server/webui/src/lib/utils/convert-files-to-extra.ts +++ b/tools/server/webui/src/lib/utils/convert-files-to-extra.ts @@ -1,12 +1,13 @@ import { convertPDFToImage, convertPDFToText } from './pdf-processing'; import { isSvgMimeType, svgBase64UrlToPngDataURL } from './svg-to-png'; import { isWebpMimeType, webpBase64UrlToPngDataURL } from './webp-to-png'; -import { FileTypeCategory, AttachmentType } from '$lib/enums'; +import { FileTypeCategory, AttachmentType, SpecialFileType } from '$lib/enums'; import { config, settingsStore } from '$lib/stores/settings.svelte'; import { modelsStore } from '$lib/stores/models.svelte'; import { getFileTypeCategory } from '$lib/utils'; import { readFileAsText, isLikelyTextFile } from './text-files'; import { toast } from 'svelte-sonner'; +import type { FileProcessingResult, ChatUploadedFile, DatabaseMessageExtra } from '$lib/types'; function readFileAsBase64(file: File): Promise { return new Promise((resolve, reject) => { @@ -25,11 +26,6 @@ function readFileAsBase64(file: File): Promise { }); } -export interface FileProcessingResult { - extras: DatabaseMessageExtra[]; - emptyFiles: string[]; -} - export async function parseFilesToMessageExtras( files: ChatUploadedFile[], activeModelId?: string @@ -38,6 +34,18 @@ export async function parseFilesToMessageExtras( const emptyFiles: string[] = []; for (const file of files) { + if (file.type === SpecialFileType.MCP_PROMPT && file.mcpPrompt) { + extras.push({ + type: AttachmentType.MCP_PROMPT, + name: file.name, + serverName: file.mcpPrompt.serverName, + promptName: file.mcpPrompt.promptName, + content: file.textContent ?? '', + arguments: file.mcpPrompt.arguments + }); + continue; + } + if (getFileTypeCategory(file.type) === FileTypeCategory.IMAGE) { if (file.preview) { let base64Url = file.preview; diff --git a/tools/server/webui/src/lib/utils/formatters.ts b/tools/server/webui/src/lib/utils/formatters.ts index bdf2ca26fd..37a8a3358c 100644 --- a/tools/server/webui/src/lib/utils/formatters.ts +++ b/tools/server/webui/src/lib/utils/formatters.ts @@ -1,3 +1,11 @@ +import { + MS_PER_SECOND, + SECONDS_PER_MINUTE, + SECONDS_PER_HOUR, + SHORT_DURATION_THRESHOLD, + MEDIUM_DURATION_THRESHOLD +} from '$lib/constants/formatters'; + /** * Formats file size in bytes to human readable format * Supports Bytes, KB, MB, and GB @@ -93,19 +101,19 @@ export function formatTime(date: Date): string { export function formatPerformanceTime(ms: number): string { if (ms < 0) return '0s'; - const totalSeconds = ms / 1000; + const totalSeconds = ms / MS_PER_SECOND; - if (totalSeconds < 1) { + if (totalSeconds < SHORT_DURATION_THRESHOLD) { return `${totalSeconds.toFixed(1)}s`; } - if (totalSeconds < 10) { + if (totalSeconds < MEDIUM_DURATION_THRESHOLD) { return `${totalSeconds.toFixed(1)}s`; } - const hours = Math.floor(totalSeconds / 3600); - const minutes = Math.floor((totalSeconds % 3600) / 60); - const seconds = Math.floor(totalSeconds % 60); + const hours = Math.floor(totalSeconds / SECONDS_PER_HOUR); + const minutes = Math.floor((totalSeconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE); + const seconds = Math.floor(totalSeconds % SECONDS_PER_MINUTE); const parts: string[] = []; @@ -123,3 +131,23 @@ export function formatPerformanceTime(ms: number): string { return parts.join(' '); } + +/** + * Formats attachment content for API requests with consistent header style. + * Used when converting message attachments to text content parts. + * + * @param label - Type label (e.g., 'File', 'PDF File', 'MCP Prompt') + * @param name - File or attachment name + * @param content - The actual content to include + * @param extra - Optional extra info to append to name (e.g., server name for MCP) + * @returns Formatted string with header and content + */ +export function formatAttachmentText( + label: string, + name: string, + content: string, + extra?: string +): string { + const header = extra ? `${name} (${extra})` : name; + return `\n\n--- ${label}: ${header} ---\n${content}`; +} diff --git a/tools/server/webui/src/lib/utils/headers.ts b/tools/server/webui/src/lib/utils/headers.ts new file mode 100644 index 0000000000..0b907b8300 --- /dev/null +++ b/tools/server/webui/src/lib/utils/headers.ts @@ -0,0 +1,44 @@ +/** + * Header utilities for parsing and serializing HTTP headers. + * Generic utilities not specific to MCP. + */ + +/** + * Parses a JSON string of headers into an array of key-value pairs. + * Returns empty array if the JSON is invalid or empty. + */ +export function parseHeadersToArray(headersJson: string): { key: string; value: string }[] { + if (!headersJson?.trim()) return []; + + try { + const parsed = JSON.parse(headersJson); + if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) { + return Object.entries(parsed).map(([key, value]) => ({ + key, + value: String(value) + })); + } + } catch { + return []; + } + + return []; +} + +/** + * Serializes an array of header key-value pairs to a JSON string. + * Filters out pairs with empty keys and returns empty string if no valid pairs. + */ +export function serializeHeaders(pairs: { key: string; value: string }[]): string { + const validPairs = pairs.filter((p) => p.key.trim()); + + if (validPairs.length === 0) return ''; + + const obj: Record = {}; + + for (const pair of validPairs) { + obj[pair.key.trim()] = pair.value; + } + + return JSON.stringify(obj); +} diff --git a/tools/server/webui/src/lib/utils/index.ts b/tools/server/webui/src/lib/utils/index.ts index 5eb2bbaea1..19eddf3ee7 100644 --- a/tools/server/webui/src/lib/utils/index.ts +++ b/tools/server/webui/src/lib/utils/index.ts @@ -13,10 +13,7 @@ export { apiFetch, apiFetchWithParams, apiPost, type ApiFetchOptions } from './a export { validateApiKey } from './api-key-validation'; // Attachment utilities -export { - getAttachmentDisplayItems, - type AttachmentDisplayItemsOptions -} from './attachment-display'; +export { getAttachmentDisplayItems } from './attachment-display'; export { isTextFile, isImageFile, isPdfFile, isAudioFile } from './attachment-type'; // Textarea utilities @@ -46,9 +43,7 @@ export { copyCodeToClipboard, formatMessageForClipboard, parseClipboardContent, - hasClipboardAttachments, - type ClipboardTextAttachment, - type ParsedClipboardContent + hasClipboardAttachments } from './clipboard'; // File preview utilities @@ -64,7 +59,15 @@ export { } from './file-type'; // Formatting utilities -export { formatFileSize, formatParameters, formatNumber } from './formatters'; +export { + formatFileSize, + formatParameters, + formatNumber, + formatJsonPretty, + formatTime, + formatPerformanceTime, + formatAttachmentText +} from './formatters'; // IME utilities export { isIMEComposing } from './is-ime-composing'; @@ -94,5 +97,50 @@ export { getLanguageFromFilename } from './syntax-highlight-language'; // Text file utilities export { isTextFileByName, readFileAsText, isLikelyTextFile } from './text-files'; +// Debounce utilities +export { debounce } from './debounce'; + // Image error fallback utilities export { getImageErrorFallbackHtml } from './image-error-fallback'; + +// MCP utilities +export { + detectMcpTransportFromUrl, + parseMcpServerSettings, + getMcpLogLevelIcon, + getMcpLogLevelClass, + isImageMimeType, + parseResourcePath, + getDisplayName, + getResourceDisplayName, + isCodeResource, + isImageResource, + getResourceIcon, + getResourceTextContent, + getResourceBlobContent, + downloadResourceContent +} from './mcp'; + +// Data URL utilities +export { createBase64DataUrl } from './data-url'; + +// Header utilities +export { parseHeadersToArray, serializeHeaders } from './headers'; + +// Favicon utilities +export { getFaviconUrl } from './favicon'; + +// Agentic content parsing utilities +export { parseAgenticContent, type AgenticSection } from './agentic'; + +// Cache utilities +export { TTLCache, ReactiveTTLMap, type TTLCacheOptions } from './cache-ttl'; + +// Abort signal utilities +export { + throwIfAborted, + isAbortError, + createLinkedController, + createTimeoutSignal, + withAbortSignal +} from './abort'; From b02d5dea03fb00870db41816839137d267bc29b3 Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Tue, 17 Feb 2026 14:44:57 +0100 Subject: [PATCH 3/8] webui: isolate settings schema and tooling updates pre-MCP --- tools/server/webui/package-lock.json | 2100 +++++++++++++++-- tools/server/webui/package.json | 21 +- .../src/lib/constants/settings-config.ts | 30 +- .../webui/src/lib/constants/settings-keys.ts | 57 + .../src/lib/constants/settings-sections.ts | 5 + 5 files changed, 1966 insertions(+), 247 deletions(-) create mode 100644 tools/server/webui/src/lib/constants/settings-keys.ts diff --git a/tools/server/webui/package-lock.json b/tools/server/webui/package-lock.json index 6834416824..f19d4ff8ec 100644 --- a/tools/server/webui/package-lock.json +++ b/tools/server/webui/package-lock.json @@ -8,6 +8,7 @@ "name": "webui", "version": "1.0.0", "dependencies": { + "@modelcontextprotocol/sdk": "^1.25.1", "highlight.js": "^11.11.1", "mode-watcher": "^1.1.0", "pdfjs-dist": "^5.4.54", @@ -19,34 +20,36 @@ "remark-html": "^16.0.1", "remark-rehype": "^11.1.2", "svelte-sonner": "^1.0.5", - "unist-util-visit": "^5.0.0" + "unist-util-visit": "^5.0.0", + "zod": "^4.2.1" }, "devDependencies": { - "@chromatic-com/storybook": "^4.1.2", + "@chromatic-com/storybook": "^5.0.0", "@eslint/compat": "^1.2.5", "@eslint/js": "^9.18.0", "@internationalized/date": "^3.10.1", "@lucide/svelte": "^0.515.0", "@playwright/test": "^1.49.1", - "@storybook/addon-a11y": "^10.0.7", - "@storybook/addon-docs": "^10.0.7", + "@storybook/addon-a11y": "^10.2.4", + "@storybook/addon-docs": "^10.2.4", "@storybook/addon-svelte-csf": "^5.0.10", - "@storybook/addon-vitest": "^10.0.7", - "@storybook/sveltekit": "^10.0.7", + "@storybook/addon-vitest": "^10.2.4", + "@storybook/sveltekit": "^10.2.4", "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.48.4", "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/forms": "^0.5.9", "@tailwindcss/typography": "^0.5.15", "@tailwindcss/vite": "^4.0.0", - "@types/node": "^22", + "@types/node": "^24", "@vitest/browser": "^3.2.3", + "@vitest/coverage-v8": "^3.2.3", "bits-ui": "^2.14.4", "clsx": "^2.1.1", "dexie": "^4.0.11", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", - "eslint-plugin-storybook": "^10.0.7", + "eslint-plugin-storybook": "^10.2.4", "eslint-plugin-svelte": "^3.0.0", "fflate": "^0.8.2", "globals": "^16.0.0", @@ -60,7 +63,7 @@ "rehype-katex": "^7.0.1", "remark-math": "^6.0.0", "sass": "^1.93.3", - "storybook": "^10.0.7", + "storybook": "^10.2.4", "svelte": "^5.38.2", "svelte-check": "^4.0.0", "tailwind-merge": "^3.3.1", @@ -113,16 +116,42 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-identifier": { + "node_modules/@babel/helper-string-parser": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { "version": "7.27.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", @@ -133,15 +162,39 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@chromatic-com/storybook": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-4.1.2.tgz", - "integrity": "sha512-QAWGtHwib0qsP5CcO64aJCF75zpFgpKK3jNpxILzQiPK3sVo4EmnVGJVdwcZWpWrGdH8E4YkncGoitw4EXzKMg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-5.0.0.tgz", + "integrity": "sha512-8wUsqL8kg6R5ue8XNE7Jv/iD1SuE4+6EXMIGIuE+T2loBITEACLfC3V8W44NJviCLusZRMWbzICddz0nU0bFaw==", "dev": true, "license": "MIT", "dependencies": { "@neoconfetti/react": "^1.0.0", - "chromatic": "^12.0.0", + "chromatic": "^13.3.4", "filesize": "^10.0.12", "jsonfile": "^6.1.0", "strip-ansi": "^7.1.0" @@ -151,7 +204,7 @@ "yarn": ">=1.22.18" }, "peerDependencies": { - "storybook": "^0.0.0-0 || ^9.0.0 || ^9.1.0-0 || ^9.2.0-0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0" + "storybook": "^0.0.0-0 || ^10.1.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0" } }, "node_modules/@esbuild/aix-ppc64": { @@ -597,9 +650,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -796,6 +849,18 @@ "dev": true, "license": "MIT" }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -873,6 +938,24 @@ "@swc/helpers": "^0.5.0" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -886,6 +969,16 @@ "node": ">=18.0.0" } }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.12", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", @@ -922,9 +1015,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -959,6 +1052,68 @@ "react": ">=16" } }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/@napi-rs/canvas": { "version": "0.1.76", "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.76.tgz", @@ -1513,6 +1668,17 @@ "node": ">=0.10" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@playwright/test": { "version": "1.56.1", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", @@ -1824,9 +1990,9 @@ "license": "MIT" }, "node_modules/@storybook/addon-a11y": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-10.0.7.tgz", - "integrity": "sha512-JsYPpZ/n67/2bI1XJeyrAWHHQkHemPkPHjCA0tAUnMz1Shlo/LV2q1Ahgpxoihx4strbHwZz71bcS4MqkHBduA==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-10.2.4.tgz", + "integrity": "sha512-VGhdZ+iP2l/CSulIKV2kt3SMWVHntOigqWqGkNYf6YNYofynUYEKdsNqBvHx4ySuNEl/eXJ8LRO8FKYnU7LxZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1838,20 +2004,20 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.0.7" + "storybook": "^10.2.4" } }, "node_modules/@storybook/addon-docs": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.0.7.tgz", - "integrity": "sha512-qQQMoeYZC4W+/8ubfOZiTrE8nYC/f4wWP1uq4peRyDy1N2nIN9SwhyxwMn0m3VpeGmRBga5dLvJY9ko6SnJekg==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.2.4.tgz", + "integrity": "sha512-FzscAmdBiOGnGrxiEM+8eTg43kjqgjLfObg+lbJVRR/a0DmZ3xfAPNB0+VKYQbN0FacNcWLM9LZ/7U0hRBPBnQ==", "dev": true, "license": "MIT", "dependencies": { "@mdx-js/react": "^3.0.0", - "@storybook/csf-plugin": "10.0.7", - "@storybook/icons": "^1.6.0", - "@storybook/react-dom-shim": "10.0.7", + "@storybook/csf-plugin": "10.2.4", + "@storybook/icons": "^2.0.1", + "@storybook/react-dom-shim": "10.2.4", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" @@ -1861,7 +2027,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.0.7" + "storybook": "^10.2.4" } }, "node_modules/@storybook/addon-svelte-csf": { @@ -1888,16 +2054,14 @@ } }, "node_modules/@storybook/addon-vitest": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-vitest/-/addon-vitest-10.0.7.tgz", - "integrity": "sha512-i6v/mAl+elrUxb+1f4NdnM17t/fg+KGJWL1U9quflXTd3KiLY0xJB4LwNP6yYo7Imc5NIO2fRkJbGvNqLBRe2Q==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-vitest/-/addon-vitest-10.2.4.tgz", + "integrity": "sha512-BT1iP89U4wcbpzTURU8WYTAeUcdNh4WIt0BqsnATmMwR/jKNJW6QgXCVqGQTSpRjWj40hX5e2JkQYCNXdjKsPw==", "dev": true, "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/icons": "^1.6.0", - "prompts": "^2.4.0", - "ts-dedent": "^2.2.0" + "@storybook/icons": "^2.0.1" }, "funding": { "type": "opencollective", @@ -1907,7 +2071,7 @@ "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", - "storybook": "^10.0.7", + "storybook": "^10.2.4", "vitest": "^3.0.0 || ^4.0.0" }, "peerDependenciesMeta": { @@ -1926,13 +2090,13 @@ } }, "node_modules/@storybook/builder-vite": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.0.7.tgz", - "integrity": "sha512-wk2TAoUY5+9t78GWVBndu9rEo9lo6Ec3SRrLT4VpIlcS2GPK+5f26UC2uvIBwOF/N7JrUUKq/zWDZ3m+do9QDg==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.2.4.tgz", + "integrity": "sha512-/hcT1xj3CL5GkJ5v5/EguZdttDwNE6weNXK7vKzp034tnGcLycOossDsTiUQkBowSL+Ylc8aKj+ZgvddPNfOig==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/csf-plugin": "10.0.7", + "@storybook/csf-plugin": "10.2.4", "ts-dedent": "^2.0.0" }, "funding": { @@ -1940,7 +2104,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.0.7", + "storybook": "^10.2.4", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, @@ -1955,9 +2119,9 @@ } }, "node_modules/@storybook/csf-plugin": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.0.7.tgz", - "integrity": "sha512-YaYYlCyJBwxaMk7yREOdz+9MDSgxIYGdeJ9EIq/bUndmkoj9SRo1P9/0lC5dseWQoiGy4T3PbZiWruD8uM5m3g==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.2.4.tgz", + "integrity": "sha512-kupPQEV+4N9mzsZHYaokvhO/KHBjYdWda9PNmPQwy0TR7r2mzthgaNH72TjmgN1L6DIbsuyOG1wtczcPJn4+Jg==", "dev": true, "license": "MIT", "dependencies": { @@ -1970,7 +2134,7 @@ "peerDependencies": { "esbuild": "*", "rollup": "*", - "storybook": "^10.0.7", + "storybook": "^10.2.4", "vite": "*", "webpack": "*" }, @@ -1997,23 +2161,20 @@ "license": "MIT" }, "node_modules/@storybook/icons": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-1.6.0.tgz", - "integrity": "sha512-hcFZIjW8yQz8O8//2WTIXylm5Xsgc+lW9ISLgUk1xGmptIJQRdlhVIXCpSyLrQaaRiyhQRaVg7l3BD9S216BHw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-2.0.1.tgz", + "integrity": "sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=14.0.0" - }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/@storybook/react-dom-shim": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.0.7.tgz", - "integrity": "sha512-bp4OnMtZGwPJQDqNRi4K5iibLbZ2TZZMkWW7oSw5jjPFpGSreSjCe8LH9yj/lDnK8Ox9bGMCBFE5RV5XuML29w==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.2.4.tgz", + "integrity": "sha512-i22OtrZ7GeZPt/odLf0vqyDhRSKyaLsHkkKSBcANQfzRRnBZmiz2FchOtWm9uvoDWybQsTruZq7kTdtpEhwyGw==", "dev": true, "license": "MIT", "funding": { @@ -2023,13 +2184,13 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.0.7" + "storybook": "^10.2.4" } }, "node_modules/@storybook/svelte": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@storybook/svelte/-/svelte-10.0.7.tgz", - "integrity": "sha512-rO+YQhHucy47Vh67z318pALmd6x+K1Kj30Fb4a6oOEw4xn4zCo9KTmkMWs24c4oduEXD/eJu3badlRmsVXzyfA==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/@storybook/svelte/-/svelte-10.2.4.tgz", + "integrity": "sha512-W9R51zUCd2iHOQBg/D93+bdpYv6kbtFx+kft5X8lPKQl6yEu0aKs9i5N5GyCASOhIApgx/tkqZIJ7vgM4cqrHA==", "dev": true, "license": "MIT", "peer": true, @@ -2042,19 +2203,19 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.0.7", + "storybook": "^10.2.4", "svelte": "^5.0.0" } }, "node_modules/@storybook/svelte-vite": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@storybook/svelte-vite/-/svelte-vite-10.0.7.tgz", - "integrity": "sha512-q9/RtrhX1CnznO6AO9MDEy1bsccbGeRxW28FLpgUrztV4IGZ/dFUrFIFurKRyuA3/nFsbtzp1F5jFt3RExmmTw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/@storybook/svelte-vite/-/svelte-vite-10.2.4.tgz", + "integrity": "sha512-FMgKMRdoZFDwPD6eIDMldcgp6d6NtIGuXyUJjb29qLias/gE5TI6hg+cWmmWXQRTrXwdyepeMBmIfRcZbB6REQ==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/builder-vite": "10.0.7", - "@storybook/svelte": "10.0.7", + "@storybook/builder-vite": "10.2.4", + "@storybook/svelte": "10.2.4", "magic-string": "^0.30.0", "svelte2tsx": "^0.7.44", "typescript": "^4.9.4 || ^5.0.0" @@ -2065,28 +2226,28 @@ }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", - "storybook": "^10.0.7", + "storybook": "^10.2.4", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "node_modules/@storybook/sveltekit": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@storybook/sveltekit/-/sveltekit-10.0.7.tgz", - "integrity": "sha512-ujTW7PfWvgBrzd7jzaZe9JgjUeM5YvBKm+xru6t7Dr4bdfmkKqlZHPRdXn/sy+fQNyfg6JL2WKy2KIIeA+RvSg==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/@storybook/sveltekit/-/sveltekit-10.2.4.tgz", + "integrity": "sha512-1qDX35iSJHWo1AOd7HMzJtCHBfgahXqTWNiyZa/JMEKJ3qC1otaU8XMmTjsZ6fCRF99piNdgqtWM8+s1TJOldg==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/builder-vite": "10.0.7", - "@storybook/svelte": "10.0.7", - "@storybook/svelte-vite": "10.0.7" + "@storybook/builder-vite": "10.2.4", + "@storybook/svelte": "10.2.4", + "@storybook/svelte-vite": "10.2.4" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.0.7", + "storybook": "^10.2.4", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } @@ -2111,9 +2272,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.49.2", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.49.2.tgz", - "integrity": "sha512-Vp3zX/qlwerQmHMP6x0Ry1oY7eKKRcOWGc2P59srOp4zcqyn+etJyQpELgOi4+ZSUgteX8Y387NuwruLgGXLUQ==", + "version": "2.50.2", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.50.2.tgz", + "integrity": "sha512-875hTUkEbz+MyJIxWbQjfMaekqdmEKUUfR7JyKcpfMRZqcGyrO9Gd+iS1D/Dx8LpE5FEtutWGOtlAh4ReSAiOA==", "dev": true, "license": "MIT", "peer": true, @@ -2123,13 +2284,13 @@ "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", - "devalue": "^5.3.2", + "devalue": "^5.6.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", - "set-cookie-parser": "^2.6.0", + "set-cookie-parser": "^3.0.0", "sirv": "^3.0.0" }, "bin": { @@ -2142,11 +2303,15 @@ "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.3.3", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "peerDependenciesMeta": { "@opentelemetry/api": { "optional": true + }, + "typescript": { + "optional": true } } }, @@ -2735,14 +2900,14 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.16.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.5.tgz", - "integrity": "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==", + "version": "24.10.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.10.tgz", + "integrity": "sha512-+0/4J266CBGPUq/ELg7QUHhN25WYjE0wYTPSQJn1xeu8DOlIOPxXxrNGiLmfAWl7HMMgWFWXpt9IDjMWrF5Iow==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/react": { @@ -3064,6 +3229,40 @@ } } }, + "node_modules/@vitest/coverage-v8": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", @@ -3108,16 +3307,6 @@ } } }, - "node_modules/@vitest/mocker/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/@vitest/pretty-format": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", @@ -3190,6 +3379,19 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -3230,6 +3432,45 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -3296,6 +3537,25 @@ "node": ">=4" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz", + "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -3353,9 +3613,9 @@ } }, "node_modules/bits-ui": { - "version": "2.14.4", - "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.14.4.tgz", - "integrity": "sha512-W6kenhnbd/YVvur+DKkaVJ6GldE53eLewur5AhUCqslYQ0vjZr8eWlOfwZnMiPB+PF5HMVqf61vXBvmyrAmPWg==", + "version": "2.15.5", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.15.5.tgz", + "integrity": "sha512-WhS+P+E//ClLfKU6KqjKC17nGDRLnz+vkwoP6ClFUPd5m1fFVDxTElPX8QVsduLj5V1KFDxlnv6sW2G5Lqk+vw==", "dev": true, "license": "MIT", "dependencies": { @@ -3423,6 +3683,46 @@ "svelte": "^5.30.2" } }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -3447,6 +3747,31 @@ "node": ">=8" } }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -3461,7 +3786,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3475,7 +3799,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3609,9 +3932,9 @@ } }, "node_modules/chromatic": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-12.2.0.tgz", - "integrity": "sha512-GswmBW9ZptAoTns1BMyjbm55Z7EsIJnUvYKdQqXIBZIKbGErmpA+p4c0BYA+nzw5B0M+rb3Iqp1IaH8TFwIQew==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-13.3.5.tgz", + "integrity": "sha512-MzPhxpl838qJUo0A55osCF2ifwPbjcIPeElr1d4SHcjnHoIcg7l1syJDrAYK/a+PcCBrOGi06jPNpQAln5hWgw==", "dev": true, "license": "MIT", "bin": { @@ -3688,6 +4011,28 @@ "dev": true, "license": "MIT" }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", @@ -3698,6 +4043,28 @@ "node": ">= 0.6" } }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/corser": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", @@ -3712,7 +4079,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -3751,9 +4117,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3829,18 +4195,70 @@ "node": ">=0.10.0" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", "dev": true, "license": "Apache-2.0", @@ -3885,7 +4303,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -3896,6 +4313,35 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.2", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", @@ -3927,7 +4373,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3937,7 +4382,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3954,7 +4398,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4017,6 +4460,12 @@ "@esbuild/win32-x64": "0.25.8" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -4109,17 +4558,184 @@ } }, "node_modules/eslint-plugin-storybook": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.0.7.tgz", - "integrity": "sha512-qOQq9KdT1jsBgT3qsxUH2n67aj1WR8D1XCoER8Q6yuVlS5TimNwk1mZeWkXVf/o4RQQT6flT2y5cG2gPLZPvJA==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.2.4.tgz", + "integrity": "sha512-D8a6Y+iun2MSOpgps0Vd/t8y9Y5ZZ7O2VeKqw2PCv2+b7yInqogOS2VBMSRZVfP8TTGQgDpbUK67k7KZEUC7Ng==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^8.8.1" + "@typescript-eslint/utils": "^8.48.0" }, "peerDependencies": { "eslint": ">=8", - "storybook": "^10.0.7" + "storybook": "^10.2.4" + } + }, + "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/project-service": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", + "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.54.0", + "@typescript-eslint/types": "^8.54.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/scope-manager": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", + "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", + "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/types": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", + "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", + "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.54.0", + "@typescript-eslint/tsconfig-utils": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", + "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", + "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-storybook/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/eslint-plugin-storybook/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/eslint-plugin-svelte": { @@ -4270,6 +4886,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -4280,6 +4906,15 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -4287,6 +4922,27 @@ "dev": true, "license": "MIT" }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/expect-type": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", @@ -4297,6 +4953,76 @@ "node": ">=12.0.0" } }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -4307,7 +5033,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -4354,6 +5079,22 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -4425,6 +5166,27 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -4484,6 +5246,41 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -4503,7 +5300,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4513,7 +5309,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -4538,7 +5333,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -4548,6 +5342,27 @@ "node": ">= 0.4" } }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4561,6 +5376,32 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { "version": "16.3.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", @@ -4578,7 +5419,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4615,7 +5455,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4628,7 +5467,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4896,6 +5734,16 @@ "node": ">=12.0.0" } }, + "node_modules/hono": { + "version": "4.11.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz", + "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -4909,6 +5757,13 @@ "node": ">=12" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", @@ -4919,6 +5774,26 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", @@ -5029,12 +5904,52 @@ "node": ">=8" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/inline-style-parser": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", "license": "MIT" }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5045,6 +5960,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -5058,6 +5983,25 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5080,13 +6024,104 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jiti": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", @@ -5097,6 +6132,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5131,6 +6175,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -5481,9 +6531,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, @@ -5540,6 +6590,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -5559,6 +6616,34 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", @@ -5573,7 +6658,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5927,6 +7011,27 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -6560,6 +7665,31 @@ "node": ">=4" } }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -6614,9 +7744,9 @@ } }, "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, "license": "MIT", "dependencies": { @@ -6626,22 +7756,6 @@ "node": ">= 18" } }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/mode-watcher": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mode-watcher/-/mode-watcher-1.1.0.tgz", @@ -6707,6 +7821,15 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -6715,11 +7838,19 @@ "license": "MIT", "optional": true }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6728,6 +7859,46 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/opener": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", @@ -6788,6 +7959,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6814,6 +7992,15 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6828,12 +8015,38 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -6883,6 +8096,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/playwright": { "version": "1.56.1", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", @@ -7238,30 +8460,6 @@ "node": ">=6" } }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prompts/node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -7272,6 +8470,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -7283,10 +8494,9 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dev": true, + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -7319,6 +8529,46 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/react": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", @@ -7579,6 +8829,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -7648,6 +8907,35 @@ "fsevents": "~2.3.2" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -7711,7 +8999,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT" }, "node_modules/sass": { @@ -7758,9 +9045,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -7770,18 +9057,68 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.0.1.tgz", + "integrity": "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==", "dev": true, "license": "MIT" }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -7794,7 +9131,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7804,7 +9140,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -7824,7 +9159,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -7841,7 +9175,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -7860,7 +9193,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -7883,6 +9215,19 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/sirv": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", @@ -7898,13 +9243,6 @@ "node": ">=18" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -7942,6 +9280,15 @@ "dev": true, "license": "MIT" }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/std-env": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", @@ -7950,23 +9297,24 @@ "license": "MIT" }, "node_modules/storybook": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.0.7.tgz", - "integrity": "sha512-7smAu0o+kdm378Q2uIddk32pn0UdIbrtTVU+rXRVtTVTCrK/P2cCui2y4JH+Bl3NgEq1bbBQpCAF/HKrDjk2Qw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.2.4.tgz", + "integrity": "sha512-LwF0VZsT4qkgx66Ad/q0QgZZrU2a5WftaADDEcJ3bGq3O2fHvwWPlSZjM1HiXD4vqP9U5JiMqQkV1gkyH0XJkw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/icons": "^1.6.0", + "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", "@vitest/spy": "3.2.4", - "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", + "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", + "open": "^10.2.0", "recast": "^0.23.5", - "semver": "^7.6.2", + "semver": "^7.7.3", + "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "bin": { @@ -7985,6 +9333,60 @@ } } }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -8015,6 +9417,20 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi/node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -8319,9 +9735,9 @@ } }, "node_modules/svelte2tsx": { - "version": "0.7.45", - "resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.7.45.tgz", - "integrity": "sha512-cSci+mYGygYBHIZLHlm/jYlEc1acjAHqaQaDFHdEBpUueM9kSTnPpvPtSl5VkJOU1qSJ7h1K+6F/LIUYiqC8VA==", + "version": "0.7.47", + "resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.7.47.tgz", + "integrity": "sha512-1aw/MFKVPM96OBevJdC12do2an9t5Zwr3Va9amLgTLpJje36ibD1iIHpuqCYWUrdR9vw6g6btKGQPmsqE8ZYCw==", "dev": true, "license": "MIT", "dependencies": { @@ -8391,23 +9807,63 @@ } }, "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", + "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", + "minizlib": "^3.1.0", "yallist": "^5.0.0" }, "engines": { "node": ">=18" } }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -8489,6 +9945,15 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", @@ -8520,9 +9985,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -8585,6 +10050,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -8625,9 +10104,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, @@ -8813,10 +10292,19 @@ "node": ">= 10.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unplugin": { - "version": "2.3.10", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz", - "integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==", + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", "dev": true, "license": "MIT", "dependencies": { @@ -8846,6 +10334,16 @@ "dev": true, "license": "MIT" }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -8867,6 +10365,15 @@ "uuid": "dist-node/bin/uuid" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -9239,7 +10746,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -9278,6 +10784,97 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -9300,6 +10897,22 @@ } } }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", @@ -9329,6 +10942,25 @@ "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", "license": "MIT" }, + "node_modules/zod": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", + "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/tools/server/webui/package.json b/tools/server/webui/package.json index a361ce76e3..f5cdc9e47f 100644 --- a/tools/server/webui/package.json +++ b/tools/server/webui/package.json @@ -23,31 +23,32 @@ "cleanup": "rm -rf .svelte-kit build node_modules test-results" }, "devDependencies": { - "@chromatic-com/storybook": "^4.1.2", + "@chromatic-com/storybook": "^5.0.0", "@eslint/compat": "^1.2.5", "@eslint/js": "^9.18.0", "@internationalized/date": "^3.10.1", "@lucide/svelte": "^0.515.0", "@playwright/test": "^1.49.1", - "@storybook/addon-a11y": "^10.0.7", - "@storybook/addon-docs": "^10.0.7", + "@storybook/addon-a11y": "^10.2.4", + "@storybook/addon-docs": "^10.2.4", "@storybook/addon-svelte-csf": "^5.0.10", - "@storybook/addon-vitest": "^10.0.7", - "@storybook/sveltekit": "^10.0.7", + "@storybook/addon-vitest": "^10.2.4", + "@storybook/sveltekit": "^10.2.4", "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.48.4", "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/forms": "^0.5.9", "@tailwindcss/typography": "^0.5.15", "@tailwindcss/vite": "^4.0.0", - "@types/node": "^22", + "@types/node": "^24", "@vitest/browser": "^3.2.3", + "@vitest/coverage-v8": "^3.2.3", "bits-ui": "^2.14.4", "clsx": "^2.1.1", "dexie": "^4.0.11", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", - "eslint-plugin-storybook": "^10.0.7", + "eslint-plugin-storybook": "^10.2.4", "eslint-plugin-svelte": "^3.0.0", "fflate": "^0.8.2", "globals": "^16.0.0", @@ -61,7 +62,7 @@ "rehype-katex": "^7.0.1", "remark-math": "^6.0.0", "sass": "^1.93.3", - "storybook": "^10.0.7", + "storybook": "^10.2.4", "svelte": "^5.38.2", "svelte-check": "^4.0.0", "tailwind-merge": "^3.3.1", @@ -78,6 +79,7 @@ "vitest-browser-svelte": "^0.1.0" }, "dependencies": { + "@modelcontextprotocol/sdk": "^1.25.1", "highlight.js": "^11.11.1", "mode-watcher": "^1.1.0", "pdfjs-dist": "^5.4.54", @@ -89,6 +91,7 @@ "remark-html": "^16.0.1", "remark-rehype": "^11.1.2", "svelte-sonner": "^1.0.5", - "unist-util-visit": "^5.0.0" + "unist-util-visit": "^5.0.0", + "zod": "^4.2.1" } } diff --git a/tools/server/webui/src/lib/constants/settings-config.ts b/tools/server/webui/src/lib/constants/settings-config.ts index 1b959f3b69..a7e412d3cd 100644 --- a/tools/server/webui/src/lib/constants/settings-config.ts +++ b/tools/server/webui/src/lib/constants/settings-config.ts @@ -1,12 +1,14 @@ +import { ColorMode } from '$lib/enums/ui'; +import { Monitor, Moon, Sun } from '@lucide/svelte'; + export const SETTING_CONFIG_DEFAULT: Record = { // Note: in order not to introduce breaking changes, please keep the same data type (number, string, etc) if you want to change the default value. Do not use null or undefined for default value. // Do not use nested objects, keep it single level. Prefix the key if you need to group them. apiKey: '', systemMessage: '', showSystemMessage: true, - theme: 'system', + theme: ColorMode.SYSTEM, showThoughtInProgress: false, - showToolCalls: false, disableReasoningParsing: false, showRawOutputSwitch: false, keepStatsVisible: false, @@ -20,6 +22,12 @@ export const SETTING_CONFIG_DEFAULT: Record = alwaysShowSidebarOnDesktop: false, autoShowSidebarOnNewChat: true, autoMicOnEmpty: false, + mcpServers: '[]', + mcpServerUsageStats: '{}', // JSON object: { [serverId]: usageCount } + agenticMaxTurns: 10, + agenticMaxToolPreviewLines: 25, + showToolCallInProgress: false, + alwaysShowAgenticTurns: false, // make sure these default values are in sync with `common.h` samplers: 'top_k;typ_p;top_p;min_p;temperature', backend_sampling: false, @@ -91,8 +99,6 @@ export const SETTING_CONFIG_INFO: Record = { max_tokens: 'The maximum number of token per output. Use -1 for infinite (no limit).', custom: 'Custom JSON parameters to send to the API. Must be valid JSON format.', showThoughtInProgress: 'Expand thought process by default when generating messages.', - showToolCalls: - 'Display tool call labels and payloads from Harmony-compatible delta.tool_calls data below assistant messages.', disableReasoningParsing: 'Send reasoning_format=none to prevent server-side extraction of reasoning tokens into separate field', showRawOutputSwitch: @@ -113,8 +119,24 @@ export const SETTING_CONFIG_INFO: Record = { 'Automatically show sidebar when starting a new chat. Disable to keep the sidebar hidden until you click on it.', autoMicOnEmpty: 'Automatically show microphone button instead of send button when textarea is empty for models with audio modality support.', + mcpServers: + 'Configure MCP servers as a JSON list. Use the form in the MCP Client settings section to edit.', + mcpServerUsageStats: + 'Usage statistics for MCP servers. Tracks how many times tools from each server have been used.', + agenticMaxTurns: + 'Maximum number of tool execution cycles before stopping (prevents infinite loops).', + agenticMaxToolPreviewLines: + 'Number of lines shown in tool output previews (last N lines). Only these previews and the final LLM response persist after the agentic loop completes.', + showToolCallInProgress: + 'Automatically expand tool call details while executing and keep them expanded after completion.', pyInterpreterEnabled: 'Enable Python interpreter using Pyodide. Allows running Python code in markdown code blocks.', enableContinueGeneration: 'Enable "Continue" button for assistant messages. Currently works only with non-reasoning models.' }; + +export const SETTINGS_COLOR_MODES_CONFIG = [ + { value: ColorMode.SYSTEM, label: 'System', icon: Monitor }, + { value: ColorMode.LIGHT, label: 'Light', icon: Sun }, + { value: ColorMode.DARK, label: 'Dark', icon: Moon } +]; diff --git a/tools/server/webui/src/lib/constants/settings-keys.ts b/tools/server/webui/src/lib/constants/settings-keys.ts new file mode 100644 index 0000000000..1cfc2b6e9e --- /dev/null +++ b/tools/server/webui/src/lib/constants/settings-keys.ts @@ -0,0 +1,57 @@ +/** + * Settings key constants for ChatSettings configuration. + * + * These keys correspond to properties in SettingsConfigType and are used + * in settings field configurations to ensure consistency. + */ +export const SETTINGS_KEYS = { + // General + THEME: 'theme', + API_KEY: 'apiKey', + SYSTEM_MESSAGE: 'systemMessage', + PASTE_LONG_TEXT_TO_FILE_LEN: 'pasteLongTextToFileLen', + COPY_TEXT_ATTACHMENTS_AS_PLAIN_TEXT: 'copyTextAttachmentsAsPlainText', + ENABLE_CONTINUE_GENERATION: 'enableContinueGeneration', + PDF_AS_IMAGE: 'pdfAsImage', + ASK_FOR_TITLE_CONFIRMATION: 'askForTitleConfirmation', + // Display + SHOW_MESSAGE_STATS: 'showMessageStats', + SHOW_THOUGHT_IN_PROGRESS: 'showThoughtInProgress', + KEEP_STATS_VISIBLE: 'keepStatsVisible', + AUTO_MIC_ON_EMPTY: 'autoMicOnEmpty', + RENDER_USER_CONTENT_AS_MARKDOWN: 'renderUserContentAsMarkdown', + DISABLE_AUTO_SCROLL: 'disableAutoScroll', + ALWAYS_SHOW_SIDEBAR_ON_DESKTOP: 'alwaysShowSidebarOnDesktop', + AUTO_SHOW_SIDEBAR_ON_NEW_CHAT: 'autoShowSidebarOnNewChat', + // Sampling + TEMPERATURE: 'temperature', + DYNATEMP_RANGE: 'dynatemp_range', + DYNATEMP_EXPONENT: 'dynatemp_exponent', + TOP_K: 'top_k', + TOP_P: 'top_p', + MIN_P: 'min_p', + XTC_PROBABILITY: 'xtc_probability', + XTC_THRESHOLD: 'xtc_threshold', + TYP_P: 'typ_p', + MAX_TOKENS: 'max_tokens', + SAMPLERS: 'samplers', + BACKEND_SAMPLING: 'backend_sampling', + // Penalties + REPEAT_LAST_N: 'repeat_last_n', + REPEAT_PENALTY: 'repeat_penalty', + PRESENCE_PENALTY: 'presence_penalty', + FREQUENCY_PENALTY: 'frequency_penalty', + DRY_MULTIPLIER: 'dry_multiplier', + DRY_BASE: 'dry_base', + DRY_ALLOWED_LENGTH: 'dry_allowed_length', + DRY_PENALTY_LAST_N: 'dry_penalty_last_n', + // MCP + AGENTIC_MAX_TURNS: 'agenticMaxTurns', + ALWAYS_SHOW_AGENTIC_TURNS: 'alwaysShowAgenticTurns', + AGENTIC_MAX_TOOL_PREVIEW_LINES: 'agenticMaxToolPreviewLines', + SHOW_TOOL_CALL_IN_PROGRESS: 'showToolCallInProgress', + // Developer + DISABLE_REASONING_PARSING: 'disableReasoningParsing', + SHOW_RAW_OUTPUT_SWITCH: 'showRawOutputSwitch', + CUSTOM: 'custom' +} as const; diff --git a/tools/server/webui/src/lib/constants/settings-sections.ts b/tools/server/webui/src/lib/constants/settings-sections.ts index 9d8a4dba4d..a2d960d404 100644 --- a/tools/server/webui/src/lib/constants/settings-sections.ts +++ b/tools/server/webui/src/lib/constants/settings-sections.ts @@ -1,5 +1,8 @@ /** * Settings section titles constants for ChatSettings component. + * + * These titles define the navigation sections in the settings dialog. + * Used for both sidebar navigation and mobile horizontal scroll menu. */ export const SETTINGS_SECTION_TITLES = { GENERAL: 'General', @@ -7,8 +10,10 @@ export const SETTINGS_SECTION_TITLES = { SAMPLING: 'Sampling', PENALTIES: 'Penalties', IMPORT_EXPORT: 'Import/Export', + MCP: 'MCP', DEVELOPER: 'Developer' } as const; +/** Type for settings section titles */ export type SettingsSectionTitle = (typeof SETTINGS_SECTION_TITLES)[keyof typeof SETTINGS_SECTION_TITLES]; From 1305b73a467dcd4854eac7b538387f598b6f7be6 Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Tue, 17 Feb 2026 15:16:35 +0100 Subject: [PATCH 4/8] webui: remove MCP references and align pre-MCP UI architecture --- tools/server/webui/package-lock.json | 2162 ++--------------- tools/server/webui/package.json | 21 +- .../app/chat/ChatForm/ChatForm.svelte | 559 ++--- .../ChatFormActionAttachmentsDropdown.svelte | 389 +-- .../ChatFormActionFileAttachments.svelte | 143 ++ .../ChatFormActions/ChatFormActions.svelte | 49 +- .../app/chat/ChatMessages/ChatMessage.svelte | 253 +- .../ChatMessages/ChatMessageActions.svelte | 19 +- .../ChatMessages/ChatMessageAssistant.svelte | 362 +-- .../ChatMessages/ChatMessageEditForm.svelte | 353 ++- .../ChatMessages/ChatMessageSystem.svelte | 65 +- .../ChatMessageThinkingBlock.svelte | 68 + .../chat/ChatMessages/ChatMessageUser.svelte | 73 +- .../app/chat/ChatMessages/ChatMessages.svelte | 159 +- .../app/chat/ChatScreen/ChatScreen.svelte | 2 +- .../app/chat/ChatScreen/ChatScreenForm.svelte | 91 +- .../src/lib/components/app/chat/index.ts | 766 ------ .../webui/src/lib/components/app/index.ts | 63 +- .../app/models/ModelsSelector.svelte | 6 +- tools/server/webui/src/lib/constants/cache.ts | 33 +- .../src/lib/constants/default-context.ts | 1 + .../webui/src/lib/constants/input-classes.ts | 1 + .../src/lib/constants/settings-config.ts | 30 +- .../webui/src/lib/constants/settings-keys.ts | 57 - .../src/lib/constants/settings-sections.ts | 5 - tools/server/webui/src/lib/enums/files.ts | 29 +- tools/server/webui/src/lib/enums/index.ts | 27 +- tools/server/webui/src/lib/enums/ui.ts | 16 +- .../use-model-change-validation.svelte.ts | 104 + .../lib/markdown/resolve-attachment-images.ts | 5 +- .../lib/services/{chat.service.ts => chat.ts} | 291 +-- tools/server/webui/src/lib/services/index.ts | 261 +- .../webui/src/lib/stores/chat.svelte.ts | 1700 +++++++------ .../src/lib/stores/conversations.svelte.ts | 645 ++--- tools/server/webui/src/lib/types/chat.d.ts | 63 +- tools/server/webui/src/lib/types/common.d.ts | 34 +- .../server/webui/src/lib/types/database.d.ts | 50 +- tools/server/webui/src/lib/types/index.ts | 71 +- .../server/webui/src/lib/types/settings.d.ts | 14 +- .../webui/src/lib/utils/attachment-display.ts | 36 +- tools/server/webui/src/lib/utils/clipboard.ts | 120 +- .../src/lib/utils/convert-files-to-extra.ts | 20 +- .../server/webui/src/lib/utils/formatters.ts | 40 +- tools/server/webui/src/lib/utils/headers.ts | 44 - tools/server/webui/src/lib/utils/index.ts | 62 +- 45 files changed, 3131 insertions(+), 6231 deletions(-) create mode 100644 tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionFileAttachments.svelte create mode 100644 tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageThinkingBlock.svelte delete mode 100644 tools/server/webui/src/lib/components/app/chat/index.ts create mode 100644 tools/server/webui/src/lib/constants/default-context.ts create mode 100644 tools/server/webui/src/lib/constants/input-classes.ts delete mode 100644 tools/server/webui/src/lib/constants/settings-keys.ts create mode 100644 tools/server/webui/src/lib/hooks/use-model-change-validation.svelte.ts rename tools/server/webui/src/lib/services/{chat.service.ts => chat.ts} (79%) delete mode 100644 tools/server/webui/src/lib/utils/headers.ts diff --git a/tools/server/webui/package-lock.json b/tools/server/webui/package-lock.json index f19d4ff8ec..6834416824 100644 --- a/tools/server/webui/package-lock.json +++ b/tools/server/webui/package-lock.json @@ -8,7 +8,6 @@ "name": "webui", "version": "1.0.0", "dependencies": { - "@modelcontextprotocol/sdk": "^1.25.1", "highlight.js": "^11.11.1", "mode-watcher": "^1.1.0", "pdfjs-dist": "^5.4.54", @@ -20,36 +19,34 @@ "remark-html": "^16.0.1", "remark-rehype": "^11.1.2", "svelte-sonner": "^1.0.5", - "unist-util-visit": "^5.0.0", - "zod": "^4.2.1" + "unist-util-visit": "^5.0.0" }, "devDependencies": { - "@chromatic-com/storybook": "^5.0.0", + "@chromatic-com/storybook": "^4.1.2", "@eslint/compat": "^1.2.5", "@eslint/js": "^9.18.0", "@internationalized/date": "^3.10.1", "@lucide/svelte": "^0.515.0", "@playwright/test": "^1.49.1", - "@storybook/addon-a11y": "^10.2.4", - "@storybook/addon-docs": "^10.2.4", + "@storybook/addon-a11y": "^10.0.7", + "@storybook/addon-docs": "^10.0.7", "@storybook/addon-svelte-csf": "^5.0.10", - "@storybook/addon-vitest": "^10.2.4", - "@storybook/sveltekit": "^10.2.4", + "@storybook/addon-vitest": "^10.0.7", + "@storybook/sveltekit": "^10.0.7", "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.48.4", "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/forms": "^0.5.9", "@tailwindcss/typography": "^0.5.15", "@tailwindcss/vite": "^4.0.0", - "@types/node": "^24", + "@types/node": "^22", "@vitest/browser": "^3.2.3", - "@vitest/coverage-v8": "^3.2.3", "bits-ui": "^2.14.4", "clsx": "^2.1.1", "dexie": "^4.0.11", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", - "eslint-plugin-storybook": "^10.2.4", + "eslint-plugin-storybook": "^10.0.7", "eslint-plugin-svelte": "^3.0.0", "fflate": "^0.8.2", "globals": "^16.0.0", @@ -63,7 +60,7 @@ "rehype-katex": "^7.0.1", "remark-math": "^6.0.0", "sass": "^1.93.3", - "storybook": "^10.2.4", + "storybook": "^10.0.7", "svelte": "^5.38.2", "svelte-check": "^4.0.0", "tailwind-merge": "^3.3.1", @@ -116,42 +113,16 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/runtime": { "version": "7.27.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", @@ -162,39 +133,15 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/@chromatic-com/storybook": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-5.0.0.tgz", - "integrity": "sha512-8wUsqL8kg6R5ue8XNE7Jv/iD1SuE4+6EXMIGIuE+T2loBITEACLfC3V8W44NJviCLusZRMWbzICddz0nU0bFaw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-4.1.2.tgz", + "integrity": "sha512-QAWGtHwib0qsP5CcO64aJCF75zpFgpKK3jNpxILzQiPK3sVo4EmnVGJVdwcZWpWrGdH8E4YkncGoitw4EXzKMg==", "dev": true, "license": "MIT", "dependencies": { "@neoconfetti/react": "^1.0.0", - "chromatic": "^13.3.4", + "chromatic": "^12.0.0", "filesize": "^10.0.12", "jsonfile": "^6.1.0", "strip-ansi": "^7.1.0" @@ -204,7 +151,7 @@ "yarn": ">=1.22.18" }, "peerDependencies": { - "storybook": "^0.0.0-0 || ^10.1.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0" + "storybook": "^0.0.0-0 || ^9.0.0 || ^9.1.0-0 || ^9.2.0-0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0" } }, "node_modules/@esbuild/aix-ppc64": { @@ -650,9 +597,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, "license": "MIT", "dependencies": { @@ -849,18 +796,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@hono/node-server": { - "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", - "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -938,24 +873,6 @@ "@swc/helpers": "^0.5.0" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -969,16 +886,6 @@ "node": ">=18.0.0" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.12", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", @@ -1015,9 +922,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1052,68 +959,6 @@ "react": ">=16" } }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", - "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, "node_modules/@napi-rs/canvas": { "version": "0.1.76", "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.76.tgz", @@ -1668,17 +1513,6 @@ "node": ">=0.10" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@playwright/test": { "version": "1.56.1", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", @@ -1990,9 +1824,9 @@ "license": "MIT" }, "node_modules/@storybook/addon-a11y": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-10.2.4.tgz", - "integrity": "sha512-VGhdZ+iP2l/CSulIKV2kt3SMWVHntOigqWqGkNYf6YNYofynUYEKdsNqBvHx4ySuNEl/eXJ8LRO8FKYnU7LxZQ==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-10.0.7.tgz", + "integrity": "sha512-JsYPpZ/n67/2bI1XJeyrAWHHQkHemPkPHjCA0tAUnMz1Shlo/LV2q1Ahgpxoihx4strbHwZz71bcS4MqkHBduA==", "dev": true, "license": "MIT", "dependencies": { @@ -2004,20 +1838,20 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.2.4" + "storybook": "^10.0.7" } }, "node_modules/@storybook/addon-docs": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.2.4.tgz", - "integrity": "sha512-FzscAmdBiOGnGrxiEM+8eTg43kjqgjLfObg+lbJVRR/a0DmZ3xfAPNB0+VKYQbN0FacNcWLM9LZ/7U0hRBPBnQ==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.0.7.tgz", + "integrity": "sha512-qQQMoeYZC4W+/8ubfOZiTrE8nYC/f4wWP1uq4peRyDy1N2nIN9SwhyxwMn0m3VpeGmRBga5dLvJY9ko6SnJekg==", "dev": true, "license": "MIT", "dependencies": { "@mdx-js/react": "^3.0.0", - "@storybook/csf-plugin": "10.2.4", - "@storybook/icons": "^2.0.1", - "@storybook/react-dom-shim": "10.2.4", + "@storybook/csf-plugin": "10.0.7", + "@storybook/icons": "^1.6.0", + "@storybook/react-dom-shim": "10.0.7", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" @@ -2027,7 +1861,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.2.4" + "storybook": "^10.0.7" } }, "node_modules/@storybook/addon-svelte-csf": { @@ -2054,14 +1888,16 @@ } }, "node_modules/@storybook/addon-vitest": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/@storybook/addon-vitest/-/addon-vitest-10.2.4.tgz", - "integrity": "sha512-BT1iP89U4wcbpzTURU8WYTAeUcdNh4WIt0BqsnATmMwR/jKNJW6QgXCVqGQTSpRjWj40hX5e2JkQYCNXdjKsPw==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@storybook/addon-vitest/-/addon-vitest-10.0.7.tgz", + "integrity": "sha512-i6v/mAl+elrUxb+1f4NdnM17t/fg+KGJWL1U9quflXTd3KiLY0xJB4LwNP6yYo7Imc5NIO2fRkJbGvNqLBRe2Q==", "dev": true, "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/icons": "^2.0.1" + "@storybook/icons": "^1.6.0", + "prompts": "^2.4.0", + "ts-dedent": "^2.2.0" }, "funding": { "type": "opencollective", @@ -2071,7 +1907,7 @@ "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", - "storybook": "^10.2.4", + "storybook": "^10.0.7", "vitest": "^3.0.0 || ^4.0.0" }, "peerDependenciesMeta": { @@ -2090,13 +1926,13 @@ } }, "node_modules/@storybook/builder-vite": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.2.4.tgz", - "integrity": "sha512-/hcT1xj3CL5GkJ5v5/EguZdttDwNE6weNXK7vKzp034tnGcLycOossDsTiUQkBowSL+Ylc8aKj+ZgvddPNfOig==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.0.7.tgz", + "integrity": "sha512-wk2TAoUY5+9t78GWVBndu9rEo9lo6Ec3SRrLT4VpIlcS2GPK+5f26UC2uvIBwOF/N7JrUUKq/zWDZ3m+do9QDg==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/csf-plugin": "10.2.4", + "@storybook/csf-plugin": "10.0.7", "ts-dedent": "^2.0.0" }, "funding": { @@ -2104,7 +1940,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.2.4", + "storybook": "^10.0.7", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, @@ -2119,9 +1955,9 @@ } }, "node_modules/@storybook/csf-plugin": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.2.4.tgz", - "integrity": "sha512-kupPQEV+4N9mzsZHYaokvhO/KHBjYdWda9PNmPQwy0TR7r2mzthgaNH72TjmgN1L6DIbsuyOG1wtczcPJn4+Jg==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.0.7.tgz", + "integrity": "sha512-YaYYlCyJBwxaMk7yREOdz+9MDSgxIYGdeJ9EIq/bUndmkoj9SRo1P9/0lC5dseWQoiGy4T3PbZiWruD8uM5m3g==", "dev": true, "license": "MIT", "dependencies": { @@ -2134,7 +1970,7 @@ "peerDependencies": { "esbuild": "*", "rollup": "*", - "storybook": "^10.2.4", + "storybook": "^10.0.7", "vite": "*", "webpack": "*" }, @@ -2161,20 +1997,23 @@ "license": "MIT" }, "node_modules/@storybook/icons": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-2.0.1.tgz", - "integrity": "sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-1.6.0.tgz", + "integrity": "sha512-hcFZIjW8yQz8O8//2WTIXylm5Xsgc+lW9ISLgUk1xGmptIJQRdlhVIXCpSyLrQaaRiyhQRaVg7l3BD9S216BHw==", "dev": true, "license": "MIT", + "engines": { + "node": ">=14.0.0" + }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta" } }, "node_modules/@storybook/react-dom-shim": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.2.4.tgz", - "integrity": "sha512-i22OtrZ7GeZPt/odLf0vqyDhRSKyaLsHkkKSBcANQfzRRnBZmiz2FchOtWm9uvoDWybQsTruZq7kTdtpEhwyGw==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.0.7.tgz", + "integrity": "sha512-bp4OnMtZGwPJQDqNRi4K5iibLbZ2TZZMkWW7oSw5jjPFpGSreSjCe8LH9yj/lDnK8Ox9bGMCBFE5RV5XuML29w==", "dev": true, "license": "MIT", "funding": { @@ -2184,13 +2023,13 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.2.4" + "storybook": "^10.0.7" } }, "node_modules/@storybook/svelte": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/@storybook/svelte/-/svelte-10.2.4.tgz", - "integrity": "sha512-W9R51zUCd2iHOQBg/D93+bdpYv6kbtFx+kft5X8lPKQl6yEu0aKs9i5N5GyCASOhIApgx/tkqZIJ7vgM4cqrHA==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@storybook/svelte/-/svelte-10.0.7.tgz", + "integrity": "sha512-rO+YQhHucy47Vh67z318pALmd6x+K1Kj30Fb4a6oOEw4xn4zCo9KTmkMWs24c4oduEXD/eJu3badlRmsVXzyfA==", "dev": true, "license": "MIT", "peer": true, @@ -2203,19 +2042,19 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.2.4", + "storybook": "^10.0.7", "svelte": "^5.0.0" } }, "node_modules/@storybook/svelte-vite": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/@storybook/svelte-vite/-/svelte-vite-10.2.4.tgz", - "integrity": "sha512-FMgKMRdoZFDwPD6eIDMldcgp6d6NtIGuXyUJjb29qLias/gE5TI6hg+cWmmWXQRTrXwdyepeMBmIfRcZbB6REQ==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@storybook/svelte-vite/-/svelte-vite-10.0.7.tgz", + "integrity": "sha512-q9/RtrhX1CnznO6AO9MDEy1bsccbGeRxW28FLpgUrztV4IGZ/dFUrFIFurKRyuA3/nFsbtzp1F5jFt3RExmmTw==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/builder-vite": "10.2.4", - "@storybook/svelte": "10.2.4", + "@storybook/builder-vite": "10.0.7", + "@storybook/svelte": "10.0.7", "magic-string": "^0.30.0", "svelte2tsx": "^0.7.44", "typescript": "^4.9.4 || ^5.0.0" @@ -2226,28 +2065,28 @@ }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", - "storybook": "^10.2.4", + "storybook": "^10.0.7", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "node_modules/@storybook/sveltekit": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/@storybook/sveltekit/-/sveltekit-10.2.4.tgz", - "integrity": "sha512-1qDX35iSJHWo1AOd7HMzJtCHBfgahXqTWNiyZa/JMEKJ3qC1otaU8XMmTjsZ6fCRF99piNdgqtWM8+s1TJOldg==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@storybook/sveltekit/-/sveltekit-10.0.7.tgz", + "integrity": "sha512-ujTW7PfWvgBrzd7jzaZe9JgjUeM5YvBKm+xru6t7Dr4bdfmkKqlZHPRdXn/sy+fQNyfg6JL2WKy2KIIeA+RvSg==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/builder-vite": "10.2.4", - "@storybook/svelte": "10.2.4", - "@storybook/svelte-vite": "10.2.4" + "@storybook/builder-vite": "10.0.7", + "@storybook/svelte": "10.0.7", + "@storybook/svelte-vite": "10.0.7" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.2.4", + "storybook": "^10.0.7", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } @@ -2272,9 +2111,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.50.2", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.50.2.tgz", - "integrity": "sha512-875hTUkEbz+MyJIxWbQjfMaekqdmEKUUfR7JyKcpfMRZqcGyrO9Gd+iS1D/Dx8LpE5FEtutWGOtlAh4ReSAiOA==", + "version": "2.49.2", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.49.2.tgz", + "integrity": "sha512-Vp3zX/qlwerQmHMP6x0Ry1oY7eKKRcOWGc2P59srOp4zcqyn+etJyQpELgOi4+ZSUgteX8Y387NuwruLgGXLUQ==", "dev": true, "license": "MIT", "peer": true, @@ -2284,13 +2123,13 @@ "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", - "devalue": "^5.6.2", + "devalue": "^5.3.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", - "set-cookie-parser": "^3.0.0", + "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "bin": { @@ -2303,15 +2142,11 @@ "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": "^5.3.3", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "peerDependenciesMeta": { "@opentelemetry/api": { "optional": true - }, - "typescript": { - "optional": true } } }, @@ -2900,14 +2735,14 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.10.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.10.tgz", - "integrity": "sha512-+0/4J266CBGPUq/ELg7QUHhN25WYjE0wYTPSQJn1xeu8DOlIOPxXxrNGiLmfAWl7HMMgWFWXpt9IDjMWrF5Iow==", + "version": "22.16.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.5.tgz", + "integrity": "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~6.21.0" } }, "node_modules/@types/react": { @@ -3229,40 +3064,6 @@ } } }, - "node_modules/@vitest/coverage-v8": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", - "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^1.0.2", - "ast-v8-to-istanbul": "^0.3.3", - "debug": "^4.4.1", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.17", - "magicast": "^0.3.5", - "std-env": "^3.9.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "3.2.4", - "vitest": "3.2.4" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", @@ -3307,6 +3108,16 @@ } } }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/@vitest/pretty-format": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", @@ -3379,19 +3190,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -3432,45 +3230,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -3537,25 +3296,6 @@ "node": ">=4" } }, - "node_modules/ast-v8-to-istanbul": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz", - "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" - } - }, - "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" - }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -3613,9 +3353,9 @@ } }, "node_modules/bits-ui": { - "version": "2.15.5", - "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.15.5.tgz", - "integrity": "sha512-WhS+P+E//ClLfKU6KqjKC17nGDRLnz+vkwoP6ClFUPd5m1fFVDxTElPX8QVsduLj5V1KFDxlnv6sW2G5Lqk+vw==", + "version": "2.14.4", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.14.4.tgz", + "integrity": "sha512-W6kenhnbd/YVvur+DKkaVJ6GldE53eLewur5AhUCqslYQ0vjZr8eWlOfwZnMiPB+PF5HMVqf61vXBvmyrAmPWg==", "dev": true, "license": "MIT", "dependencies": { @@ -3683,46 +3423,6 @@ "svelte": "^5.30.2" } }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -3747,31 +3447,6 @@ "node": ">=8" } }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -3786,6 +3461,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3799,6 +3475,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3932,9 +3609,9 @@ } }, "node_modules/chromatic": { - "version": "13.3.5", - "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-13.3.5.tgz", - "integrity": "sha512-MzPhxpl838qJUo0A55osCF2ifwPbjcIPeElr1d4SHcjnHoIcg7l1syJDrAYK/a+PcCBrOGi06jPNpQAln5hWgw==", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-12.2.0.tgz", + "integrity": "sha512-GswmBW9ZptAoTns1BMyjbm55Z7EsIJnUvYKdQqXIBZIKbGErmpA+p4c0BYA+nzw5B0M+rb3Iqp1IaH8TFwIQew==", "dev": true, "license": "MIT", "bin": { @@ -4011,28 +3688,6 @@ "dev": true, "license": "MIT" }, - "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", @@ -4043,28 +3698,6 @@ "node": ">= 0.6" } }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/corser": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", @@ -4079,6 +3712,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -4117,9 +3751,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -4195,65 +3829,13 @@ "node": ">=0.10.0" } }, - "node_modules/default-browser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", - "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", - "dev": true, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" + "node": ">=6" } }, "node_modules/detect-libc": { @@ -4303,6 +3885,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4313,35 +3896,6 @@ "node": ">= 0.4" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/enhanced-resolve": { "version": "5.18.2", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", @@ -4373,6 +3927,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4382,6 +3937,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4398,6 +3954,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4460,12 +4017,6 @@ "@esbuild/win32-x64": "0.25.8" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -4558,184 +4109,17 @@ } }, "node_modules/eslint-plugin-storybook": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.2.4.tgz", - "integrity": "sha512-D8a6Y+iun2MSOpgps0Vd/t8y9Y5ZZ7O2VeKqw2PCv2+b7yInqogOS2VBMSRZVfP8TTGQgDpbUK67k7KZEUC7Ng==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.0.7.tgz", + "integrity": "sha512-qOQq9KdT1jsBgT3qsxUH2n67aj1WR8D1XCoER8Q6yuVlS5TimNwk1mZeWkXVf/o4RQQT6flT2y5cG2gPLZPvJA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^8.48.0" + "@typescript-eslint/utils": "^8.8.1" }, "peerDependencies": { "eslint": ">=8", - "storybook": "^10.2.4" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/project-service": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", - "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.54.0", - "@typescript-eslint/types": "^8.54.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/scope-manager": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", - "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", - "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/types": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", - "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", - "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.54.0", - "@typescript-eslint/tsconfig-utils": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", - "debug": "^4.4.3", - "minimatch": "^9.0.5", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", - "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", - "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.54.0", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "storybook": "^10.0.7" } }, "node_modules/eslint-plugin-svelte": { @@ -4886,16 +4270,6 @@ "node": ">=4.0" } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -4906,15 +4280,6 @@ "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -4922,27 +4287,6 @@ "dev": true, "license": "MIT" }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/expect-type": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", @@ -4953,76 +4297,6 @@ "node": ">=12.0.0" } }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", - "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", - "license": "MIT", - "dependencies": { - "ip-address": "10.0.1" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/express/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -5033,6 +4307,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -5079,22 +4354,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -5166,27 +4425,6 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -5246,41 +4484,6 @@ } } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -5300,6 +4503,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5309,6 +4513,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5333,6 +4538,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5342,27 +4548,6 @@ "node": ">= 0.4" } }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -5376,32 +4561,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/globals": { "version": "16.3.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", @@ -5419,6 +4578,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5455,6 +4615,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5467,6 +4628,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5734,16 +4896,6 @@ "node": ">=12.0.0" } }, - "node_modules/hono": { - "version": "4.11.7", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz", - "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=16.9.0" - } - }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -5757,13 +4909,6 @@ "node": ">=12" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, "node_modules/html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", @@ -5774,26 +4919,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", @@ -5904,52 +5029,12 @@ "node": ">=8" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, "node_modules/inline-style-parser": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", "license": "MIT" }, - "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5960,16 +5045,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -5983,145 +5058,35 @@ "node": ">=0.10.0" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.12.0" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, "node_modules/jiti": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", @@ -6132,15 +5097,6 @@ "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/jose": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", - "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6175,12 +5131,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "license": "BSD-2-Clause" - }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -6531,9 +5481,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true, "license": "MIT" }, @@ -6590,13 +5540,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -6616,34 +5559,6 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", @@ -6658,6 +5573,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7011,27 +5927,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -7665,31 +6560,6 @@ "node": ">=4" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -7744,9 +6614,9 @@ } }, "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", "dev": true, "license": "MIT", "dependencies": { @@ -7756,6 +6626,22 @@ "node": ">= 18" } }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/mode-watcher": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mode-watcher/-/mode-watcher-1.1.0.tgz", @@ -7821,15 +6707,6 @@ "dev": true, "license": "MIT" }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -7838,19 +6715,11 @@ "license": "MIT", "optional": true }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7859,46 +6728,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/opener": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", @@ -7959,13 +6788,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7992,15 +6814,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -8015,38 +6828,12 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -8096,15 +6883,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkce-challenge": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, "node_modules/playwright": { "version": "1.56.1", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", @@ -8460,6 +7238,30 @@ "node": ">=6" } }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -8470,19 +7272,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -8494,9 +7283,10 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -8529,46 +7319,6 @@ ], "license": "MIT" }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/react": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", @@ -8829,15 +7579,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -8889,51 +7630,22 @@ "@rollup/rollup-darwin-arm64": "4.45.1", "@rollup/rollup-darwin-x64": "4.45.1", "@rollup/rollup-freebsd-arm64": "4.45.1", - "@rollup/rollup-freebsd-x64": "4.45.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", - "@rollup/rollup-linux-arm-musleabihf": "4.45.1", - "@rollup/rollup-linux-arm64-gnu": "4.45.1", - "@rollup/rollup-linux-arm64-musl": "4.45.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-musl": "4.45.1", - "@rollup/rollup-linux-s390x-gnu": "4.45.1", - "@rollup/rollup-linux-x64-gnu": "4.45.1", - "@rollup/rollup-linux-x64-musl": "4.45.1", - "@rollup/rollup-win32-arm64-msvc": "4.45.1", - "@rollup/rollup-win32-ia32-msvc": "4.45.1", - "@rollup/rollup-win32-x64-msvc": "4.45.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/run-applescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@rollup/rollup-freebsd-x64": "4.45.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", + "@rollup/rollup-linux-arm-musleabihf": "4.45.1", + "@rollup/rollup-linux-arm64-gnu": "4.45.1", + "@rollup/rollup-linux-arm64-musl": "4.45.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-musl": "4.45.1", + "@rollup/rollup-linux-s390x-gnu": "4.45.1", + "@rollup/rollup-linux-x64-gnu": "4.45.1", + "@rollup/rollup-linux-x64-musl": "4.45.1", + "@rollup/rollup-win32-arm64-msvc": "4.45.1", + "@rollup/rollup-win32-ia32-msvc": "4.45.1", + "@rollup/rollup-win32-x64-msvc": "4.45.1", + "fsevents": "~2.3.2" } }, "node_modules/run-parallel": { @@ -8999,6 +7711,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, "license": "MIT" }, "node_modules/sass": { @@ -9045,9 +7758,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -9057,68 +7770,18 @@ "node": ">=10" } }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/set-cookie-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.0.1.tgz", - "integrity": "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", "dev": true, "license": "MIT" }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -9131,6 +7794,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9140,6 +7804,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9159,6 +7824,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9175,6 +7841,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -9193,6 +7860,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -9215,19 +7883,6 @@ "dev": true, "license": "ISC" }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/sirv": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", @@ -9243,6 +7898,13 @@ "node": ">=18" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9280,15 +7942,6 @@ "dev": true, "license": "MIT" }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/std-env": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", @@ -9297,24 +7950,23 @@ "license": "MIT" }, "node_modules/storybook": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.2.4.tgz", - "integrity": "sha512-LwF0VZsT4qkgx66Ad/q0QgZZrU2a5WftaADDEcJ3bGq3O2fHvwWPlSZjM1HiXD4vqP9U5JiMqQkV1gkyH0XJkw==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.0.7.tgz", + "integrity": "sha512-7smAu0o+kdm378Q2uIddk32pn0UdIbrtTVU+rXRVtTVTCrK/P2cCui2y4JH+Bl3NgEq1bbBQpCAF/HKrDjk2Qw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/icons": "^2.0.1", + "@storybook/icons": "^1.6.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", "@vitest/spy": "3.2.4", - "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", - "open": "^10.2.0", + "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", "recast": "^0.23.5", - "semver": "^7.7.3", - "use-sync-external-store": "^1.5.0", + "semver": "^7.6.2", "ws": "^8.18.0" }, "bin": { @@ -9333,60 +7985,6 @@ } } }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -9417,20 +8015,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-ansi/node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -9735,9 +8319,9 @@ } }, "node_modules/svelte2tsx": { - "version": "0.7.47", - "resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.7.47.tgz", - "integrity": "sha512-1aw/MFKVPM96OBevJdC12do2an9t5Zwr3Va9amLgTLpJje36ibD1iIHpuqCYWUrdR9vw6g6btKGQPmsqE8ZYCw==", + "version": "0.7.45", + "resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.7.45.tgz", + "integrity": "sha512-cSci+mYGygYBHIZLHlm/jYlEc1acjAHqaQaDFHdEBpUueM9kSTnPpvPtSl5VkJOU1qSJ7h1K+6F/LIUYiqC8VA==", "dev": true, "license": "MIT", "dependencies": { @@ -9807,63 +8391,23 @@ } }, "node_modules/tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", - "minizlib": "^3.1.0", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", "yallist": "^5.0.0" }, "engines": { "node": ">=18" } }, - "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -9945,15 +8489,6 @@ "node": ">=8.0" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", @@ -9985,9 +8520,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, "license": "MIT", "engines": { @@ -10050,20 +8585,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -10104,9 +8625,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, @@ -10292,19 +8813,10 @@ "node": ">= 10.0.0" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/unplugin": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", - "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz", + "integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==", "dev": true, "license": "MIT", "dependencies": { @@ -10334,16 +8846,6 @@ "dev": true, "license": "MIT" }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -10365,15 +8867,6 @@ "uuid": "dist-node/bin/uuid" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -10746,6 +9239,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -10784,97 +9278,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -10897,22 +9300,6 @@ } } }, - "node_modules/wsl-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", @@ -10942,25 +9329,6 @@ "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", "license": "MIT" }, - "node_modules/zod": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", - "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.25 || ^4" - } - }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/tools/server/webui/package.json b/tools/server/webui/package.json index f5cdc9e47f..a361ce76e3 100644 --- a/tools/server/webui/package.json +++ b/tools/server/webui/package.json @@ -23,32 +23,31 @@ "cleanup": "rm -rf .svelte-kit build node_modules test-results" }, "devDependencies": { - "@chromatic-com/storybook": "^5.0.0", + "@chromatic-com/storybook": "^4.1.2", "@eslint/compat": "^1.2.5", "@eslint/js": "^9.18.0", "@internationalized/date": "^3.10.1", "@lucide/svelte": "^0.515.0", "@playwright/test": "^1.49.1", - "@storybook/addon-a11y": "^10.2.4", - "@storybook/addon-docs": "^10.2.4", + "@storybook/addon-a11y": "^10.0.7", + "@storybook/addon-docs": "^10.0.7", "@storybook/addon-svelte-csf": "^5.0.10", - "@storybook/addon-vitest": "^10.2.4", - "@storybook/sveltekit": "^10.2.4", + "@storybook/addon-vitest": "^10.0.7", + "@storybook/sveltekit": "^10.0.7", "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.48.4", "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/forms": "^0.5.9", "@tailwindcss/typography": "^0.5.15", "@tailwindcss/vite": "^4.0.0", - "@types/node": "^24", + "@types/node": "^22", "@vitest/browser": "^3.2.3", - "@vitest/coverage-v8": "^3.2.3", "bits-ui": "^2.14.4", "clsx": "^2.1.1", "dexie": "^4.0.11", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", - "eslint-plugin-storybook": "^10.2.4", + "eslint-plugin-storybook": "^10.0.7", "eslint-plugin-svelte": "^3.0.0", "fflate": "^0.8.2", "globals": "^16.0.0", @@ -62,7 +61,7 @@ "rehype-katex": "^7.0.1", "remark-math": "^6.0.0", "sass": "^1.93.3", - "storybook": "^10.2.4", + "storybook": "^10.0.7", "svelte": "^5.38.2", "svelte-check": "^4.0.0", "tailwind-merge": "^3.3.1", @@ -79,7 +78,6 @@ "vitest-browser-svelte": "^0.1.0" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.25.1", "highlight.js": "^11.11.1", "mode-watcher": "^1.1.0", "pdfjs-dist": "^5.4.54", @@ -91,7 +89,6 @@ "remark-html": "^16.0.1", "remark-rehype": "^11.1.2", "svelte-sonner": "^1.0.5", - "unist-util-visit": "^5.0.0", - "zod": "^4.2.1" + "unist-util-visit": "^5.0.0" } } diff --git a/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatForm.svelte b/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatForm.svelte index fda3124a2e..e335f6c546 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatForm.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatForm.svelte @@ -1,29 +1,20 @@
{ - e.preventDefault(); - if (!canSubmit || disabled || isLoading || hasLoadingAttachments) return; - onSubmit?.(); - }} + onsubmit={handleSubmit} + class="relative {INPUT_CLASSES} border-radius-bottom-none mx-auto max-w-[48rem] overflow-hidden rounded-3xl backdrop-blur-md {disabled + ? 'cursor-not-allowed opacity-60' + : ''} {className}" + data-slot="chat-form" > -
- -
- { - handleInput(); - onValueChange?.(value); - }} - {disabled} - {placeholder} - /> - - {#if mcpHasResourceAttachments()} - { - preSelectedResourceUri = uri; - isResourcePickerOpen = true; - }} - /> - {/if} - - 0} - {disabled} - {isLoading} - {isRecording} - {uploadedFiles} - onFileUpload={handleFileUpload} - onMicClick={handleMicClick} - {onStop} - onSystemPromptClick={() => onSystemPromptClick?.({ message: value, files: uploadedFiles })} - onMcpPromptClick={showMcpPromptButton ? () => (isPromptPickerOpen = true) : undefined} - onMcpResourcesClick={() => (isResourcePickerOpen = true)} - /> -
+ 0 || uploadedFiles.length > 0} + hasText={message.trim().length > 0} + {disabled} + {isLoading} + {isRecording} + {uploadedFiles} + onFileUpload={handleFileUpload} + onMicClick={handleMicClick} + onStop={handleStop} + onSystemPromptClick={handleSystemPromptClick} + />
- { - mcpStore.attachResource(resource.uri); - }} - onOpenChange={(newOpen: boolean) => { - if (!newOpen) { - preSelectedResourceUri = undefined; - } - }} -/> + diff --git a/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAttachmentsDropdown.svelte b/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAttachmentsDropdown.svelte index 87d56d3ba3..f8c1b23b06 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAttachmentsDropdown.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAttachmentsDropdown.svelte @@ -1,31 +1,29 @@
- + @@ -113,222 +101,89 @@ variant="secondary" type="button" > - {fileUploadTooltipText} + {triggerTooltipText} -

{fileUploadTooltipText}

+

{triggerTooltipText}

- - {#if hasVisionModality} - onFileUpload?.()} - > - - - Images - - {:else} - - - + + {#each actions as item (item.id)} + {@const hasDisabledTooltip = !!item.disabled && !!item.disabledReason} + {@const hasEnabledTooltip = !item.disabled && !!item.tooltip} + + {#if hasDisabledTooltip} + + + + {#if item.id === 'images'} + + {:else if item.id === 'audio'} + + {:else if item.id === 'text'} + + {:else if item.id === 'pdf'} + + {:else} + + {/if} + + {item.label} + + + + +

{item.disabledReason}

+
+
+ {:else if hasEnabledTooltip} + + + handleActionClick(item.id)}> + {#if item.id === 'images'} + + {:else if item.id === 'audio'} + + {:else if item.id === 'text'} + + {:else if item.id === 'pdf'} + + {:else} + + {/if} + + {item.label} + + + + +

{item.tooltip}

+
+
+ {:else} + handleActionClick(item.id)}> + {#if item.id === 'images'} - - Images - -
- - -

Images require vision models to be processed

-
-
- {/if} - - {#if hasAudioModality} - onFileUpload?.()} - > - - - Audio Files - - {:else} - - - + {:else if item.id === 'audio'} - - Audio Files - - - - -

Audio files require audio models to be processed

-
-
- {/if} - - onFileUpload?.()} - > - - - Text Files - - - {#if hasVisionModality} - onFileUpload?.()} - > - - - PDF Files - - {:else} - - - onFileUpload?.()} - > + {:else if item.id === 'text'} + + {:else if item.id === 'pdf'} + {:else} + + {/if} - PDF Files - - - - -

PDFs will be converted to text. Image-based PDFs may not work properly.

-
-
- {/if} - - - - onSystemPromptClick?.()} - > - - - System Message + {item.label} - - - -

{systemMessageTooltip}

-
-
- - - - - - - - MCP Servers - - - - -
- {#each filteredMcpServers as server (server.id)} - {@const healthState = mcpStore.getHealthCheckState(server.id)} - {@const hasError = healthState.status === HealthCheckStatus.ERROR} - {@const isEnabledForChat = isServerEnabledForChat(server.id)} - - - {/each} -
- - {#snippet footer()} - - - - Manage MCP Servers - - {/snippet} -
-
-
- - {#if hasMcpPromptsSupport} - - - - MCP Prompt - - {/if} - - {#if hasMcpResourcesSupport} - - - - MCP Resources - - {/if} + {/if} + {/each}
diff --git a/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionFileAttachments.svelte b/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionFileAttachments.svelte new file mode 100644 index 0000000000..3545b4aebf --- /dev/null +++ b/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionFileAttachments.svelte @@ -0,0 +1,143 @@ + + +
+ + + + + + + + +

{fileUploadTooltipText}

+
+
+
+ + + + + onFileUpload?.()} + > + + + Images + + + + {#if !hasVisionModality} + +

Images require vision models to be processed

+
+ {/if} +
+ + + + onFileUpload?.()} + > + + + Audio Files + + + + {#if !hasAudioModality} + +

Audio files require audio models to be processed

+
+ {/if} +
+ + onFileUpload?.()} + > + + + Text Files + + + + + onFileUpload?.()} + > + + + PDF Files + + + + {#if !hasVisionModality} + +

PDFs will be converted to text. Image-based PDFs may not work properly.

+
+ {/if} +
+ + + + onSystemPromptClick?.()} + > + + + System Prompt + + + + +

Add a custom system message for this conversation

+
+
+
+
+
diff --git a/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActions.svelte b/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActions.svelte index 1197a8e347..cf5aca42a1 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActions.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActions.svelte @@ -5,19 +5,16 @@ ChatFormActionAttachmentsDropdown, ChatFormActionRecord, ChatFormActionSubmit, - McpServersSelector, ModelsSelector } from '$lib/components/app'; - import { DialogChatSettings } from '$lib/components/app/dialogs'; - import { SETTINGS_SECTION_TITLES } from '$lib/constants/settings-sections'; - import { mcpStore } from '$lib/stores/mcp.svelte'; import { FileTypeCategory } from '$lib/enums'; import { getFileTypeCategory } from '$lib/utils'; import { config } from '$lib/stores/settings.svelte'; import { modelsStore, modelOptions, selectedModelId } from '$lib/stores/models.svelte'; import { isRouterMode } from '$lib/stores/server.svelte'; import { chatStore } from '$lib/stores/chat.svelte'; - import { activeMessages, conversationsStore } from '$lib/stores/conversations.svelte'; + import { activeMessages, usedModalities } from '$lib/stores/conversations.svelte'; + import { useModelChangeValidation } from '$lib/hooks/use-model-change-validation.svelte'; interface Props { canSend?: boolean; @@ -31,8 +28,6 @@ onMicClick?: () => void; onStop?: () => void; onSystemPromptClick?: () => void; - onMcpPromptClick?: () => void; - onMcpResourcesClick?: () => void; } let { @@ -46,9 +41,7 @@ onFileUpload, onMicClick, onStop, - onSystemPromptClick, - onMcpPromptClick, - onMcpResourcesClick + onSystemPromptClick }: Props = $props(); let currentConfig = $derived(config()); @@ -162,18 +155,13 @@ selectorModelRef?.open(); } - let showChatSettingsDialogWithMcpSection = $state(false); - - let hasMcpPromptsSupport = $derived.by(() => { - const perChatOverrides = conversationsStore.getAllMcpServerOverrides(); - - return mcpStore.hasPromptsCapability(perChatOverrides); - }); - - let hasMcpResourcesSupport = $derived.by(() => { - const perChatOverrides = conversationsStore.getAllMcpServerOverrides(); - - return mcpStore.hasResourcesCapability(perChatOverrides); + const { handleModelChange } = useModelChangeValidation({ + getRequiredModalities: () => usedModalities(), + onValidationFailure: async (previousModelId: string | null) => { + if (previousModelId) { + await modelsStore.selectModelById(previousModelId); + } + } }); @@ -183,18 +171,8 @@ {disabled} {hasAudioModality} {hasVisionModality} - {hasMcpPromptsSupport} - {hasMcpResourcesSupport} {onFileUpload} {onSystemPromptClick} - {onMcpPromptClick} - {onMcpResourcesClick} - onMcpSettingsClick={() => (showChatSettingsDialogWithMcpSection = true)} - /> - - (showChatSettingsDialogWithMcpSection = true)} />
@@ -205,6 +183,7 @@ currentModel={conversationModel} forceForegroundText={true} useGlobalSelection={true} + onModelChange={handleModelChange} />
@@ -233,9 +212,3 @@ /> {/if}
- - (showChatSettingsDialogWithMcpSection = open)} - initialSection={SETTINGS_SECTION_TITLES.MCP} -/> diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessage.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessage.svelte index 29c326cf30..25895c83b7 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessage.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessage.svelte @@ -1,37 +1,61 @@ -{#if message.role === MessageRole.SYSTEM} +{#if message.role === 'system'} -{:else if mcpPromptExtra} - -{:else if message.role === MessageRole.USER} +{:else if message.role === 'user'} (shouldBranchAfterEdit = value)} {showDeleteDialog} {siblingInfo} + {thinkingContent} + {toolCallContent} /> {/if} diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageActions.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageActions.svelte index 97b34e92cc..dbd9b98228 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageActions.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageActions.svelte @@ -1,15 +1,14 @@
- {#if showProcessingInfoTop} + {#if thinkingContent} + + {/if} + + {#if message?.role === 'assistant' && isLoading() && !message?.content?.trim()}
- {processingState.getPromptProgressText() ?? - processingState.getProcessingMessage() ?? - 'Processing...'} + {processingState.getPromptProgressText() ?? processingState.getProcessingMessage()}
{/if} - {#if editCtx.isEditing} + {#if isEditing}
@@ -230,42 +221,30 @@ (shouldBranchAfterEdit = checked === true)} + onCheckedChange={(checked) => onShouldBranchAfterEditChange?.(checked === true)} />
- -
- {:else if message.role === MessageRole.ASSISTANT} + {:else if message.role === 'assistant'} {#if showRawOutput}
{messageContent || ''}
- {:else if isStructuredContent} - {:else} - + {/if} {:else}
@@ -273,52 +252,26 @@
{/if} - {#if showProcessingInfoBottom} -
-
- - {processingState.getPromptProgressText() ?? - processingState.getProcessingMessage() ?? - 'Processing...'} - -
-
- {/if} -
- {#if displayedModel} -
+ {#if displayedModel()} +
{#if isRouter} { - const status = modelsStore.getModelStatus(modelId); - - if (status !== ServerModelStatus.LOADED) { - await modelsStore.loadModel(modelId); - } - - onRegenerate(modelName); - return true; - }} + upToMessageId={message.id} /> {:else} - + {/if} {#if currentConfig.showMessageStats && message.timings && message.timings.predicted_n && message.timings.predicted_ms} - {@const agentic = message.timings.agentic} {:else if isLoading() && currentConfig.showMessageStats} {@const liveStats = processingState.getLiveProcessingStats()} @@ -340,11 +293,53 @@ {/if}
{/if} + + {#if config().showToolCalls} + {#if (toolCalls && toolCalls.length > 0) || fallbackToolCalls} + + + + + Tool calls: + + + {#if toolCalls && toolCalls.length > 0} + {#each toolCalls as toolCall, index (toolCall.id ?? `${index}`)} + {@const badge = formatToolCallBadge(toolCall, index)} + + {/each} + {:else if fallbackToolCalls} + + {/if} + + {/if} + {/if}
- {#if message.timestamp && !editCtx.isEditing} + {#if message.timestamp && !isEditing} diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte index 0f0e53b81b..c216ea690b 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte @@ -1,26 +1,79 @@ -
-